diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index abd5110185f..26301992871 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,33 +2,33 @@ name: Build and Test on: push jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest strategy: fail-fast: false matrix: - java-version: [ 21, 22-ea ] + java-version: [ 25, 26-ea ] steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: ${{ matrix.java-version }} distribution: temurin - name: Cache Maven artifacts - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Cache node - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os}}-node- - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml', '**/package.json') }} diff --git a/.github/workflows/publish-github-packages.yml b/.github/workflows/publish-github-packages.yml index d015eabf504..d5aeaf19aaa 100644 --- a/.github/workflows/publish-github-packages.yml +++ b/.github/workflows/publish-github-packages.yml @@ -9,32 +9,32 @@ on: jobs: publish: if: github.repository_owner == 'graphhopper' - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest permissions: contents: read packages: write steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: 17 distribution: temurin - name: Cache Maven artifacts - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Cache node - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os}}-node- - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml', '**/package.json') }} diff --git a/.github/workflows/publish-maven-central.yml b/.github/workflows/publish-maven-central.yml index de089b4892c..a898bd2da6b 100644 --- a/.github/workflows/publish-maven-central.yml +++ b/.github/workflows/publish-maven-central.yml @@ -8,10 +8,10 @@ on: jobs: maven-central-publish: if: github.repository_owner == 'graphhopper' - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-java@v3 + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 with: java-version: 17 distribution: temurin @@ -23,21 +23,21 @@ jobs: gpg-passphrase: GPG_PASSPHRASE - name: Cache Maven artifacts - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ~/.m2/repository key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os }}-maven- - name: Cache node - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml') }} restore-keys: | ${{ runner.os}}-node- - name: Cache node_modules - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: web-bundle/node_modules key: ${{ runner.os }}-node-${{ hashFiles('**/pom.xml', '**/package.json') }} @@ -53,6 +53,6 @@ jobs: mvn -B versions:set -DnewVersion=$GITHUB_REF_NAME -DgenerateBackupPoms=false mvn deploy -P release -DskipTests=true -Dpgp.secretkey=keyring:id=0E2FBADB -B env: - MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} + MAVEN_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + MAVEN_PASSWORD: ${{ secrets.CENTRAL_TOKEN }} GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} diff --git a/.github/workflows/trigger-benchmarks.yml b/.github/workflows/trigger-benchmarks.yml index 0aace67ba95..62930a09ace 100644 --- a/.github/workflows/trigger-benchmarks.yml +++ b/.github/workflows/trigger-benchmarks.yml @@ -3,7 +3,7 @@ on: push jobs: trigger_measurement: if: github.repository_owner == 'graphhopper' - runs-on: ubuntu-22.04 + runs-on: ubuntu-latest environment: benchmarks steps: - name: trigger diff --git a/.gitignore b/.gitignore index 9e896cadab7..a7ee26c871a 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,5 @@ local.properties **/node_modules .DS_Store /graph-cache -package-lock.json \ No newline at end of file +package-lock.json +.vscode/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 951edb14465..fdb4a2430d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,43 @@ -### 9.0 [not yet released] - +### 12.0 [not yet released] + +- weightings are now expected to return whole numbers, the built-in weightings (most importantly CustomWeighting) now return x10 their previous value (#3297) +- the default value for `graph.elevation.clear` changed to false as is much better when the cached tiles can be reused +- artificial tag way_distance does no longer include the elevation => renamed to way_distance_2d +- new edge.getDistance_mm/setDistance_mm methods for exact millimeter distances; distance accumulation now uses exact long arithmetic instead of lossy double summation (#3286) +- OSMReader no longer sets the artificial speed_from_duration tag but instead uses duration_in_seconds, when the duration tag is present (#3266) +- country rules were moved into parsers and are now enabled by default +- speeds generated from highway class now respects country-specific default speed limits, but the max_speed encoded value is now required; see #3249 + +### 11.0 [14 Oct 2025] + +- country-dependent toll rules are now always enabled. in the absence of explicit tags or special toll rules we use Toll.NO instead of Toll.MISSING #3111 +- max_weight_except: changed NONE to MISSING +- the list of restrictions for BIKE returned from OSMRoadAccessParser.toOSMRestrictions is again `[bicycle, vehicle, access]` and not `[bicycle, access]` like before #2981 +- road_access now contains value of highest transportation mode for CAR, i.e. access=private, motorcar=yes will now return YES and not PRIVATE +- car.json by default avoids private roads +- maxspeed<5 is ignored, maxspeed=none is ignored with some exceptions, maxspeed parsing and related constants were renamed #3077 +- improved performance by sorting graph during import, #3177 +- trunk roads in Austria are no longer considered to be toll roads by default + +### 10.0 [5 Nov 2024] + +- The config-example.yml uses a non-empty snap_preventions default array: [tunnel, bridge and ferry] for the /route endpoint +- the default u-turn time is now 0, the default u-turn weight is still infinite +- turn restriction support for restrictions with overlapping and/or multiple via-edges/ways, #3030 +- constructor of BaseGraph.Builder uses byte instead of integer count. +- KeyValue is now KValue as it holds the value only. Note, the two parameter constructor uses one value for the forward and one for the backward direction (and no longer "key, value") +- sac_scale priority handling for bicycles moved to the bike custom models + +### 9.0 [23 Apr 2024] + +- max_slope is now a signed decimal, see #2955 +- move sac_scale handling out of foot_access parser and made foot safer via lowering to sac_scale<2, same for hike sac_scale<5 +- removed shortest+fastest weightings, #2938 +- u_turn_costs information is no longer stored in profile. Use the TurnCostsConfig instead +- the custom models do no longer include the speed, access and priority encoded values only implicitly, see docs/migration/config-migration-08-09.md +- conditional access restriction tags are no longer considered from vehicle tag parsers and instead a car_temporal_access encoded value (similarly for bike + foot) can be used in a custom model. This fixes #2477. More details are accessible via path details named according to the OSM tags e.g. for access:conditional it is "access_conditional" (i.e. converted from OSM access:conditional). See #2863 and #2965. +- replaced (Vehicle)EncodedValueFactory and (Vehicle)TagParserFactory with ImportRegistry, #2935 +- encoded values used in custom models are added automatically, no need to add them to graph.encoded_values anymore, #2935 - removed the ability to sort the graph (graph.do_sort) due to incomplete support, #2919 - minor changes for import hooks, #2917 - removed wheelchair vehicle and related parsers, with currently no complete replacement as it needs to be redone properly with a custom model diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 7951aba1f62..74979b3be08 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -11,6 +11,7 @@ Here is an overview: * Anvoker, fixes like #1614 and helped with JUnit 5 migration #1632 * b3nn0, Android improvements * baumboi, path detail and landmark improvements + * baybatu, improved error for EncodingManager * boldtrn, one of the core developers with motorcycle knowledge :) * bt90, fixes like #2786 * cgarreau, increase of routing success rate via subnetwork cleanup @@ -19,6 +20,7 @@ Here is an overview: * ChristophKaser, agrees to the project's CLA, improved Android compatibility #1207 * Christoph Lingg, elevation smoothing #2772 * chucre, add special JSON output format, see #41 + * ctriley, added busway support * daisy1754, fixed usage of graphhopper.sh script * dardin88, instructions improved * dewos, web API bug fixes @@ -40,11 +42,15 @@ Here is an overview: * highsource, more efficient geometry update, UI fixes * hoofstephan, bug fix * IsNull, improvements like #708 + * IldarKhayrutdinov, use cache on different machines + * james-willis, improved isochrones triangulation robustness * Janekdererste, GUI for public transit * jansoe, many improvements regarding A* algorithm, forcing direction, roundabouts etc * jansonhanson, general host config + * jessLryan, max elevation can now be negative * joe-akeem, improvements like #2158 * JohannesPelzer, improved GPX information and various other things + * karololszacki, introduce `navigation_mode` option for Profiles to easily set which Voice Guidance distances to use * karussell, one of the core developers * khuebner, initial turn costs support * kodonnell, adding support for CH and other algorithms (#60) and penalizing inner-link U-turns (#88) @@ -58,14 +64,18 @@ Here is an overview: * michaz, one of the core developers * mprins, improvements for travis CI and regarding JDK9 #806 * msbarry, fixes like #1733 + * nakaner, documentation * naser13, fixes like #1923 * njanakiev, fixes like #1560 * NopMap, massive improvements regarding OSM, parsing and encoding, route relations * ocampana, initial implementation for instructions * oflebbe, work on iOS port and issues like #2060 * OlafFlebbeBosch, improvements like #2730 + * osamaalmaani, added missing config option for graph.encoded_values in the config-example.yml file * oschlueter, fixes like #1185 * otbutz, added multiple EncodedValues + * PabloaRuiz, pt_BR 1i8n improvements + * pantsleftinwash, speed parsing improvements * PGWelch, shapefile reader #874 * rafaelstelles, fix deserializer web-api * rajanski, script to do routing via PostGIS @@ -83,8 +93,12 @@ Here is an overview: * Svantulden, improved documentation and nearest API * taulinger, hopefully more to come * thehereward, code cleanups like #620 + * tyrasd, improved toll road handling in Austria #3190 + * Vectorial1024, country-specific speed limit issues like #3244, #3248 * vvikas, ideas for many to many improvements and #616 * zstadler, multiple fixes and car4wd + * binora, fix mode in navigation response converter + * jwltrr, fixed pillar node limits ## Translations diff --git a/NOTICE.md b/NOTICE.md index 6f4cbf8a318..706566e8707 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -2,28 +2,31 @@ GraphHopper licensed under the Apache license, Version 2.0 -Copyright 2012 - 2022 GraphHopper GmbH +Copyright 2012 - 2026 GraphHopper GmbH The core module includes the following additional software: * slf4j.org - SLF4J distributed under the MIT license. * com.carrotsearch:hppc (Apache license) - * SparseArray from the Android project (Apache license) * Snippets regarding mmap, vint/vlong and compression from Lucene (Apache license) * XMLGraphics-Commons for CGIAR elevation files (Apache License) - * Apache Commons Lang - we copied the implementation of the Levenshtein Distance (Apache License) - * Apache Commons Collections - we copied parts of the BinaryHeap (Apache License) + * libwebp from Google for PMTiles (BSD-3-Clause license) + * com.github.usefulness:webp-imageio for PMTiles (Apache license) + * Apache Commons Lang - we copied the implementation of the Levenshtein Distance - see com.graphhopper.apache.commons.lang3 (Apache License) + * Apache Commons Collections - we copied parts of the BinaryHeap - see com.graphhopper.apache.commons.collections (Apache License) * java-string-similarity - we copied the implementation of JaroWinkler (MIT license) * Jackson (Apache License) - * org.locationtech:jts (EDL), see #1039 + * org.locationtech:jts (EDL) * AngleCalc.atan2 from Jim Shima, 1999 (public domain) * janino compiler (BSD-3-Clause license) - * protobuf - New BSD license - * OSM-binary - LGPL license + * osm-legal-default-speeds-jvm (BSD-3-Clause license) + * kotlin stdlib (Apache License) + * protobuf from Google - (BSD-3-Clause license) + * OSM-binary - (LGPL license) * Osmosis - public domain, see their github under package/copying.txt reader-gtfs: - + * some files from com.conveyal:gtfs-lib (BSD 2-clause license) * com.google.transit:gtfs-realtime-bindings (Apache license) * com.google.guava:guava (Apache license) @@ -43,8 +46,7 @@ web: * org.eclipse.jetty:jetty-server (Apache License) * Dropwizard and dependencies (Apache license) - * no.ecc:java-vector-tile (Apache license) - * some images from mapbox https://www.mapbox.com/maki/, BSD License, see core/files + * classes in no.ecc are a copy of no.ecc.vectortile:java-vector-tile, see #2698 (Apache license) ## Data diff --git a/README.md b/README.md index 5d017e3a307..f855f0df0da 100644 --- a/README.md +++ b/README.md @@ -2,35 +2,53 @@ ![Build Status](https://github.com/graphhopper/graphhopper/actions/workflows/build.yml/badge.svg?branch=master) -GraphHopper is a fast and memory-efficient routing engine released under Apache License 2.0. It can be used as a Java library or standalone web server to calculate the distance, time, turn-by-turn instructions and many road attributes for a route between two or more points. Beyond this "A-to-B" routing it supports ["snap to road"](README.md#Map-Matching), [Isochrone calculation](README.md#Analysis), [mobile navigation](README.md#mobile-apps) and [more](README.md#Features). GraphHopper uses OpenStreetMap and GTFS data by default and it can import [other data sources too](README.md#OpenStreetMap-Support). +GraphHopper is a fast and memory-efficient routing engine released under Apache License 2.0. +It can be used as a Java library or standalone web server to calculate the distance, time, +turn-by-turn instructions and many road attributes for a route between two or more points. +Beyond this "A-to-B" routing it supports ["snap to road"](README.md#Map-Matching), +[Isochrone calculation](README.md#Analysis), [mobile navigation](README.md#mobile-apps) and +[more](README.md#Features). GraphHopper uses OpenStreetMap and GTFS data by default and it +can import [other data sources too](README.md#OpenStreetMap-Support). # Community -We have an open community and welcome everyone. Let us know your problems, use cases or just [say hello](https://discuss.graphhopper.com/). Please see our [community guidelines](https://graphhopper.com/agreements/cccoc.html). +We have an open community and welcome everyone. Let us know your problems, use cases or just [say hello](https://discuss.graphhopper.com/). +Please see our [community guidelines](https://graphhopper.com/agreements/cccoc.html). ## Questions -All questions go to our [forum](https://discuss.graphhopper.com/) where we also have subsections specially for developers, mobile usage, and [our map matching component](./map-matching). You can also search [Stackoverflow](http://stackoverflow.com/questions/tagged/graphhopper) for answers. Please do not use our issue section for questions :) +All questions go to our [forum](https://discuss.graphhopper.com/) where we also have subsections specially for developers, mobile usage, and [our map matching component](./map-matching). +You can also search [Stackoverflow](http://stackoverflow.com/questions/tagged/graphhopper) for answers. ## Contribute -Read through [how to contribute](CONTRIBUTING.md) for information on topics -like finding and fixing bugs and improving our documentation or translations! -We even have [good first issues](https://github.com/graphhopper/graphhopper/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) to get started. +Read through our [contributing guide](CONTRIBUTING.md) for information on topics +like finding and fixing bugs and improving our documentation or translations! +We also have [good first issues](https://github.com/graphhopper/graphhopper/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22) +to get started with contribution. ## Get Started -To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read through our documentation and install the GraphHopper Web Service locally. +To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read through [our documentation](./docs/index.md) and install GraphHopper including the Maps UI locally. -* 8.x: [documentation](https://github.com/graphhopper/graphhopper/blob/8.x/docs/index.md) - , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar) - , [announcement](https://www.graphhopper.com/blog/2023/10/18/graphhopper-routing-engine-8-0-released/) +* 11.x: [documentation](https://github.com/graphhopper/graphhopper/blob/11.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/11.0/graphhopper-web-11.0.jar) + , [announcement](https://www.graphhopper.com/blog/2025/10/14/graphhopper-routing-engine-11-0-released/) * unstable master: [documentation](https://github.com/graphhopper/graphhopper/blob/master/docs/index.md) -
Click to see older releases +See the [changelog file](./CHANGELOG.md) for Java API Changes. -* See our [changelog file](./CHANGELOG.md) for Java API Changes. +
Click to see older releases +* 10.x: [documentation](https://github.com/graphhopper/graphhopper/blob/10.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/10.0/graphhopper-web-10.0.jar) + , [announcement](https://www.graphhopper.com/blog/2024/11/05/graphhopper-routing-engine-10-0-released/) +* 9.x: [documentation](https://github.com/graphhopper/graphhopper/blob/9.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/9.1/graphhopper-web-9.1.jar) + , [announcement](https://www.graphhopper.com/blog/2024/04/23/graphhopper-routing-engine-9-0-released) +* 8.x: [documentation](https://github.com/graphhopper/graphhopper/blob/8.x/docs/index.md) + , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar) + , [announcement](https://www.graphhopper.com/blog/2023/10/18/graphhopper-routing-engine-8-0-released/) * 7.x: [documentation](https://github.com/graphhopper/graphhopper/blob/7.x/docs/index.md) , [web service jar](https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/7.0/graphhopper-web-7.0.jar) , [announcement](https://www.graphhopper.com/blog/2023/03/14/graphhopper-routing-engine-7-0-released/) @@ -88,7 +106,9 @@ To get started you can try [GraphHopper Maps](README.md#graphhopper-maps), read To install the [GraphHopper Maps](https://graphhopper.com/maps/) UI and the web service locally you [need a JVM](https://adoptium.net) (>= Java 17) and do: ```bash -wget https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/8.0/graphhopper-web-8.0.jar https://raw.githubusercontent.com/graphhopper/graphhopper/8.x/config-example.yml http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf +wget https://repo1.maven.org/maven2/com/graphhopper/graphhopper-web/11.0/graphhopper-web-11.0.jar \ + https://raw.githubusercontent.com/graphhopper/graphhopper/11.x/config-example.yml \ + http://download.geofabrik.de/europe/germany/berlin-latest.osm.pbf java -D"dw.graphhopper.datareader.file=berlin-latest.osm.pbf" -jar graphhopper*.jar server config-example.yml ``` @@ -104,13 +124,21 @@ The Docker images created by the community from the `master` branch can be found ## GraphHopper Maps -To see the road routing feature of GraphHopper in action please go to [GraphHopper Maps](https://graphhopper.com/maps). +The GraphHopper routing server uses GraphHopper Maps as web interface, which is also [open source](https://github.com/graphhopper/graphhopper-maps). -[![GraphHopper Maps](https://www.graphhopper.com/wp-content/uploads/2022/10/maps2-1024x661.png)](https://graphhopper.com/maps) +To see GraphHopper Maps in action go to [graphhopper.com/maps/](https://graphhopper.com/maps/), +which is an instance of GraphHopper Maps and available for free, via encrypted connections and from German servers - for a nice and private route planning experience! -GraphHopper Maps is an open source user interface, which you can find [here](https://github.com/graphhopper/graphhopper-maps). It can use this open source routing engine or the [GraphHopper Directions API](https://www.graphhopper.com), which provides the Routing API, a Route Optimization API (based on [jsprit](http://jsprit.github.io/)), a fast Matrix API and an address search (based on [photon](https://github.com/komoot/photon)). The photon project is also supported by the GraphHopper GmbH. Additionally to the GraphHopper Directions API, map tiles from various providers are used where the default is [Omniscale](http://omniscale.com/). +[![GraphHopper Maps](https://www.graphhopper.com/wp-content/uploads/2025/06/graphhopper-maps-2025.png)](https://graphhopper.com/maps) -All this is available for free, via encrypted connections and from German servers for a nice and private route planning experience! +## GraphHopper Directions API + +The GraphHopper Directions API is [our](https://www.graphhopper.com/) commercial offering that provides +[multiple APIs](https://docs.graphhopper.com) based on this open source routing engine: the Routing API, the Matrix API, the Isochrone API and the Map Matching API. + +It also provides the Route Optimization API, which is based on our open source [jsprit project](http://jsprit.github.io/) and uses the fast Matrix API behind the scenes. + +The address search is based on the open source [photon project](https://github.com/komoot/photon), which is supported by GraphHopper GmbH. ## Public Transit @@ -122,22 +150,23 @@ All this is available for free, via encrypted connections and from German server ### Online -There is a [web service](./navigation) that can be consumed by [our navigation Android client](https://github.com/graphhopper/graphhopper-navigation-example). +There is the [/navigate web service](./navigation) that can be consumed by [the Maplibre Navigation SDK](https://github.com/maplibre/maplibre-navigation-android) or [the ferrostar SDK](https://github.com/stadiamaps/ferrostar). -[![android navigation demo app](https://raw.githubusercontent.com/graphhopper/graphhopper-navigation-example/master/files/graphhopper-navigation-example.png)](https://github.com/graphhopper/graphhopper-navigation-example) +[](https://github.com/graphhopper/graphhopper-navigation-example) ### Offline -Offline routing is [no longer officially supported](https://github.com/graphhopper/graphhopper/issues/1940) but should still work. See -[version 1.0](https://github.com/graphhopper/graphhopper/blob/1.0/docs/android/index.md) with still an Android -demo and [this pull request](http://github.com/graphhopper/graphhopper-ios) of the iOS fork including a demo for iOS. - -[![simple routing](https://www.graphhopper.com/wp-content/uploads/2016/10/android-demo-screenshot-2.png)](./android/README.md) +Offline routing is [no longer officially supported](https://github.com/graphhopper/graphhopper/issues/1940) +but should still work as Android supports most of Java. See [version 1.0](https://github.com/graphhopper/graphhopper/blob/1.0/docs/android/index.md) +with the Android demo and also see [this pull request](http://github.com/graphhopper/graphhopper-ios) of the iOS fork including a demo for iOS. +[](./android/README.md) ## Analysis -Use isochrones to calculate and visualize the reachable area for a certain travel mode +Use isochrones to calculate and visualize the reachable area for a certain travel mode. + +You can try the debug user interface at http://localhost:8989/maps/isochrone/ to see the `/isochrone` and `/spt` endpoint in action. ### [Isochrone Web API](./docs/web/api-doc.md#isochrone) @@ -147,9 +176,6 @@ Use isochrones to calculate and visualize the reachable area for a certain trave [![high precision reachability image](https://www.graphhopper.com/wp-content/uploads/2018/06/berlin-reachability-768x401.png)](https://www.graphhopper.com/blog/2018/07/04/high-precision-reachability/) -To support these high precision reachability approaches there is the /spt -endpoint (shortest path tree). [See #1577](https://github.com/graphhopper/graphhopper/pull/1577) - ### [Map Matching](./map-matching) There is the map matching subproject to snap GPX traces to the road. @@ -223,34 +249,32 @@ client. ### Desktop -GraphHopper also runs on the Desktop in a Java application without internet access. -For debugging purposes GraphHopper can produce vector tiles, i.e. a visualization of the road network in the browser (see #1572). Also a more low level Swing-based UI is provided via MiniGraphUI in the tools module, see some -visualizations done with it [here](https://graphhopper.com/blog/2016/01/19/alternative-roads-to-rome/). -A fast and production ready map visualization for the Desktop can be implemented via [mapsforge](https://github.com/mapsforge/mapsforge) or [mapsforge vtm](https://github.com/mapsforge/vtm). +GraphHopper also runs on the Desktop in a Java application without internet access. For debugging +purposes GraphHopper can produce vector tiles, i.e. a visualization of the road network in the +browser (see #1572). Also a more low level Swing-based UI is provided via MiniGraphUI in the +tools module, see some visualizations done with it [here](https://graphhopper.com/blog/2016/01/19/alternative-roads-to-rome/). +A fast and production-ready map visualization for the Desktop can be implemented via [mapsforge](https://github.com/mapsforge/mapsforge) or [mapsforge vtm](https://github.com/mapsforge/vtm). # Features -Here is a list of the more detailed features: - * Works out of the box with OpenStreetMap (osm/xml and pbf) and can be adapted to custom data - * OpenStreetMap integration: stores and considers road type, speed limit, the surface, barriers, access restrictions, ferries, [conditional access restrictions](https://github.com/graphhopper/graphhopper/pull/621), ... + * OpenStreetMap integration: stores and considers road type, speed limit, the surface, barriers, access restrictions, ferries, conditional access restrictions and more * GraphHopper is fast. And with the so called "Contraction Hierarchies" it can be even faster (enabled by default). * Memory efficient data structures, algorithms and [the low and high level API](./docs/core/low-level-api.md) is tuned towards ease of use and efficiency - * Pre-built routing profiles: car, bike, racing bike, mountain bike, foot, hike, motorcycle, ... - * [Customization of these profiles](./docs/core/profiles.md#custom-profiles) are possible and e.g. get truck routing or support for cargo bikes and [many other changes](https://www.graphhopper.com/blog/2020/05/31/examples-for-customizable-routing/) + * Pre-built routing profiles: car, bike, racing bike, mountain bike, foot, hike, truck, bus, motorcycle, ... + * [Customization of these profiles](./docs/core/profiles.md#custom-profiles) are possible. Read about it [here](https://www.graphhopper.com/blog/2020/05/31/examples-for-customizable-routing/). * Provides a powerful [web API](./docs/web/api-doc.md) that exposes the data from OpenStreetMap and allows customizing the vehicle profiles per request. With JavaScript and Java clients. - * Does [map matching](./map-matching) - * Supports public transit routing and [GTFS](./reader-gtfs/README.md). - * Offers turn instructions in more than 45 languages, contribute or improve [here](./docs/core/translations.md) - * Displays and takes into account [elevation data](./docs/core/elevation.md) - * [Alternative routes](https://discuss.graphhopper.com/t/alternative-routes/424) - * [Turn costs and restrictions](./docs/core/turn-restrictions.md) - * Country specific routing via country rules - * Allows customizing routing behavior using custom areas - * The core uses only a few dependencies (hppc, jts, janino and slf4j) - * Scales from small indoor-sized to world-wide-sized graphs - * Finds nearest point on street e.g. to get elevation or 'snap to road' or being used as spatial index (see [#1485](https://github.com/graphhopper/graphhopper/pull/1485)) - * Calculates isochrones and [shortest path trees](https://github.com/graphhopper/graphhopper/pull/1577) - * Shows the whole road network in the browser for debugging purposes ("vector tile support") [#1572](https://github.com/graphhopper/graphhopper/pull/1572) - * Shows details along a route like road_class or max_speed ("path details") [#1142](https://github.com/graphhopper/graphhopper/pull/1142) - * Written Java and simple start for developers via Maven. + * Provides [map matching](./map-matching) i.e. "snap to road". + * Supports time-dependent public transit routing and reading [GTFS](./reader-gtfs/README.md). + * Offers turn instructions in more than 45 languages. Contribute or improve [here](./docs/core/translations.md). + * Displays and takes into account [elevation data](./docs/core/elevation.md). + * Supports [alternative routes](https://discuss.graphhopper.com/t/alternative-routes/424). + * Supports [turn costs and restrictions](./docs/core/turn-restrictions.md). + * Offers country-specific routing via country rules. + * Allows customizing routing behavior using custom areas. + * Scales from small indoor-sized to world-wide-sized graphs. + * Finds nearest point on street e.g. to get elevation or 'snap to road' or being used as spatial index (see [#1485](https://github.com/graphhopper/graphhopper/pull/1485)). + * Calculates isochrones and [shortest path trees](https://github.com/graphhopper/graphhopper/pull/1577). + * Shows the whole road network in the browser for debugging purposes ("vector tile support"), see [#1572](https://github.com/graphhopper/graphhopper/pull/1572). + * Shows so called "path details" along a route like road_class or max_speed, see [#1142](https://github.com/graphhopper/graphhopper/pull/1142) or the web documentation. + * Written in Java and simple to start for developers via Maven. diff --git a/benchmark/benchmark.sh b/benchmark/benchmark.sh index cdbc4c4d71f..d41d6e0a594 100755 --- a/benchmark/benchmark.sh +++ b/benchmark/benchmark.sh @@ -18,8 +18,8 @@ set -o xtrace defaultGraphDir=measurements/ defaultResultsDir=measurements/results/$(date '+%d-%m-%Y-%s%N')/ defaultSummaryDir=measurements/ -defaultSmallMap=core/files/andorra.osm.pbf -defaultBigMap=core/files/andorra.osm.pbf +defaultSmallMap=map-matching/files/leipzig_germany.osm.pbf +defaultBigMap=map-matching/files/leipzig_germany.osm.pbf defaultUseMeasurementTimeAsRefTime=false GRAPH_DIR=${1:-$defaultGraphDir} @@ -90,7 +90,7 @@ measurement.count=5000 \ measurement.use_measurement_time_as_ref_time=${USE_MEASUREMENT_TIME_AS_REF_TIME} echo "3 - big map with a custom model that is 'very customized', i.e. has many custom weighting rules" -echo "node-based CH + LM" +echo "node-based CH + LM + slow routing" java -cp tools/target/graphhopper-tools-*-jar-with-dependencies.jar \ -XX:+UseParallelGC -Xmx20g -Xms20g \ com.graphhopper.tools.Measurement \ @@ -102,10 +102,10 @@ measurement.clean=true \ measurement.stop_on_error=true \ measurement.summaryfile=${SUMMARY_DIR}summary_big_very_custom.dat \ measurement.repeats=1 \ -measurement.run_slow_routing=false \ +measurement.run_slow_routing=true \ measurement.weighting=custom \ measurement.custom_model_file=benchmark/very_custom.json \ -graph.encoded_values=max_width,max_height,toll,hazmat \ +graph.encoded_values=max_width,max_height,toll,hazmat,road_access,road_class \ measurement.ch.node=true \ measurement.ch.edge=false \ measurement.lm=true \ diff --git a/benchmark/very_custom.json b/benchmark/very_custom.json index ddd48ba0772..55a2d54b62f 100644 --- a/benchmark/very_custom.json +++ b/benchmark/very_custom.json @@ -1,11 +1,13 @@ // this is a heavily customized model used to benchmark routing with a custom weighting { "speed": [ + { "if": "true", "limit_to": "car_average_speed" }, { "if": "road_class == MOTORWAY", "multiply_by": "0.85" }, { "else_if": "road_class == PRIMARY", "multiply_by": "0.9" }, { "if": "true", "limit_to": "110" } ], "priority": [ + { "if": "!car_access", "multiply_by": "0" }, { "if": "road_access == PRIVATE", "multiply_by": "0" }, { "if": "max_height < 3.8", "multiply_by": "0" }, { "if": "max_width < 2.5", "multiply_by": "0" }, @@ -14,4 +16,4 @@ { "if": "toll != NO", "multiply_by": "0.5" }, { "if": "hazmat == NO", "multiply_by": "0" } ] -} \ No newline at end of file +} diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 069d1716972..952afb6ba49 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -22,14 +22,14 @@ 4.0.0 directions-api-client-hc - 9.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Directions API hand-crafted Java Client. com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT @@ -38,10 +38,11 @@ graphhopper-web-api ${project.parent.version} + com.squareup.okhttp3 - okhttp - 4.11.0 + okhttp-jvm + 5.3.2 org.slf4j diff --git a/client-hc/src/main/java/com/graphhopper/api/GHMRequest.java b/client-hc/src/main/java/com/graphhopper/api/GHMRequest.java index 3868f9a7cf9..d8081dd57bc 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GHMRequest.java +++ b/client-hc/src/main/java/com/graphhopper/api/GHMRequest.java @@ -28,7 +28,7 @@ public class GHMRequest { private List snapPreventions; private final PMap hints = new PMap(); private List outArrays = Collections.EMPTY_LIST; - private boolean failFast = true; + private boolean failFast = false; public GHMRequest setProfile(String profile) { this.profile = profile; diff --git a/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java b/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java index b6b8796dd77..273185d4d78 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java +++ b/client-hc/src/main/java/com/graphhopper/api/GHMatrixAbstractRequester.java @@ -303,7 +303,7 @@ protected String buildURLNoHints(String path, GHMRequest ghRequest) { return url; } - record JsonResult(String body, int statusCode) { + protected record JsonResult(String body, int statusCode, Map> headers) { } protected JsonResult postJson(String url, JsonNode data) throws IOException { @@ -318,7 +318,7 @@ protected JsonResult postJson(String url, JsonNode data) throws IOException { try { Response rsp = getDownloader().newCall(okRequest).execute(); body = rsp.body(); - return new JsonResult(body.string(), rsp.code()); + return new JsonResult(body.string(), rsp.code(), rsp.headers().toMultimap()); } finally { Helper.close(body); } diff --git a/client-hc/src/main/java/com/graphhopper/api/GHMatrixBatchRequester.java b/client-hc/src/main/java/com/graphhopper/api/GHMatrixBatchRequester.java index e38d4d5c6c0..e3adaba7d18 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GHMatrixBatchRequester.java +++ b/client-hc/src/main/java/com/graphhopper/api/GHMatrixBatchRequester.java @@ -18,7 +18,7 @@ package com.graphhopper.api; import com.fasterxml.jackson.databind.JsonNode; -import com.graphhopper.jackson.ResponsePathDeserializer; +import com.graphhopper.jackson.ResponsePathDeserializerHelper; import com.graphhopper.util.Helper; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -86,6 +86,7 @@ public MatrixResponse route(GHMRequest ghRequest) { try { String postUrl = buildURLNoHints("/calculate", ghRequest); JsonResult jsonResult = postJson(postUrl, requestJson); + matrixResponse.setHeaders(jsonResult.headers()); boolean debug = ghRequest.getHints().getBool("debug", false); if (debug) { logger.info("POST URL:" + postUrl + ", request:" + requestJson + ", response: " + jsonResult); @@ -94,7 +95,7 @@ public MatrixResponse route(GHMRequest ghRequest) { JsonNode responseJson = fromStringToJSON(postUrl, jsonResult.body()); if (responseJson.has("message")) { matrixResponse.setStatusCode(jsonResult.statusCode()); - matrixResponse.addErrors(ResponsePathDeserializer.readErrors(objectMapper, responseJson)); + matrixResponse.addErrors(ResponsePathDeserializerHelper.readErrors(objectMapper, responseJson)); return matrixResponse; } if (!responseJson.has("job_id")) { @@ -123,7 +124,7 @@ public MatrixResponse route(GHMRequest ghRequest) { if (debug) { logger.info(i + " GET URL:" + getUrl + ", response: " + rsp); } - matrixResponse.addErrors(ResponsePathDeserializer.readErrors(objectMapper, getResponseJson)); + matrixResponse.addErrors(ResponsePathDeserializerHelper.readErrors(objectMapper, getResponseJson)); matrixResponse.setStatusCode(rsp.statusCode()); if (matrixResponse.hasErrors()) { break; @@ -166,7 +167,7 @@ protected JsonResult getJson(String url) throws IOException { try { Response rsp = getDownloader().newCall(okRequest).execute(); body = rsp.body(); - return new JsonResult(body.string(), rsp.code()); + return new JsonResult(body.string(), rsp.code(), rsp.headers().toMultimap()); } finally { Helper.close(body); } diff --git a/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java b/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java index 30dfdd95e12..9441e0510c4 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java +++ b/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java @@ -1,7 +1,7 @@ package com.graphhopper.api; import com.fasterxml.jackson.databind.JsonNode; -import com.graphhopper.jackson.ResponsePathDeserializer; +import com.graphhopper.jackson.ResponsePathDeserializerHelper; import okhttp3.OkHttpClient; import java.io.IOException; @@ -19,7 +19,7 @@ public GHMatrixSyncRequester() { public GHMatrixSyncRequester(String serviceUrl) { this(serviceUrl, new OkHttpClient.Builder(). connectTimeout(5, TimeUnit.SECONDS). - readTimeout(5, TimeUnit.SECONDS).build(), true); + readTimeout(15, TimeUnit.SECONDS).build(), true); } public GHMatrixSyncRequester(String serviceUrl, OkHttpClient client, boolean doRequestGzip) { @@ -42,13 +42,14 @@ public MatrixResponse route(GHMRequest ghRequest) { String postUrl = buildURLNoHints("", ghRequest); JsonResult jsonResult = postJson(postUrl, requestJson); JsonNode responseJson = fromStringToJSON(postUrl, jsonResult.body()); + matrixResponse.setHeaders(jsonResult.headers()); matrixResponse.setStatusCode(jsonResult.statusCode()); if (responseJson.has("message")) { - matrixResponse.addErrors(ResponsePathDeserializer.readErrors(objectMapper, responseJson)); + matrixResponse.addErrors(ResponsePathDeserializerHelper.readErrors(objectMapper, responseJson)); return matrixResponse; } - matrixResponse.addErrors(ResponsePathDeserializer.readErrors(objectMapper, responseJson)); + matrixResponse.addErrors(ResponsePathDeserializerHelper.readErrors(objectMapper, responseJson)); if (!matrixResponse.hasErrors()) matrixResponse.addErrors(readUsableEntityError(ghRequest.getOutArrays(), responseJson)); diff --git a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java index ffd168b9d78..99c92c7bc17 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java +++ b/client-hc/src/main/java/com/graphhopper/api/GraphHopperWeb.java @@ -26,19 +26,15 @@ import com.graphhopper.GHResponse; import com.graphhopper.ResponsePath; import com.graphhopper.jackson.Jackson; -import com.graphhopper.jackson.ResponsePathDeserializer; +import com.graphhopper.jackson.ResponsePathDeserializerHelper; import com.graphhopper.util.CustomModel; import com.graphhopper.util.Helper; -import com.graphhopper.util.PMap; import com.graphhopper.util.Parameters; import com.graphhopper.util.shapes.GHPoint; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.ResponseBody; +import okhttp3.*; -import java.io.UnsupportedEncodingException; import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.TimeUnit; @@ -68,7 +64,7 @@ public class GraphHopperWeb { private String optimize = "false"; private boolean postRequest = true; private int maxUnzippedLength = 1000; - private final Set ignoreSet; + private final Set ignoreSetForGet; private final Set ignoreSetForPost; public static final String TIMEOUT = "timeout"; @@ -94,24 +90,26 @@ public GraphHopperWeb(String serviceUrl) { ignoreSetForPost.add("elevation"); ignoreSetForPost.add("optimize"); ignoreSetForPost.add("points_encoded"); + ignoreSetForPost.add("points_encoded_multiplier"); - ignoreSet = new HashSet<>(); - ignoreSet.add(KEY); - ignoreSet.add(CALC_POINTS); - ignoreSet.add("calcpoints"); - ignoreSet.add(INSTRUCTIONS); - ignoreSet.add("elevation"); - ignoreSet.add("optimize"); + ignoreSetForGet = new HashSet<>(); + ignoreSetForGet.add(KEY); + ignoreSetForGet.add(CALC_POINTS); + ignoreSetForGet.add("calcpoints"); + ignoreSetForGet.add(INSTRUCTIONS); + ignoreSetForGet.add("elevation"); + ignoreSetForGet.add("optimize"); // some parameters are in the request: - ignoreSet.add("algorithm"); - ignoreSet.add("locale"); - ignoreSet.add("point"); + ignoreSetForGet.add("algorithm"); + ignoreSetForGet.add("locale"); + ignoreSetForGet.add("point"); // some are special and need to be avoided - ignoreSet.add("points_encoded"); - ignoreSet.add("pointsencoded"); - ignoreSet.add("type"); + ignoreSetForGet.add("points_encoded"); + ignoreSetForGet.add("pointsencoded"); + ignoreSetForGet.add("points_encoded_multiplier"); + ignoreSetForGet.add("type"); objectMapper = Jackson.newObjectMapper(); } @@ -139,9 +137,8 @@ public GraphHopperWeb setKey(String key) { return this; } - /** - * Use new endpoint 'POST /route' instead of 'GET /route' + * If false it will use the 'GET /route' endpoint instead of the default 'POST /route'. */ public GraphHopperWeb setPostRequest(boolean postRequest) { this.postRequest = postRequest; @@ -179,8 +176,6 @@ public GraphHopperWeb setElevation(boolean withElevation) { * location is optimized according to the overall best route and returned * this way i.e. the traveling salesman problem is solved under the hood. * Note that in this case the request takes longer and costs more credits. - * For more details see: - * https://github.com/graphhopper/directions-api/blob/master/FAQ.md#what-is-one-credit */ public GraphHopperWeb setOptimize(String optimize) { this.optimize = optimize; @@ -195,24 +190,26 @@ public GHResponse route(GHRequest ghRequest) { ghRequest.getHints().remove("turn_description"); // do not include in request Request okRequest = postRequest ? createPostRequest(ghRequest) : createGetRequest(ghRequest); - rspBody = getClientForRequest(ghRequest).newCall(okRequest).execute().body(); + Response rsp = getClientForRequest(ghRequest).newCall(okRequest).execute(); + rspBody = rsp.body(); JsonNode json = objectMapper.reader().readTree(rspBody.byteStream()); GHResponse res = new GHResponse(); - res.addErrors(ResponsePathDeserializer.readErrors(objectMapper, json)); + res.addErrors(ResponsePathDeserializerHelper.readErrors(objectMapper, json)); if (res.hasErrors()) return res; JsonNode paths = json.get("paths"); for (JsonNode path : paths) { - ResponsePath altRsp = ResponsePathDeserializer.createResponsePath(objectMapper, path, tmpElevation, tmpTurnDescription); + ResponsePath altRsp = ResponsePathDeserializerHelper.createResponsePath(objectMapper, path, tmpElevation, tmpTurnDescription); res.add(altRsp); } + for (Map.Entry> entry : rsp.headers().toMultimap().entrySet()) { + res.getHints().putObject(entry.getKey(), entry.getValue()); + } JsonNode b = json.get("hints"); - PMap hints = new PMap(); - b.fields().forEachRemaining(f -> hints.putObject(f.getKey(), Helper.toObject(f.getValue().asText()))); - res.setHints(hints); + b.fields().forEachRemaining(f -> res.getHints().putObject(f.getKey(), Helper.toObject(f.getValue().asText()))); return res; @@ -240,7 +237,7 @@ Request createPostRequest(GHRequest ghRequest) { String tmpServiceURL = ghRequest.getHints().getString(SERVICE_URL, routeServiceUrl); String url = tmpServiceURL + "?"; if (!Helper.isEmpty(key)) - url += "key=" + key; + url += "key=" + encodeURL(key); ObjectNode requestJson = requestToJson(ghRequest); String body; @@ -266,8 +263,9 @@ ObjectNode requestToJson(GHRequest ghRequest) { requestJson.putArray("headings").addAll(createDoubleList(ghRequest.getHeadings())); if (!ghRequest.getCurbsides().isEmpty()) requestJson.putArray("curbsides").addAll(createStringList(ghRequest.getCurbsides())); - if (!ghRequest.getSnapPreventions().isEmpty()) + if (ghRequest.hasSnapPreventions()) requestJson.putArray("snap_preventions").addAll(createStringList(ghRequest.getSnapPreventions())); + if (!ghRequest.getPathDetails().isEmpty()) requestJson.putArray("details").addAll(createStringList(ghRequest.getPathDetails())); @@ -278,6 +276,7 @@ ObjectNode requestToJson(GHRequest ghRequest) { requestJson.put("algorithm", ghRequest.getAlgorithm()); requestJson.put("points_encoded", true); + requestJson.put("points_encoded_multiplier", 1e6); requestJson.put(INSTRUCTIONS, ghRequest.getHints().getBool(INSTRUCTIONS, instructions)); requestJson.put(CALC_POINTS, ghRequest.getHints().getBool(CALC_POINTS, calcPoints)); requestJson.put("elevation", ghRequest.getHints().getBool("elevation", elevation)); @@ -329,6 +328,7 @@ Request createGetRequest(GHRequest ghRequest) { + "&type=" + type + "&instructions=" + tmpInstructions + "&points_encoded=true" + + "&points_encoded_multiplier=1000000" + "&calc_points=" + tmpCalcPoints + "&algorithm=" + ghRequest.getAlgorithm() + "&locale=" + ghRequest.getLocale().toString() @@ -336,7 +336,7 @@ Request createGetRequest(GHRequest ghRequest) { + "&optimize=" + tmpOptimize; for (String details : ghRequest.getPathDetails()) { - url += "&" + Parameters.Details.PATH_DETAILS + "=" + details; + url += "&" + Parameters.Details.PATH_DETAILS + "=" + encodeURL(details); } // append *all* point hints if at least one is not empty @@ -355,9 +355,13 @@ Request createGetRequest(GHRequest ghRequest) { for (Double heading : ghRequest.getHeadings()) url += "&heading=" + heading; - - for (String snapPrevention : ghRequest.getSnapPreventions()) { - url += "&" + Parameters.Routing.SNAP_PREVENTION + "=" + encodeURL(snapPrevention); + if (ghRequest.hasSnapPreventions()) { + if (ghRequest.getSnapPreventions().isEmpty()) + url += "&" + Parameters.Routing.SNAP_PREVENTION + "="; // send empty value to indicate explicit empty list + else + for (String snapPrevention : ghRequest.getSnapPreventions()) { + url += "&" + Parameters.Routing.SNAP_PREVENTION + "=" + encodeURL(snapPrevention); + } } if (!key.isEmpty()) { @@ -369,7 +373,7 @@ Request createGetRequest(GHRequest ghRequest) { String urlValue = entry.getValue().toString(); // use lower case conversion for check only! - if (ignoreSet.contains(toLowerCase(urlKey))) { + if (ignoreSetForGet.contains(toLowerCase(urlKey))) { continue; } @@ -385,16 +389,20 @@ Request createGetRequest(GHRequest ghRequest) { public String export(GHRequest ghRequest) { String str = "Creating request failed"; + ResponseBody body = null; try { if (postRequest) throw new IllegalArgumentException("GPX export only works for GET requests, make sure to use `setPostRequest(false)`"); Request okRequest = createGetRequest(ghRequest); - str = getClientForRequest(ghRequest).newCall(okRequest).execute().body().string(); + body = getClientForRequest(ghRequest).newCall(okRequest).execute().body(); + str = body.string(); return str; } catch (Exception ex) { throw new RuntimeException("Problem while fetching export " + ghRequest.getPoints() + ", error: " + ex.getMessage() + " response: " + str, ex); + } finally { + Helper.close(body); } } @@ -426,10 +434,6 @@ private ArrayNode createPointList(List list) { } private static String encodeURL(String str) { - try { - return URLEncoder.encode(str, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); - } + return URLEncoder.encode(str, StandardCharsets.UTF_8); } } diff --git a/client-hc/src/main/java/com/graphhopper/api/MatrixResponse.java b/client-hc/src/main/java/com/graphhopper/api/MatrixResponse.java index 21e718b5f6d..63e342cec5b 100644 --- a/client-hc/src/main/java/com/graphhopper/api/MatrixResponse.java +++ b/client-hc/src/main/java/com/graphhopper/api/MatrixResponse.java @@ -1,9 +1,6 @@ package com.graphhopper.api; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; /** * This class defines the response for a M-to-N requests. @@ -12,6 +9,7 @@ */ public class MatrixResponse { + private Map> headers = new HashMap<>(); private String debugInfo = ""; private final List errors = new ArrayList<>(4); private final List disconnectedPoints = new ArrayList<>(0); @@ -50,6 +48,20 @@ public MatrixResponse(int fromCap, int toCap, boolean withTimes, boolean withDis throw new IllegalArgumentException("Please specify times, distances or weights that should be calculated by the matrix"); } + public void setHeaders(Map> headers) { + this.headers = headers; + } + + public Map> getHeaders() { + return headers; + } + + public String getHeader(String key, String defaultValue) { + List res = headers.get(key); + if (!res.isEmpty()) return res.get(0); + return defaultValue; + } + public void setFromRow(int row, long[] timeRow, int[] distanceRow, double[] weightRow) { if (times.length > 0) { check(timeRow.length, toCount, "to times"); diff --git a/client-hc/src/test/java/com/graphhopper/api/AbstractGHMatrixWebTester.java b/client-hc/src/test/java/com/graphhopper/api/AbstractGHMatrixWebTester.java index 6fc83ce6f7c..3f190aab8b1 100644 --- a/client-hc/src/test/java/com/graphhopper/api/AbstractGHMatrixWebTester.java +++ b/client-hc/src/test/java/com/graphhopper/api/AbstractGHMatrixWebTester.java @@ -196,7 +196,7 @@ public void noProfileWhenNotSpecified() { GHMatrixBatchRequester requester = new GHMatrixBatchRequester("url"); JsonNode json = requester.createPostRequest(new GHMRequest().setOutArrays(Collections.singletonList("weights")). setPoints(Arrays.asList(new GHPoint(11, 12))).setProfile("car")); - assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":true,\"profile\":\"car\"}", json.toString()); + assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":false,\"profile\":\"car\"}", json.toString()); } @Test @@ -207,7 +207,7 @@ public void hasProfile() { ghmRequest.setPoints(Arrays.asList(new GHPoint(11, 12))); ghmRequest.setProfile("bike"); JsonNode json = requester.createPostRequest(ghmRequest); - assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":true,\"profile\":\"bike\"}", json.toString()); + assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":false,\"profile\":\"bike\"}", json.toString()); } @Test @@ -219,7 +219,7 @@ public void hasHintsWhenSpecified() { ghmRequest.setOutArrays(Collections.singletonList("weights")); ghmRequest.setPoints(Arrays.asList(new GHPoint(11, 12))); JsonNode json = requester.createPostRequest(ghmRequest); - assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":true,\"profile\":\"car\",\"some_property\":\"value\"}", json.toString()); + assertEquals("{\"points\":[[12.0,11.0]],\"out_arrays\":[\"weights\"],\"fail_fast\":false,\"profile\":\"car\",\"some_property\":\"value\"}", json.toString()); ghmRequest.putHint("profile", "car"); Exception ex = assertThrows(IllegalArgumentException.class, () -> requester.createPostRequest(ghmRequest)); diff --git a/client-hc/src/test/java/com/graphhopper/api/Examples.java b/client-hc/src/test/java/com/graphhopper/api/Examples.java index 887742d070f..34eaf3314d3 100644 --- a/client-hc/src/test/java/com/graphhopper/api/Examples.java +++ b/client-hc/src/test/java/com/graphhopper/api/Examples.java @@ -75,7 +75,7 @@ public void routing() { InstructionList il = res.getInstructions(); for (Instruction i : il) { // System.out.println(i.getName()); - + // to get the translated turn instructions you call: // System.out.println(i.getTurnDescription(null)); // Note, that you can control the language only in via the request setLocale method and cannot change it only the client side @@ -85,6 +85,9 @@ public void routing() { for (PathDetail detail : pathDetails) { // System.out.println(detail.getValue()); } + + // get headers + // System.out.println(fullRes.getHeader("x-ratelimit-remaining", "0")); } @Test @@ -106,7 +109,9 @@ public void matrix() { if (responseSymm.hasErrors()) throw new RuntimeException(responseSymm.getErrors().toString()); // get time from first to second point: - // System.out.println(response.getTime(0, 1)); + // System.out.println(responseSymm.getTime(0, 1)); + // get header information: + // System.out.println(responseSymm.getHeader("x-ratelimit-remaining", "0")); // Option 2: for an asymmetric matrix do: ghmRequest = new GHMRequest(); @@ -121,6 +126,6 @@ public void matrix() { throw new RuntimeException(responseAsymm.getErrors().toString()); // get time from first to second point: - // System.out.println(response.getTime(0, 1)); + // System.out.println(responseAsymm.getTime(0, 1)); } } diff --git a/client-hc/src/test/java/com/graphhopper/api/GHMatrixBatchTest.java b/client-hc/src/test/java/com/graphhopper/api/GHMatrixBatchTest.java index 2a237ad4a87..edf474e9145 100644 --- a/client-hc/src/test/java/com/graphhopper/api/GHMatrixBatchTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/GHMatrixBatchTest.java @@ -2,7 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; -import java.io.IOException; +import java.util.HashMap; /** * @author Peter Karich @@ -17,12 +17,12 @@ GraphHopperMatrixWeb createMatrixClient(final String jsonTmp, int statusCode) { @Override protected JsonResult postJson(String url, JsonNode data) { - return new JsonResult("{\"job_id\": \"1\"}", statusCode); + return new JsonResult("{\"job_id\": \"1\"}", statusCode, new HashMap<>()); } @Override protected JsonResult getJson(String url) { - return new JsonResult(json, statusCode); + return new JsonResult(json, statusCode, new HashMap<>()); } }.setSleepAfterGET(0)); } diff --git a/client-hc/src/test/java/com/graphhopper/api/GHMatrixSyncTest.java b/client-hc/src/test/java/com/graphhopper/api/GHMatrixSyncTest.java index 953fce7b6d0..2ded245a662 100644 --- a/client-hc/src/test/java/com/graphhopper/api/GHMatrixSyncTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/GHMatrixSyncTest.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; +import java.util.HashMap; /** * @author Peter Karich @@ -23,7 +24,7 @@ GraphHopperMatrixWeb createMatrixClient(String jsonStr, int errorCode) throws IO @Override protected JsonResult postJson(String url, JsonNode data) { - return new JsonResult(finalJsonStr, errorCode); + return new JsonResult(finalJsonStr, errorCode, new HashMap<>()); } }); } diff --git a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java index 067883d953d..f64cc130341 100644 --- a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java @@ -19,8 +19,8 @@ import java.util.Arrays; import java.util.HashMap; -import java.util.LinkedHashMap; +import static com.graphhopper.json.Statement.If; import static org.junit.jupiter.api.Assertions.*; /** @@ -43,12 +43,12 @@ public void testGetClientForRequest(boolean usePost) { public void profileIncludedAsGiven() { GraphHopperWeb hopper = new GraphHopperWeb("https://localhost:8000/route"); // no vehicle -> no vehicle - assertEquals("https://localhost:8000/route?profile=&type=json&instructions=true&points_encoded=true" + + assertEquals("https://localhost:8000/route?profile=&type=json&instructions=true&points_encoded=true&points_encoded_multiplier=1000000" + "&calc_points=true&algorithm=&locale=en_US&elevation=false&optimize=false", hopper.createGetRequest(new GHRequest()).url().toString()); // vehicle given -> vehicle used in url - assertEquals("https://localhost:8000/route?profile=my_car&type=json&instructions=true&points_encoded=true" + + assertEquals("https://localhost:8000/route?profile=my_car&type=json&instructions=true&points_encoded=true&points_encoded_multiplier=1000000" + "&calc_points=true&algorithm=&locale=en_US&elevation=false&optimize=false", hopper.createGetRequest(new GHRequest().setProfile("my_car")).url().toString()); } @@ -59,7 +59,7 @@ public void headings() { GHRequest req = new GHRequest(new GHPoint(42.509225, 1.534728), new GHPoint(42.512602, 1.551558)). setHeadings(Arrays.asList(10.0, 90.0)). setProfile("car"); - assertEquals("http://localhost:8080/route?profile=car&point=42.509225,1.534728&point=42.512602,1.551558&type=json&instructions=true&points_encoded=true" + + assertEquals("http://localhost:8080/route?profile=car&point=42.509225,1.534728&point=42.512602,1.551558&type=json&instructions=true&points_encoded=true&points_encoded_multiplier=1000000" + "&calc_points=true&algorithm=&locale=en_US&elevation=false&optimize=false&heading=10.0&heading=90.0", hopper.createGetRequest(req).url().toString()); } @@ -92,9 +92,9 @@ public void customModel() throws JsonProcessingException { new GeometryFactory().createPolygon(area_2_coordinates), new HashMap<>())); CustomModel customModel = new CustomModel() - .addToSpeed(Statement.If("road_class == MOTORWAY", Statement.Op.LIMIT, "80")) - .addToPriority(Statement.If("surface == DIRT", Statement.Op.MULTIPLY, "0.7")) - .addToPriority(Statement.If("surface == SAND", Statement.Op.MULTIPLY, "0.6")) + .addToSpeed(If("road_class == MOTORWAY", Statement.Op.LIMIT, "80")) + .addToPriority(If("surface == DIRT", Statement.Op.MULTIPLY, "0.7")) + .addToPriority(If("surface == SAND", Statement.Op.MULTIPLY, "0.6")) .setDistanceInfluence(69d) .setHeadingPenalty(22) .setAreas(areas); @@ -108,15 +108,15 @@ public void customModel() throws JsonProcessingException { ObjectNode postRequest = client.requestToJson(req); JsonNode customModelJson = postRequest.get("custom_model"); ObjectMapper objectMapper = Jackson.newObjectMapper(); - JsonNode expected = objectMapper.readTree("{\"distance_influence\":69.0,\"heading_penalty\":22.0,\"internal\":false,\"areas\":{" + - "\"type\":\"FeatureCollection\",\"features\":["+ + JsonNode expected = objectMapper.readTree("{\"distance_influence\":69.0,\"heading_penalty\":22.0,\"areas\":{" + + "\"type\":\"FeatureCollection\",\"features\":[" + "{\"id\":\"area_1\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.019324184801185,11.28021240234375],[48.019324184801185,11.53564453125],[48.11843396091691,11.53564453125],[48.11843396091691,11.28021240234375],[48.019324184801185,11.28021240234375]]]},\"properties\":{}}," + "{\"id\":\"area_2\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.15509285476017,11.53289794921875],[48.15509285476017,11.8212890625],[48.281365151571755,11.8212890625],[48.281365151571755,11.53289794921875],[48.15509285476017,11.53289794921875]]]},\"properties\":{}}]}," + - "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}]," + - "\"priority\":[{\"if\":\"surface == DIRT\",\"multiply_by\":\"0.7\"},{\"if\":\"surface == SAND\",\"multiply_by\":\"0.6\"}]}"); + "\"priority\":[{\"if\":\"surface == DIRT\",\"multiply_by\":\"0.7\"},{\"if\":\"surface == SAND\",\"multiply_by\":\"0.6\"}]," + + "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}],\"turn_penalty\":[]}"); assertEquals(expected, objectMapper.valueToTree(customModelJson)); CustomModel cm = objectMapper.readValue("{\"distance_influence\":null}", CustomModel.class); assertNull(cm.getDistanceInfluence()); } -} \ No newline at end of file +} diff --git a/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java b/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java index ae783ce2a37..80eed059c76 100644 --- a/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java @@ -18,22 +18,22 @@ package com.graphhopper.api.model; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.graphhopper.jackson.Jackson; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Envelope; -import static io.dropwizard.testing.FixtureHelpers.fixture; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; public class GHGeocodingResponseRepresentationTest { @Test - public void testGeocodingRepresentation() throws JsonProcessingException { + public void testGeocodingRepresentation() throws IOException { ObjectMapper objectMapper = Jackson.newObjectMapper(); - GHGeocodingResponse geocodingResponse = objectMapper.readValue(fixture("fixtures/geocoding-response.json"), GHGeocodingResponse.class); + GHGeocodingResponse geocodingResponse = objectMapper.readValue(getClass().getResource("/fixtures/geocoding-response.json"), GHGeocodingResponse.class); Envelope extent = geocodingResponse.getHits().get(0).getExtent(); // Despite the unusual representation of the bounding box... assertEquals(10.0598605, extent.getMinX(), 0.0); diff --git a/config-example.yml b/config-example.yml index 497a91826fa..932fd47079e 100644 --- a/config-example.yml +++ b/config-example.yml @@ -14,12 +14,13 @@ graphhopper: # # In general a profile consists of the following # - name (required): a unique string identifier for the profile - # - vehicle (required): refers to the `graph.vehicles` used for this profile - # - weighting (required): the weighting used for this profile like custom - # - turn_costs (true/false, default: false): whether or not turn restrictions should be applied for this profile. + # - weighting (optional): by default 'custom' + # - turn_costs (optional): + # vehicle_types: [motorcar, motor_vehicle] (vehicle types used for vehicle-specific turn restrictions) + # u_turn_costs: 60 (time-penalty for doing a u-turn in seconds) + # allow_turn_penalty_in_request: true (enable custom turn_penalty for requests at runtime) # # Depending on the above fields there are other properties that can be used, e.g. - # - u_turn_costs: 60 (time-penalty for doing a u-turn in seconds (only possible when `turn_costs: true`)). # - custom_model_files: when you specified "weighting: custom" you need to set one or more json files which are searched in # custom_models.directory or the working directory that defines the custom_model. If you want an empty model you can # set "custom_model_files: [] @@ -29,28 +30,61 @@ graphhopper: # profiles (see below). Or at least limit the number of `routing.max_visited_nodes`. profiles: - - name: car - vehicle: car - custom_model: - distance_influence: 70 -# turn_costs: true -# u_turn_costs: 60 - -# - name: bike -# # to use the bike vehicle make sure to not ignore cycleways etc., see import.osm.ignored_highways below -# vehicle: bike -# custom_model_files: [bike.json, bike_elevation.json] - - # instead of the inbuilt custom models (see ./core/src/main/resources/com/graphhopper/custom_models) - # you can specify a folder where to find your own custom model files - # custom_models.directory: custom_models + - name: car + # By default car comes without turn restrictions as this requires less resources. Uncomment the following if you need it: +# turn_costs: +# vehicle_types: [motorcar, motor_vehicle] +# u_turn_costs: 60 +# for more advanced turn costs see avoid_turns.json + custom_model_files: [car.json] + # By default foot and bike are without CH preparation as this requires less resources. Add them to "profiles_ch" if you need it. + - name: foot + custom_model_files: [foot.json, foot_elevation.json] + + - name: bike + custom_model_files: [bike.json, bike_elevation.json] + +# Uncomment the following in-built profiles if you want more profiles. After you start GraphHopper it will print which encoded values you'll have to add to graph.encoded_values in this config file. +# +# - name: racingbike +# custom_model_files: [racingbike.json, bike_elevation.json] +# +# - name: mtb +# custom_model_files: [mtb.json, bike_elevation.json] +# +# - name: custom_profile +# navigation_mode: bike +# turn_costs: +# vehicle_types: [bicycle] +# u_turn_costs: 10 +# custom_model_files: [your_custom_model.json] +# +# # See the bus.json for more details. +# - name: bus +# turn_costs: +# vehicle_types: [bus, motor_vehicle] +# u_turn_costs: 60 +# custom_model_files: [bus.json] +# +# You can configure a profile with turn costs like "3 seconds for left" and "5 seconds for right turns" via: +# 1. add the turn_costs entry to the profile (see e.g. the car profile) +# 2. add orientation to the graph.encoded_values list +# 3. add avoid_turns.json to the custom_model_files +# Edit avoid_turns.json or create your own JSON file and put it into the "custom_models.directory". See also bike_tc.yml. +# +# Other custom models not listed here are: car4wd.json, motorcycle.json, truck.json or cargo-bike.json. You might need to modify and test them before production usage. +# See ./core/src/main/resources/com/graphhopper/custom_models and let us know if you customize them, improve them or create new onces! +# Also there is the curvature.json custom model which might be useful for a motorcyle profile or the opposite for a truck profile. +# Then specify a folder where to find your own custom model files: +# custom_models.directory: custom_models + # Speed mode: - # Its possible to speed up routing by doing a special graph preparation (Contraction Hierarchies, CH). This requires + # It's possible to speed up routing by doing a special graph preparation (Contraction Hierarchies, CH). This requires # more RAM/disk space for holding the prepared graph but also means less memory usage per request. Using the following # list you can define for which of the above routing profiles such preparation shall be performed. Note that to support - # profiles with `turn_costs: true` a more elaborate preparation is required (longer preparation time and more memory - # usage) and the routing will also be slower than with `turn_costs: false`. + # profiles with `turn_costs` a more elaborate preparation is required (longer preparation time and more memory + # usage) and the routing will also be slower than without `turn_costs`. profiles_ch: - profile: car @@ -66,23 +100,19 @@ graphhopper: profiles_lm: [] - #### Vehicles #### - - # The vehicle defines the base for how the routing of a profile behaves. It can be adjusted with the turn_costs=true - # option or, only for the roads vehicle, there is the transportation_mode option: - # name=mycustomvehicle,turn_costs=true,transportation_mode=MOTOR_VEHICLE - # But you should prefer to configure the turn_costs via the profile configuration. - # Other standard vehicles: foot,bike,mtb,racingbike - - #### Encoded Values #### # Add additional information to every edge. Used for path details (#1548) and custom models (docs/core/custom-models.md) - # Default values are: road_class,road_class_link,road_environment,max_speed,road_access - # More are: surface,smoothness,max_width,max_height,max_weight,max_weight_except,hgv,max_axle_load,max_length, - # hazmat,hazmat_tunnel,hazmat_water,lanes,osm_way_id,toll,track_type,mtb_rating,hike_rating,horse_rating, - # country,curvature,average_slope,max_slope - # graph.encoded_values: surface,toll,track_type + # Example values: + # average_slope,country,curvature,hazmat,hgv,hike_rating,lanes,max_height,max_length, ... + # For more details and a full list see the documentation: https://github.com/graphhopper/graphhopper/blob/master/docs/core/custom-models.md + # Private roads are blocked by default. To disable this you can specify block_private=false as option: + # car_access|block_private=false, bike_access|block_private=false + # By default we add the necessary encoded values for car.json, bike.json and foot.json (remove them if you do not need them) + graph.encoded_values: | + car_access, car_average_speed, country, road_class, roundabout, max_speed, + foot_access, foot_average_speed, foot_priority, foot_road_access, hike_rating, average_slope, + bike_access, bike_average_speed, bike_priority, bike_road_access, bike_network, mtb_rating, ferry_speed #### Speed, hybrid and flexible mode #### @@ -100,8 +130,9 @@ graphhopper: #### Elevation #### - # To populate your graph with elevation data use SRTM, default is noop (no elevation). Read more about it in docs/core/elevation.md - # graph.elevation.provider: srtm + # This populates your graph with elevation data using SRTM. Comment out to disable it (no elevation). Read more about it in docs/core/elevation.md + + graph.elevation.provider: srtm # default location for cache is /tmp/srtm # graph.elevation.cache_dir: ./srtmprovider/ @@ -121,7 +152,6 @@ graphhopper: # window size in meter along a way used for averaging a node's elevation # graph.elevation.edge_smoothing.moving_average.window_size: 150 - # To increase elevation profile resolution, use the following two parameters to tune the extra resolution you need # against the additional storage space used for edge geometries. You should enable bilinear interpolation when using # these features (see #1953 for details). @@ -163,13 +193,15 @@ graphhopper: prepare.min_network_size: 200 prepare.subnetworks.threads: 1 - #### Routing #### # You can define the maximum visited nodes when routing. This may result in not found connections if there is no # connection between two points within the given visited nodes. The default is Integer.MAX_VALUE. Useful for flexibility mode # routing.max_visited_nodes: 1000000 + # default for snap_preventions + routing.snap_preventions_default: tunnel, bridge, ferry + # The maximum time in milliseconds after which a routing request will be aborted. This has some routing algorithm # specific caveats, but generally it should allow the prevention of long-running requests. The default is Long.MAX_VALUE # routing.timeout_ms: 300000 @@ -185,23 +217,36 @@ graphhopper: #### Storage #### # Excludes certain types of highways during the OSM import to speed up the process and reduce the size of the graph. - # A typical application is excluding 'footway','cycleway','path' and maybe 'pedestrian' and 'track' highways for + # A typical application is excluding 'footway','cycleway','path' and maybe 'track' highways for # motorized vehicles. This leads to a smaller and less dense graph, because there are fewer ways (obviously), # but also because there are fewer crossings between highways (=junctions). # Another typical example is excluding 'motorway', 'trunk' and maybe 'primary' highways for bicycle or pedestrian routing. - import.osm.ignored_highways: footway,cycleway,path,pedestrian,steps # typically useful for motorized-only routing - # import.osm.ignored_highways: motorway,trunk # typically useful for non-motorized routing + import.osm.ignored_highways: '' + # import.osm.ignored_highways: footway,construction,cycleway,path,steps # use if you only have motorized-only vehicle profiles + # import.osm.ignored_highways: motorway,trunk # use if you only have non-motorized vehicle profiles + + # will write way names in the preferred language (language code as defined in ISO 639-1 or ISO 639-2): + # datareader.preferred_language: en # configure the memory access, use RAM_STORE for well equipped servers (default and recommended) graph.dataaccess.default_type: RAM_STORE - # will write way names in the preferred language (language code as defined in ISO 639-1 or ISO 639-2): - # datareader.preferred_language: en + # If MMAP is not suited for everything you can use it for selected files e.g. while import to reduce heap usage (see #2440): + # graph.dataaccess.type.geometry: MMAP + # graph.dataaccess.type.edges: MMAP + # graph.dataaccess.type.nodes: MMAP + # graph.dataaccess.type.nodes_ch.*: MMAP + # graph.dataaccess.type.shortcuts_.*: MMAP + + # If also for routing the environment is heap constrained and you can sacrify speed, then you can also use MMAP but maybe preload them: + # first rule matches + # graph.dataaccess.mmap.preload.nodes_ch_car.*: 100 + # graph.dataaccess.mmap.preload.nodes_ch.*: 30 #### Custom Areas #### # GraphHopper reads GeoJSON polygon files including their properties from this directory and makes them available - # to all tag parsers, vehicles and custom models. All GeoJSON Features require to have the "id" property. + # to all tag parsers and custom models. All GeoJSON Features require to have the "id" property. # Country borders are included automatically (see countries.geojson). # custom_areas.directory: path/to/custom_areas diff --git a/core/files/conditional-restrictions.osm.xml b/core/files/conditional-restrictions.osm.xml new file mode 100644 index 00000000000..091d3c70fad --- /dev/null +++ b/core/files/conditional-restrictions.osm.xml @@ -0,0 +1,393 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/files/near-badschandau-z10-11.pmtiles b/core/files/near-badschandau-z10-11.pmtiles new file mode 100644 index 00000000000..49ac93e61a6 Binary files /dev/null and b/core/files/near-badschandau-z10-11.pmtiles differ diff --git a/core/files/update-translations.sh b/core/files/update-translations.sh index a4c0e16abd7..a7165266cd2 100755 --- a/core/files/update-translations.sh +++ b/core/files/update-translations.sh @@ -3,12 +3,10 @@ cd $HOME/.. destination=src/main/resources/com/graphhopper/util/ -translations="en_US SKIP SKIP ar ast az bg bn_BN ca cs_CZ da_DK de_DE el eo es fa fil fi fr_FR fr_CH gl he hr_HR hsb hu_HU in_ID it ja ko kz lt_LT nb_NO ne nl pl_PL pt_BR pt_PT ro ru sk sl_SI sr_RS sv_SE tr uk uz vi_VN zh_CN zh_HK zh_TW" +translations="en_US SKIP SKIP ar ast az bg bn_BN ca cs_CZ da_DK de_DE el eo es fa fil fi fr_FR fr_CH gl he hr_HR hsb hu_HU in_ID it ja ko kz lt_LT mn nb_NO ne nl pl_PL pt_BR pt_PT ro ru sk sl_SI sr_RS sv_SE tr uk uz vi_VN zh_CN zh_HK zh_TW" file=$1 -# You can execute the following -# curl -L 'https://docs.google.com/spreadsheets/d/e/2PACX-1vTjOxfOBVw9VvEroPw30w77XA-JCCbraf4GeL9URMgK0kjfS-YT5R8TT6PACF8O7o6fhPKMsWKFf9M-/pub?output=tsv' > tmp.tsv -# ./files/update-translations.sh tmp.tsv && rm tmp.tsv +# See translations.md for how to run this INDEX=1 for tr in $translations; do diff --git a/core/nb-configuration.xml b/core/nb-configuration.xml deleted file mode 100644 index ef9a362e2e2..00000000000 --- a/core/nb-configuration.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - false - all - - diff --git a/core/pom.xml b/core/pom.xml index 0591d7da9d9..8678861d6c4 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ graphhopper-core GraphHopper Core - 9.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper is a fast and memory efficient Java road routing engine @@ -14,7 +14,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT @@ -82,10 +82,16 @@ + + + + com.github.usefulness + webp-imageio + de.westnordost osm-legal-default-speeds-jvm - 1.4 + 1.5 @@ -102,7 +108,7 @@ org.jetbrains.kotlin kotlin-stdlib - 1.6.20 + 2.2.21 diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index 0cdb0afe152..3568ecb4295 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -18,6 +18,10 @@ package com.graphhopper; import com.bedatadriven.jackson.datatype.jts.JtsModule; +import com.carrotsearch.hppc.BitSet; +import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.LongArrayList; +import com.carrotsearch.hppc.sorting.IndirectSort; import com.fasterxml.jackson.databind.ObjectMapper; import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; @@ -26,7 +30,6 @@ import com.graphhopper.reader.dem.*; import com.graphhopper.reader.osm.OSMReader; import com.graphhopper.reader.osm.RestrictionTagParser; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.*; import com.graphhopper.routing.ch.CHPreparationHandler; import com.graphhopper.routing.ch.PrepareContractionHierarchies; @@ -38,10 +41,13 @@ import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks; import com.graphhopper.routing.subnetwork.PrepareRoutingSubnetworks.PrepareJob; import com.graphhopper.routing.util.*; -import com.graphhopper.routing.util.countryrules.CountryRuleFactory; -import com.graphhopper.routing.util.parsers.*; +import com.graphhopper.routing.util.parsers.OSMBikeNetworkTagParser; +import com.graphhopper.routing.util.parsers.OSMFootNetworkTagParser; +import com.graphhopper.routing.util.parsers.TagParser; import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.routing.weighting.custom.CustomWeighting; +import com.graphhopper.routing.weighting.custom.NameValidator; import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; @@ -60,7 +66,10 @@ import java.nio.file.Paths; import java.text.DateFormat; import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.StreamSupport; import static com.graphhopper.util.GHUtility.readCountries; import static com.graphhopper.util.Helper.*; @@ -79,19 +88,19 @@ public class GraphHopper { // utils private final TranslationMap trMap = new TranslationMap().doImport(); boolean removeZipped = true; - // for country rules: - private CountryRuleFactory countryRuleFactory = null; + boolean calcChecksums = false; // for custom areas: private String customAreasDirectory = ""; // for graph: private BaseGraph baseGraph; private StorableProperties properties; - private EncodingManager encodingManager; + protected EncodingManager encodingManager; private OSMParsers osmParsers; - private int defaultSegmentSize = -1; + private int defaultSegmentSize = AbstractDataAccess.SEGMENT_SIZE_DEFAULT; private String ghLocation = ""; private DAType dataAccessDefaultType = DAType.RAM_STORE; private final LinkedHashMap dataAccessConfig = new LinkedHashMap<>(); + private boolean sortGraph = true; private boolean elevation = false; private LockFactory lockFactory = new NativeFSLockFactory(); private boolean allowWrites = true; @@ -122,15 +131,11 @@ public class GraphHopper { // for data reader private String osmFile; private ElevationProvider eleProvider = ElevationProvider.NOOP; - private VehicleEncodedValuesFactory vehicleEncodedValuesFactory = new DefaultVehicleEncodedValuesFactory(); - private VehicleTagParserFactory vehicleTagParserFactory = new DefaultVehicleTagParserFactory(); - private EncodedValueFactory encodedValueFactory = new DefaultEncodedValueFactory(); - private TagParserFactory tagParserFactory = new DefaultTagParserFactory(); + private ImportRegistry importRegistry = new DefaultImportRegistry(); private PathDetailsBuilderFactory pathBuilderFactory = new PathDetailsBuilderFactory(); private String dateRangeParserString = ""; private String encodedValuesString = ""; - private String vehiclesString = ""; public GraphHopper setEncodedValuesString(String encodedValuesString) { this.encodedValuesString = encodedValuesString; @@ -141,15 +146,6 @@ public String getEncodedValuesString() { return encodedValuesString; } - public GraphHopper setVehiclesString(String vehiclesString) { - this.vehiclesString = vehiclesString; - return this; - } - - public String getVehiclesString() { - return vehiclesString; - } - public EncodingManager getEncodingManager() { if (encodingManager == null) throw new IllegalStateException("EncodingManager not yet built"); @@ -252,8 +248,8 @@ public GraphHopper setStoreOnFlush(boolean storeOnFlush) { *
      * {@code
      *   hopper.setProfiles(
-     *     new Profile("my_car").setVehicle("car"),
-     *     new Profile("your_bike").setVehicle("bike")
+     *     new Profile("my_car"),
+     *     new Profile("your_bike")
      *   );
      *   hopper.getCHPreparationHandler().setCHProfiles(
      *     new CHProfile("my_car"),
@@ -298,6 +294,16 @@ public Profile getProfile(String profileName) {
         return profilesByName.get(profileName);
     }
 
+    public TransportationMode getNavigationMode(String profileName) {
+        Profile profile = profilesByName.get(profileName);
+        if (profile == null) return TransportationMode.CAR;
+        try {
+            return TransportationMode.valueOf(profile.getHints().getString("navigation_mode", profileName).toUpperCase(Locale.ROOT));
+        } catch (IllegalArgumentException e) {
+            return TransportationMode.CAR;
+        }
+    }
+
     /**
      * @return true if storing and fetching elevation data is enabled. Default is false
      */
@@ -351,6 +357,11 @@ public GraphHopper setMaxSpeedCalculator(MaxSpeedCalculator maxSpeedCalculator)
         return this;
     }
 
+    public GraphHopper setSortGraph(boolean sortGraph) {
+        this.sortGraph = sortGraph;
+        return this;
+    }
+
     /**
      * The underlying graph used in algorithms.
      *
@@ -421,36 +432,13 @@ public TranslationMap getTranslationMap() {
         return trMap;
     }
 
-    public GraphHopper setVehicleEncodedValuesFactory(VehicleEncodedValuesFactory factory) {
-        this.vehicleEncodedValuesFactory = factory;
-        return this;
-    }
-
-    public EncodedValueFactory getEncodedValueFactory() {
-        return this.encodedValueFactory;
-    }
-
-    public GraphHopper setEncodedValueFactory(EncodedValueFactory factory) {
-        this.encodedValueFactory = factory;
-        return this;
-    }
-
-    public VehicleTagParserFactory getVehicleTagParserFactory() {
-        return this.vehicleTagParserFactory;
-    }
-
-    public GraphHopper setVehicleTagParserFactory(VehicleTagParserFactory factory) {
-        this.vehicleTagParserFactory = factory;
+    public GraphHopper setImportRegistry(ImportRegistry importRegistry) {
+        this.importRegistry = importRegistry;
         return this;
     }
 
-    public TagParserFactory getTagParserFactory() {
-        return this.tagParserFactory;
-    }
-
-    public GraphHopper setTagParserFactory(TagParserFactory factory) {
-        this.tagParserFactory = factory;
-        return this;
+    public ImportRegistry getImportRegistry() {
+        return importRegistry;
     }
 
     public GraphHopper setCustomAreasDirectory(String customAreasDirectory) {
@@ -462,18 +450,6 @@ public String getCustomAreasDirectory() {
         return this.customAreasDirectory;
     }
 
-    /**
-     * Sets the factory used to create country rules. Use `null` to disable country rules
-     */
-    public GraphHopper setCountryRuleFactory(CountryRuleFactory countryRuleFactory) {
-        this.countryRuleFactory = countryRuleFactory;
-        return this;
-    }
-
-    public CountryRuleFactory getCountryRuleFactory() {
-        return this.countryRuleFactory;
-    }
-
     /**
      * Reads the configuration from a {@link GraphHopperConfig} object which can be manually filled, or more typically
      * is read from `config.yml`.
@@ -508,7 +484,6 @@ public GraphHopper init(GraphHopperConfig ghConfig) {
         }
         ghLocation = graphHopperFolder;
 
-        countryRuleFactory = ghConfig.getBool("country_rules.enabled", false) ? new CountryRuleFactory() : null;
         customAreasDirectory = ghConfig.getString("custom_areas.directory", customAreasDirectory);
 
         defaultSegmentSize = ghConfig.getInt("graph.dataaccess.segment_size", defaultSegmentSize);
@@ -522,6 +497,7 @@ public GraphHopper init(GraphHopperConfig ghConfig) {
                 dataAccessConfig.put(entry.getKey().substring("graph.dataaccess.mmap.".length()), entry.getValue().toString());
         }
 
+        sortGraph = ghConfig.getBool("graph.sort", sortGraph);
         if (ghConfig.getBool("max_speed_calculator.enabled", false))
             maxSpeedCalculator = new MaxSpeedCalculator(MaxSpeedCalculator.createLegalDefaultSpeeds());
 
@@ -540,11 +516,10 @@ public GraphHopper init(GraphHopperConfig ghConfig) {
         String customModelFolder = ghConfig.getString("custom_models.directory", ghConfig.getString("custom_model_folder", ""));
         setProfiles(GraphHopper.resolveCustomModelFiles(customModelFolder, ghConfig.getProfiles(), globalAreas));
 
-        if (ghConfig.has("graph.vehicles") && ghConfig.has("graph.flag_encoders"))
-            throw new IllegalArgumentException("Remove graph.flag_encoders as it cannot be used in parallel with graph.vehicles");
+        if (ghConfig.has("graph.vehicles"))
+            throw new IllegalArgumentException("The option graph.vehicles is no longer supported. Use the appropriate turn_costs and custom_model instead, see docs/migration/config-migration-08-09.md");
         if (ghConfig.has("graph.flag_encoders"))
-            logger.warn("The option graph.flag_encoders is deprecated and will be removed. Replace with graph.vehicles");
-        vehiclesString = ghConfig.getString("graph.vehicles", ghConfig.getString("graph.flag_encoders", vehiclesString));
+            throw new IllegalArgumentException("The option graph.flag_encoders is no longer supported.");
 
         encodedValuesString = ghConfig.getString("graph.encoded_values", encodedValuesString);
         dateRangeParserString = ghConfig.getString("datareader.date_range_parser_day", dateRangeParserString);
@@ -618,139 +593,102 @@ public GraphHopper init(GraphHopperConfig ghConfig) {
                     + " should be less or equal to landmark count of " + lmPreparationHandler.getLandmarks());
         routerConfig.setActiveLandmarkCount(activeLandmarkCount);
 
+        calcChecksums = ghConfig.getBool("graph.calc_checksums", false);
+
         return this;
     }
 
-    protected EncodingManager buildEncodingManager(Map vehiclePropsByVehicle, List encodedValueStrings,
-                                                   boolean withUrbanDensity, boolean withMaxSpeedEst, Collection profiles) {
+    protected EncodingManager buildEncodingManager(Map encodedValuesWithProps,
+                                                   Map activeImportUnits,
+                                                   Map> restrictionVehicleTypesByProfile) {
+        List encodedValues = new ArrayList<>(activeImportUnits.entrySet().stream()
+                .map(e -> {
+                    Function f = e.getValue().getCreateEncodedValue();
+                    return f == null ? null : f.apply(encodedValuesWithProps.getOrDefault(e.getKey(), new PMap()));
+                })
+                .filter(Objects::nonNull)
+                .toList());
+
+        encodedValues.addAll(createSubnetworkEncodedValues());
+
+        List sortedEVs = getEVSortIndex(profilesByName);
+        encodedValues.sort(Comparator.comparingInt(ev -> sortedEVs.indexOf(ev.getName())));
+
         EncodingManager.Builder emBuilder = new EncodingManager.Builder();
-        vehiclePropsByVehicle.forEach((name, props) -> emBuilder.add(vehicleEncodedValuesFactory.createVehicleEncodedValues(name, props)));
-        profiles.forEach(profile -> emBuilder.add(Subnetwork.create(profile.getName())));
-        if (withMaxSpeedEst)
-            emBuilder.add(MaxSpeedEstimated.create());
-        if (withUrbanDensity)
-            emBuilder.add(UrbanDensity.create());
-        encodedValueStrings.forEach(s -> emBuilder.add(encodedValueFactory.create(s, new PMap())));
+        encodedValues.forEach(emBuilder::add);
+        restrictionVehicleTypesByProfile.entrySet().stream()
+                .filter(e -> !e.getValue().isEmpty())
+                .forEach(e -> emBuilder.addTurnCostEncodedValue(TurnRestriction.create(e.getKey())));
         return emBuilder.build();
     }
 
-    protected OSMParsers buildOSMParsers(Map vehiclePropsByVehicle, List encodedValueStrings,
-                                         List ignoredHighways, String dateRangeParserString) {
+    protected List createSubnetworkEncodedValues() {
+        return profilesByName.values().stream().map(profile -> Subnetwork.create(profile.getName())).toList();
+    }
+
+    protected List getEVSortIndex(Map profilesByName) {
+        return Collections.emptyList();
+    }
+
+    protected OSMParsers buildOSMParsers(Map encodedValuesWithProps,
+                                         Map activeImportUnits,
+                                         Map> restrictionVehicleTypesByProfile,
+                                         List ignoredHighways) {
+        ImportUnitSorter sorter = new ImportUnitSorter(activeImportUnits);
+        Map sortedImportUnits = new LinkedHashMap<>();
+        sorter.sort().forEach(name -> sortedImportUnits.put(name, activeImportUnits.get(name)));
+        List sortedParsers = new ArrayList<>();
+        sortedImportUnits.forEach((name, importUnit) -> {
+            BiFunction createTagParser = importUnit.getCreateTagParser();
+            if (createTagParser != null) {
+                PMap pmap = encodedValuesWithProps.getOrDefault(name, new PMap());
+                if (!pmap.has("date_range_parser_day"))
+                    pmap.putObject("date_range_parser_day", dateRangeParserString);
+                sortedParsers.add(createTagParser.apply(encodingManager, pmap));
+            }
+        });
+
         OSMParsers osmParsers = new OSMParsers();
         ignoredHighways.forEach(osmParsers::addIgnoredHighway);
-        for (String s : encodedValueStrings) {
-            TagParser tagParser = tagParserFactory.create(encodingManager, s, new PMap());
-            if (tagParser != null)
-                osmParsers.addWayTagParser(tagParser);
-        }
-
-        // this needs to be in sync with the default EVs added in EncodingManager.Builder#build. ideally I would like to remove
-        // all these defaults and just use the config as the single source of truth
-        if (!encodedValueStrings.contains(Roundabout.KEY))
-            osmParsers.addWayTagParser(new OSMRoundaboutParser(encodingManager.getBooleanEncodedValue(Roundabout.KEY)));
-        if (!encodedValueStrings.contains(RoadClass.KEY))
-            osmParsers.addWayTagParser(new OSMRoadClassParser(encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class)));
-        if (!encodedValueStrings.contains(RoadClassLink.KEY))
-            osmParsers.addWayTagParser(new OSMRoadClassLinkParser(encodingManager.getBooleanEncodedValue(RoadClassLink.KEY)));
-        if (!encodedValueStrings.contains(RoadEnvironment.KEY))
-            osmParsers.addWayTagParser(new OSMRoadEnvironmentParser(encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)));
-        if (!encodedValueStrings.contains(MaxSpeed.KEY))
-            osmParsers.addWayTagParser(new OSMMaxSpeedParser(encodingManager.getDecimalEncodedValue(MaxSpeed.KEY)));
-        if (!encodedValueStrings.contains(RoadAccess.KEY))
-            osmParsers.addWayTagParser(new OSMRoadAccessParser(encodingManager.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class), OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)));
-        if (!encodedValueStrings.contains(FerrySpeed.KEY))
-            osmParsers.addWayTagParser(new FerrySpeedCalculator(encodingManager.getDecimalEncodedValue(FerrySpeed.KEY)));
-        if (encodingManager.hasEncodedValue(AverageSlope.KEY) || encodingManager.hasEncodedValue(MaxSlope.KEY)) {
-            if (!encodingManager.hasEncodedValue(AverageSlope.KEY) || !encodingManager.hasEncodedValue(MaxSlope.KEY))
-                throw new IllegalArgumentException("Enable both, average_slope and max_slope");
-            osmParsers.addWayTagParser(new SlopeCalculator(encodingManager.getDecimalEncodedValue(MaxSlope.KEY),
-                    encodingManager.getDecimalEncodedValue(AverageSlope.KEY)));
-        }
+        sortedParsers.forEach(osmParsers::addWayTagParser);
 
         if (maxSpeedCalculator != null) {
             maxSpeedCalculator.checkEncodedValues(encodingManager);
             osmParsers.addWayTagParser(maxSpeedCalculator.getParser());
         }
 
-        if (encodingManager.hasEncodedValue(Curvature.KEY))
-            osmParsers.addWayTagParser(new CurvatureCalculator(encodingManager.getDecimalEncodedValue(Curvature.KEY)));
-
-        DateRangeParser dateRangeParser = DateRangeParser.createInstance(dateRangeParserString);
-        Set added = new HashSet<>();
-        vehiclePropsByVehicle.forEach((name, props) -> {
-            VehicleTagParsers vehicleTagParsers = vehicleTagParserFactory.createParsers(encodingManager, name,
-                    new PMap(props).putObject("date_range_parser", dateRangeParser));
-            if (vehicleTagParsers == null)
-                return;
-            vehicleTagParsers.getTagParsers().forEach(tagParser -> {
-                if (tagParser == null) return;
-                if (tagParser instanceof BikeCommonAccessParser) {
-                    if (encodingManager.hasEncodedValue(BikeNetwork.KEY) && added.add(BikeNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig));
-                    if (encodingManager.hasEncodedValue(MtbNetwork.KEY) && added.add(MtbNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig));
-                    if (encodingManager.hasEncodedValue(Smoothness.KEY) && added.add(Smoothness.KEY))
-                        osmParsers.addWayTagParser(new OSMSmoothnessParser(encodingManager.getEnumEncodedValue(Smoothness.KEY, Smoothness.class)));
-                } else if (tagParser instanceof FootAccessParser) {
-                    if (encodingManager.hasEncodedValue(FootNetwork.KEY) && added.add(FootNetwork.KEY))
-                        osmParsers.addRelationTagParser(relConfig -> new OSMFootNetworkTagParser(encodingManager.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class), relConfig));
-                }
-                String turnRestrictionKey = TurnRestriction.key(name);
-                if (encodingManager.hasTurnEncodedValue(turnRestrictionKey)
-                        // need to make sure we do not add the same restriction parsers multiple times
-                        && osmParsers.getRestrictionTagParsers().stream().noneMatch(r -> r.getTurnRestrictionEnc().getName().equals(turnRestrictionKey))) {
-                    List restrictions = tagParser instanceof AbstractAccessParser
-                            ? ((AbstractAccessParser) tagParser).getRestrictions()
-                            : OSMRoadAccessParser.toOSMRestrictions(TransportationMode.valueOf(props.getString("transportation_mode", "VEHICLE")));
-                    osmParsers.addRestrictionTagParser(new RestrictionTagParser(restrictions, encodingManager.getTurnBooleanEncodedValue(turnRestrictionKey)));
-                }
-            });
-            vehicleTagParsers.getTagParsers().forEach(tagParser -> {
-                if (tagParser == null) return;
-                osmParsers.addWayTagParser(tagParser);
+        if (encodingManager.hasEncodedValue(BikeNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig, "bicycle"));
+        if (encodingManager.hasEncodedValue(MtbNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig, "mtb"));
+        if (encodingManager.hasEncodedValue(FootNetwork.KEY))
+            osmParsers.addRelationTagParser(relConfig -> new OSMFootNetworkTagParser(encodingManager.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class), relConfig));
 
-                if (tagParser instanceof BikeCommonAccessParser && encodingManager.hasEncodedValue(GetOffBike.KEY) && added.add(GetOffBike.KEY))
-                    osmParsers.addWayTagParser(new OSMGetOffBikeParser(encodingManager.getBooleanEncodedValue(GetOffBike.KEY), ((BikeCommonAccessParser) tagParser).getAccessEnc()));
-            });
+        restrictionVehicleTypesByProfile.forEach((profile, restrictionVehicleTypes) -> {
+            osmParsers.addRestrictionTagParser(new RestrictionTagParser(
+                    restrictionVehicleTypes, encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(profile))));
         });
         return osmParsers;
     }
 
-    public static List getEncodedValueStrings(String encodedValuesStr) {
-        return Arrays.stream(encodedValuesStr.split(","))
-                .map(String::trim)
-                .filter(s -> !s.isEmpty())
-                .collect(Collectors.toList());
+    public static Map parseEncodedValueString(String encodedValuesStr) {
+        Map encodedValuesWithProps = new LinkedHashMap<>();
+        Arrays.stream(encodedValuesStr.split(","))
+                .filter(evStr -> !evStr.isBlank())
+                .forEach(evStr -> {
+                    String key = evStr.trim().split("\\|")[0];
+                    if (encodedValuesWithProps.put(key, new PMap(evStr)) != null)
+                        throw new IllegalArgumentException("duplicate encoded value in config graph.encoded_values: " + key);
+                });
+        return encodedValuesWithProps;
     }
 
-    public static Map getVehiclePropsByVehicle(String vehiclesStr, Collection profiles) {
-        Map vehicleProps = new LinkedHashMap<>();
-        for (String encoderStr : vehiclesStr.split(",")) {
-            String name = encoderStr.split("\\|")[0].trim();
-            if (name.isEmpty())
-                continue;
-            if (vehicleProps.containsKey(name))
-                throw new IllegalArgumentException("Duplicate vehicle: " + name + " in: " + vehicleProps);
-            vehicleProps.put(name, new PMap(encoderStr));
-        }
-        Map vehiclePropsFromProfiles = new LinkedHashMap<>();
-        for (Profile profile : profiles) {
-            if (profile.isTurnCosts() && vehicleProps.containsKey(profile.getVehicle())
-                    && vehicleProps.get(profile.getVehicle()).has("turn_costs") && !vehicleProps.get(profile.getVehicle()).getBool("turn_costs", false))
-                throw new IllegalArgumentException("turn_costs=false was set explicitly for vehicle '" + profile.getVehicle() + "', but profile '" + profile.getName() + "' using it uses turn costs");
-            // if a profile uses a vehicle with turn costs make sure we add that vehicle with turn costs
-            String vehicle = profile.getVehicle().trim();
-            if (!vehiclePropsFromProfiles.containsKey(vehicle) || profile.isTurnCosts())
-                vehiclePropsFromProfiles.put(vehicle, new PMap(profile.isTurnCosts() ? "turn_costs=true" : ""));
-        }
-        // vehicles from profiles are only taken into account when they were not given explicitly
-        vehiclePropsFromProfiles.forEach(vehicleProps::putIfAbsent);
-        // ... but the turn costs property is always determined by the profile
-        vehiclePropsFromProfiles.forEach((vehicle, props) -> {
-            if (props.getBool("turn_costs", false) && !vehicleProps.get(vehicle).getBool("turn_costs", false))
-                vehicleProps.get(vehicle).putObject("turn_costs", true);
-        });
-        return vehicleProps;
+    private static Map> getRestrictionVehicleTypesByProfile(Collection profiles) {
+        Map> result = new LinkedHashMap<>();
+        for (Profile profile : profiles)
+            if (profile.hasTurnCosts())
+                result.put(profile.getName(), profile.getTurnCostsConfig().getVehicleTypes());
+        return result;
     }
 
     private static ElevationProvider createElevationProvider(GraphHopperConfig ghConfig) {
@@ -763,6 +701,12 @@ private static ElevationProvider createElevationProvider(GraphHopperConfig ghCon
         if (cacheDirStr.isEmpty() && ghConfig.has("graph.elevation.cachedir"))
             throw new IllegalArgumentException("use graph.elevation.cache_dir not cachedir in configuration");
 
+        boolean interpolate = ghConfig.has("graph.elevation.interpolate")
+                ? "bilinear".equals(ghConfig.getString("graph.elevation.interpolate", "none"))
+                : ghConfig.getBool("graph.elevation.calc_mean", false);
+        boolean removeTempElevationFiles = ghConfig.getBool("graph.elevation.clear",
+                ghConfig.getBool("graph.elevation.cgiar.clear", false));
+
         ElevationProvider elevationProvider = ElevationProvider.NOOP;
         if (eleProviderStr.equalsIgnoreCase("hgt")) {
             elevationProvider = new HGTProvider(cacheDirStr);
@@ -778,6 +722,20 @@ private static ElevationProvider createElevationProvider(GraphHopperConfig ghCon
             elevationProvider = new MultiSourceElevationProvider(cacheDirStr);
         } else if (eleProviderStr.equalsIgnoreCase("skadi")) {
             elevationProvider = new SkadiProvider(cacheDirStr);
+        } else if (eleProviderStr.equalsIgnoreCase("sonny")) {
+            elevationProvider = new SonnyProvider(cacheDirStr);
+        } else if (eleProviderStr.equalsIgnoreCase("multi3")) {
+            elevationProvider = new MultiSource3ElevationProvider(cacheDirStr);
+        } else if (eleProviderStr.equalsIgnoreCase("pmtiles")) {
+            int zoom = ghConfig.getInt("graph.elevation.pmtiles.zoom", -1);
+            String terrainEncoding = ghConfig.getString("graph.elevation.pmtiles.terrain_encoding", "terrarium");
+            elevationProvider = new PMTilesElevationProvider(
+                    ghConfig.getString("graph.elevation.pmtiles.location", "/tmp/planet.pmtiles"),
+                    PMTilesElevationProvider.TerrainEncoding.valueOf(terrainEncoding.toUpperCase(Locale.ROOT)),
+                    interpolate, zoom, cacheDirStr)
+                    .setAutoRemoveTemporaryFiles(removeTempElevationFiles);
+        } else if (!eleProviderStr.isEmpty() && !eleProviderStr.equalsIgnoreCase("noop")) {
+            throw new IllegalArgumentException("Did not find elevation provider: " + eleProviderStr);
         }
 
         if (elevationProvider instanceof TileBasedElevationProvider) {
@@ -789,13 +747,6 @@ private static ElevationProvider createElevationProvider(GraphHopperConfig ghCon
 
             DAType elevationDAType = DAType.fromString(ghConfig.getString("graph.elevation.dataaccess", "MMAP"));
 
-            boolean interpolate = ghConfig.has("graph.elevation.interpolate")
-                    ? "bilinear".equals(ghConfig.getString("graph.elevation.interpolate", "none"))
-                    : ghConfig.getBool("graph.elevation.calc_mean", false);
-
-            boolean removeTempElevationFiles = ghConfig.getBool("graph.elevation.cgiar.clear", true);
-            removeTempElevationFiles = ghConfig.getBool("graph.elevation.clear", removeTempElevationFiles);
-
             provider
                     .setAutoRemoveTemporaryFiles(removeTempElevationFiles)
                     .setInterpolate(interpolate)
@@ -863,20 +814,15 @@ public void importAndClose() {
      * Creates the graph from OSM data.
      */
     protected void process(boolean closeEarly) {
-        GHDirectory directory = new GHDirectory(ghLocation, dataAccessDefaultType);
+        prepareImport();
+        if (encodingManager == null)
+            throw new IllegalStateException("The EncodingManager must be created in `prepareImport()`");
+        GHDirectory directory = new GHDirectory(ghLocation, dataAccessDefaultType, defaultSegmentSize);
         directory.configure(dataAccessConfig);
-        boolean withUrbanDensity = urbanDensityCalculationThreads > 0;
-        boolean withMaxSpeedEstimation = maxSpeedCalculator != null;
-        Map vehiclePropsByVehicle = getVehiclePropsByVehicle(vehiclesString, profilesByName.values());
-        List encodedValueStrings = getEncodedValueStrings(encodedValuesString);
-        encodingManager = buildEncodingManager(vehiclePropsByVehicle, encodedValueStrings, withUrbanDensity,
-                withMaxSpeedEstimation, profilesByName.values());
-        osmParsers = buildOSMParsers(vehiclePropsByVehicle, encodedValueStrings, osmReaderConfig.getIgnoredHighways(), dateRangeParserString);
         baseGraph = new BaseGraph.Builder(getEncodingManager())
                 .setDir(directory)
                 .set3D(hasElevation())
                 .withTurnCosts(encodingManager.needsTurnCostsSupport())
-                .setSegmentSize(defaultSegmentSize)
                 .build();
         properties = new StorableProperties(directory);
         checkProfilesConsistency();
@@ -906,13 +852,63 @@ protected void process(boolean closeEarly) {
         }
     }
 
+    protected void prepareImport() {
+        Map encodedValuesWithProps = parseEncodedValueString(encodedValuesString);
+        NameValidator nameValidator = s -> importRegistry.createImportUnit(s) != null;
+        Set missing = new LinkedHashSet<>();
+        profilesByName.values().
+                forEach(profile -> CustomModelParser.findVariablesForEncodedValuesString(profile.getCustomModel(), nameValidator, s -> "").
+                        forEach(var -> {
+                            if (!encodedValuesWithProps.containsKey(var)) missing.add(var);
+                            encodedValuesWithProps.putIfAbsent(var, new PMap());
+                        }));
+        if (!missing.isEmpty()) {
+            String encodedValuesString = encodedValuesWithProps.entrySet().stream()
+                    .map(e -> e.getKey() + (e.getValue().isEmpty() ? "" : ("|" + e.getValue().toMap().entrySet().stream().map(p -> p.getKey() + "=" + p.getValue()).collect(Collectors.joining("|")))))
+                    .collect(Collectors.joining(", "));
+            throw new IllegalArgumentException("Encoded values missing: " + String.join(", ", missing) + ".\n" +
+                    "To avoid that certain encoded values are automatically removed when you change the custom model later, you need to set the encoded values manually:\n" +
+                    "graph.encoded_values: " + encodedValuesString);
+        }
+
+        // following encoded values are used by instructions and in the snap prevention filter (avoid motorway, tunnel, etc.)
+        encodedValuesWithProps.putIfAbsent(RoadClass.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(RoadEnvironment.KEY, new PMap());
+        // now only used by instructions:
+        encodedValuesWithProps.putIfAbsent(Roundabout.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(VehicleAccess.key("car"), new PMap());
+        encodedValuesWithProps.putIfAbsent(RoadClassLink.KEY, new PMap());
+        encodedValuesWithProps.putIfAbsent(MaxSpeed.KEY, new PMap());
+
+        Map> restrictionVehicleTypesByProfile = getRestrictionVehicleTypesByProfile(profilesByName.values());
+
+        if (urbanDensityCalculationThreads > 0)
+            encodedValuesWithProps.put(UrbanDensity.KEY, new PMap());
+        if (maxSpeedCalculator != null) {
+            if (urbanDensityCalculationThreads <= 0)
+                throw new IllegalArgumentException("For max_speed_calculator the urban density calculation needs to be enabled (e.g. graph.urban_density.threads: 1)");
+            encodedValuesWithProps.put(MaxSpeedEstimated.KEY, new PMap());
+        }
+
+        Map activeImportUnits = new LinkedHashMap<>();
+        ArrayDeque deque = new ArrayDeque<>(encodedValuesWithProps.keySet());
+        while (!deque.isEmpty()) {
+            String ev = deque.removeFirst();
+            ImportUnit importUnit = importRegistry.createImportUnit(ev);
+            if (importUnit == null)
+                throw new IllegalArgumentException("Unknown encoded value: " + ev);
+            if (activeImportUnits.put(ev, importUnit) == null)
+                deque.addAll(importUnit.getRequiredImportUnits());
+        }
+        encodingManager = buildEncodingManager(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile);
+        osmParsers = buildOSMParsers(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile, osmReaderConfig.getIgnoredHighways());
+    }
+
     protected void postImportOSM() {
         // Important note: To deal with via-way turn restrictions we introduce artificial edges in OSMReader (#2689).
         // These are simply copies of real edges. Any further modifications of the graph edges must take care of keeping
         // the artificial edges in sync with their real counterparts. So if an edge attribute shall be changed this change
         // must also be applied to the corresponding artificial edge.
-
-
         calculateUrbanDensity();
 
         if (maxSpeedCalculator != null) {
@@ -922,6 +918,26 @@ protected void postImportOSM() {
 
         if (hasElevation())
             interpolateBridgesTunnelsAndFerries();
+
+        calculateSlope();
+
+        if (encodingManager.hasEncodedValue(Curvature.KEY))
+            new CurvatureCalculator(encodingManager.getDecimalEncodedValue(Curvature.KEY)).execute(baseGraph.getBaseGraph());
+
+        if (sortGraph)
+            sortGraphAlongHilbertCurve(baseGraph);
+    }
+
+    private void calculateSlope() {
+        if (encodingManager.hasEncodedValue(AverageSlope.KEY) || encodingManager.hasEncodedValue(MaxSlope.KEY)) {
+            if (!hasElevation())
+                throw new IllegalArgumentException("average_slope and max_slope encoded values require elevation, but no elevation provider is configured");
+            DecimalEncodedValue maxSlopeEnc = encodingManager.hasEncodedValue(MaxSlope.KEY)
+                    ? encodingManager.getDecimalEncodedValue(MaxSlope.KEY) : null;
+            DecimalEncodedValue averageSlopeEnc = encodingManager.hasEncodedValue(AverageSlope.KEY)
+                    ? encodingManager.getDecimalEncodedValue(AverageSlope.KEY) : null;
+            new SlopeCalculator(maxSlopeEnc, averageSlopeEnc).execute(baseGraph.getBaseGraph());
+        }
     }
 
     protected void importOSM() {
@@ -938,17 +954,12 @@ protected void importOSM() {
         }
 
         AreaIndex areaIndex = new AreaIndex<>(customAreas);
-        if (countryRuleFactory == null || countryRuleFactory.getCountryToRuleMap().isEmpty()) {
-            logger.info("No country rules available");
-        } else {
-            logger.info("Applying rules for the following countries: {}", countryRuleFactory.getCountryToRuleMap().keySet());
-        }
 
+        eleProvider.init();
         logger.info("start creating graph from " + osmFile);
         OSMReader reader = new OSMReader(baseGraph.getBaseGraph(), osmParsers, osmReaderConfig).setFile(_getOSMFile()).
                 setAreaIndex(areaIndex).
-                setElevationProvider(eleProvider).
-                setCountryRuleFactory(countryRuleFactory);
+                setElevationProvider(eleProvider);
         logger.info("using " + getBaseGraphString() + ", memory:" + getMemInfo());
 
         createBaseGraphAndProperties();
@@ -972,6 +983,79 @@ protected void createBaseGraphAndProperties() {
             maxSpeedCalculator.createDataAccessForParser(baseGraph.getDirectory());
     }
 
+    public static void sortGraphAlongHilbertCurve(BaseGraph graph) {
+        logger.info("sorting graph along Hilbert curve.... (memory:" + getMemInfo() + ")");
+        StopWatch sw = StopWatch.started();
+        NodeAccess na = graph.getNodeAccess();
+        final int order = 31; // using 15 would allow us to use ints for sortIndices, but this would result in (marginally) slower routing
+        LongArrayList sortIndices = new LongArrayList();
+        for (int node = 0; node < graph.getNodes(); node++)
+            sortIndices.add(latLonToHilbertIndex(na.getLat(node), na.getLon(node), order));
+        int[] nodeOrder = IndirectSort.mergesort(0, graph.getNodes(), (nodeA, nodeB) -> Long.compare(sortIndices.get(nodeA), sortIndices.get(nodeB)));
+        EdgeExplorer explorer = graph.createEdgeExplorer();
+        int edges = graph.getEdges();
+        IntArrayList edgeOrder = new IntArrayList();
+        com.carrotsearch.hppc.BitSet edgesFound = new BitSet(edges);
+        for (int node : nodeOrder) {
+            EdgeIterator iter = explorer.setBaseNode(node);
+            while (iter.next()) {
+                if (!edgesFound.get(iter.getEdge())) {
+                    edgeOrder.add(iter.getEdge());
+                    edgesFound.set(iter.getEdge());
+                }
+            }
+        }
+        IntArrayList newEdgesByOldEdges = ArrayUtil.invert(edgeOrder);
+        IntArrayList newNodesByOldNodes = IntArrayList.from(ArrayUtil.invert(nodeOrder));
+        logger.info("calculating sort order took: " + sw.stop().getTimeString() + ", memory:" + getMemInfo());
+        sortGraphForGivenOrdering(graph, newNodesByOldNodes, newEdgesByOldEdges);
+    }
+
+    public static void sortGraphForGivenOrdering(BaseGraph baseGraph, IntArrayList newNodesByOldNodes, IntArrayList newEdgesByOldEdges) {
+        if (!ArrayUtil.isPermutation(newEdgesByOldEdges))
+            throw new IllegalStateException("New edges: not a permutation");
+        if (!ArrayUtil.isPermutation(newNodesByOldNodes))
+            throw new IllegalStateException("New nodes: not a permutation");
+        logger.info("sort graph for fixed ordering...");
+        StopWatch sw = new StopWatch().start();
+        baseGraph.sortEdges(newEdgesByOldEdges::get);
+        logger.info("sorting {} edges took: {}", Helper.nf(newEdgesByOldEdges.size()), sw.stop().getTimeString());
+        sw = new StopWatch().start();
+        baseGraph.relabelNodes(newNodesByOldNodes::get);
+        logger.info("sorting {} nodes took: {}", Helper.nf(newNodesByOldNodes.size()), sw.stop().getTimeString());
+    }
+
+    public static long latLonToHilbertIndex(double lat, double lon, int order) {
+        double nx = (lon + 180) / 360;
+        double ny = (90 - lat) / 180;
+        long size = 1L << order;
+        long x = (long) (nx * size);
+        long y = (long) (ny * size);
+        x = Math.max(0, Math.min(size - 1, x));
+        y = Math.max(0, Math.min(size - 1, y));
+        return xy2d(order, x, y);
+    }
+
+    public static long xy2d(int n, long x, long y) {
+        long d = 0;
+        for (long s = 1L << (n - 1); s > 0; s >>= 1) {
+            int rx = (x & s) > 0 ? 1 : 0;
+            int ry = (y & s) > 0 ? 1 : 0;
+            d += s * s * ((3 * rx) ^ ry);
+            // rotate
+            if (ry == 0) {
+                if (rx == 1) {
+                    x = s - 1 - x;
+                    y = s - 1 - y;
+                }
+                long tmp = x;
+                x = y;
+                y = tmp;
+            }
+        }
+        return d;
+    }
+
     private void calculateUrbanDensity() {
         if (encodingManager.hasEncodedValue(UrbanDensity.KEY)) {
             EnumEncodedValue urbanDensityEnc = encodingManager.getEnumEncodedValue(UrbanDensity.KEY, UrbanDensity.class);
@@ -1072,18 +1156,24 @@ public boolean load() {
                     .setDir(directory)
                     .set3D(hasElevation())
                     .withTurnCosts(encodingManager.needsTurnCostsSupport())
-                    .setSegmentSize(defaultSegmentSize)
                     .build();
-            baseGraph.loadExisting();
-            String storedProfiles = properties.get("profiles");
-            String configuredProfiles = getProfilesString();
-            if (!storedProfiles.equals(configuredProfiles))
-                throw new IllegalStateException("Profiles do not match:"
-                        + "\nGraphhopper config: " + configuredProfiles
-                        + "\nGraph: " + storedProfiles
-                        + "\nChange configuration to match the graph or delete " + baseGraph.getDirectory().getLocation());
             checkProfilesConsistency();
-
+            baseGraph.loadExisting();
+            String storedProfilesString = properties.get("profiles");
+            Map storedProfileHashes = Arrays.stream(storedProfilesString.split(",")).map(s -> s.split("\\|", 2)).collect((Collectors.toMap(kv -> kv[0], kv -> Integer.parseInt(kv[1]))));
+            Map configuredProfileHashes = getProfileHashes();
+            configuredProfileHashes.forEach((profile, hash) -> {
+                Integer storedHash = storedProfileHashes.get(profile);
+                if (storedHash == null)
+                    throw new IllegalStateException("You cannot add new profiles to the loaded graph. Profile '" + profile + "' is new."
+                            + "\nExisting profiles: " + String.join(",", storedProfileHashes.keySet())
+                            + "\nChange your configuration to match the graph or delete " + baseGraph.getDirectory().getLocation());
+                if (!hash.equals(storedHash))
+                    throw new IllegalStateException("Profile '" + profile + "' does not match."
+                            + "\nStored: " + storedHash
+                            + "\nConfigured: " + hash
+                            + "\nChange this profile to match the stored one or delete " + baseGraph.getDirectory().getLocation());
+            });
             postProcessing(false);
             directory.loadMMap();
             setFullyLoaded();
@@ -1094,26 +1184,22 @@ public boolean load() {
         }
     }
 
+    protected int getProfileHash(Profile profile) {
+        return profile.getVersion();
+    }
+
     private String getProfilesString() {
-        return profilesByName.values().stream().map(p -> p.getName() + "|" + p.getVersion()).collect(Collectors.joining(","));
+        return getProfileHashes().entrySet().stream().map(e -> e.getKey() + "|" + e.getValue()).collect(Collectors.joining(","));
+    }
+
+    private Map getProfileHashes() {
+        return profilesByName.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> getProfileHash(e.getValue())));
     }
 
-    private void checkProfilesConsistency() {
+    public void checkProfilesConsistency() {
         if (profilesByName.isEmpty())
             throw new IllegalArgumentException("There has to be at least one profile");
-        EncodingManager encodingManager = getEncodingManager();
         for (Profile profile : profilesByName.values()) {
-            if (!encodingManager.getVehicles().contains(profile.getVehicle()))
-                throw new IllegalArgumentException("Unknown vehicle '" + profile.getVehicle() + "' in profile: " + profile + ". " +
-                        "Available vehicles: " + String.join(",", encodingManager.getVehicles()));
-            BooleanEncodedValue turnRestrictionEnc = encodingManager.hasTurnEncodedValue(TurnRestriction.key(profile.getVehicle()))
-                    ? encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(profile.getVehicle()))
-                    : null;
-            if (profile.isTurnCosts() && turnRestrictionEnc == null) {
-                throw new IllegalArgumentException("The profile '" + profile.getName() + "' was configured with " +
-                        "'turn_costs=true', but the corresponding vehicle '" + profile.getVehicle() + "' does not support turn costs." +
-                        "\nYou need to add `|turn_costs=true` to the vehicle in `graph.vehicles`");
-            }
             try {
                 createWeighting(profile, new PMap());
             } catch (IllegalArgumentException e) {
@@ -1170,7 +1256,7 @@ private List createCHConfigs(List chProfiles) {
         List chConfigs = new ArrayList<>();
         for (CHProfile chProfile : chProfiles) {
             Profile profile = profilesByName.get(chProfile.getProfile());
-            if (profile.isTurnCosts()) {
+            if (profile.hasTurnCosts()) {
                 chConfigs.add(CHConfig.edgeBased(profile.getName(), createWeighting(profile, new PMap())));
             } else {
                 chConfigs.add(CHConfig.nodeBased(profile.getName(), createWeighting(profile, new PMap())));
@@ -1206,6 +1292,7 @@ private List createLMConfigs(List lmProfiles) {
      * @param closeEarly release resources as early as possible
      */
     protected void postProcessing(boolean closeEarly) {
+        calcChecksums();
         initLocationIndex();
         importPublicTransit();
 
@@ -1296,6 +1383,37 @@ protected LocationIndex createLocationIndex(Directory dir) {
         return tmpIndex;
     }
 
+    private void calcChecksums() {
+        if (!calcChecksums) return;
+        logger.info("Calculating checksums for {} profiles", profilesByName.size());
+        StopWatch sw = StopWatch.started();
+        double[] checksums_fwd = new double[profilesByName.size()];
+        double[] checksums_bwd = new double[profilesByName.size()];
+        List weightings = profilesByName.values().stream().map(profile -> createWeighting(profile, new PMap())).toList();
+        AllEdgesIterator edge = baseGraph.getAllEdges();
+        while (edge.next()) {
+            for (int i = 0; i < profilesByName.size(); i++) {
+                double weightFwd = weightings.get(i).calcEdgeWeight(edge, false);
+                if (Double.isInfinite(weightFwd)) weightFwd = -1;
+                weightFwd *= (i % 2 == 0) ? -1 : 1;
+                double weightBwd = weightings.get(i).calcEdgeWeight(edge, true);
+                if (Double.isInfinite(weightBwd)) weightBwd = -1;
+                weightBwd *= (i % 2 == 0) ? -1 : 1;
+                checksums_fwd[i] += weightFwd;
+                checksums_bwd[i] += weightBwd;
+            }
+        }
+        int index = 0;
+        for (Profile profile : profilesByName.values()) {
+            properties.put("checksum.fwd." + profile.getName(), checksums_fwd[index]);
+            properties.put("checksum.bwd." + profile.getName(), checksums_bwd[index]);
+            logger.info("checksum.fwd." + profile.getName() + ": " + checksums_fwd[index]);
+            logger.info("checksum.bwd." + profile.getName() + ": " + checksums_bwd[index]);
+            index++;
+        }
+        logger.info("Calculating checksums took: " + sw.stop().getTimeString());
+    }
+
     /**
      * Initializes the location index after the import is done.
      */
@@ -1325,7 +1443,7 @@ private void setLMProfileVersion(String profile, int version) {
     protected void loadOrPrepareCH(boolean closeEarly) {
         for (CHProfile profile : chPreparationHandler.getCHProfiles())
             if (!getCHProfileVersion(profile.getProfile()).isEmpty()
-                    && !getCHProfileVersion(profile.getProfile()).equals("" + profilesByName.get(profile.getProfile()).getVersion()))
+                    && !getCHProfileVersion(profile.getProfile()).equals("" + getProfileHash(profilesByName.get(profile.getProfile()))))
                 throw new IllegalArgumentException("CH preparation of " + profile.getProfile() + " already exists in storage and doesn't match configuration");
 
         // we load ch graphs that already exist and prepare the other ones
@@ -1340,7 +1458,7 @@ protected void loadOrPrepareCH(boolean closeEarly) {
             if (loaded.containsKey(profile.getProfile()) && prepared.containsKey(profile.getProfile()))
                 throw new IllegalStateException("CH graph should be either loaded or prepared, but not both: " + profile.getProfile());
             else if (prepared.containsKey(profile.getProfile())) {
-                setCHProfileVersion(profile.getProfile(), profilesByName.get(profile.getProfile()).getVersion());
+                setCHProfileVersion(profile.getProfile(), getProfileHash(profilesByName.get(profile.getProfile())));
                 PrepareContractionHierarchies.Result res = prepared.get(profile.getProfile());
                 chGraphs.put(profile.getProfile(), RoutingCHGraphImpl.fromGraph(baseGraph.getBaseGraph(), res.getCHStorage(), res.getCHConfig()));
             } else if (loaded.containsKey(profile.getProfile())) {
@@ -1348,6 +1466,10 @@ else if (prepared.containsKey(profile.getProfile())) {
             } else
                 throw new IllegalStateException("CH graph should be either loaded or prepared: " + profile.getProfile());
         }
+        chGraphs.forEach((name, ch) -> {
+            CHStorage store = ((RoutingCHGraphImpl) ch).getCHStorage();
+            logger.info("CH available for profile {}, {}MB, {}, ({}MB)", name, Helper.nf(store.getCapacity() / Helper.MB), store.toDetailsString(), store.getMB());
+        });
     }
 
     protected Map prepareCH(boolean closeEarly, List configsToPrepare) {
@@ -1364,13 +1486,13 @@ protected Map prepareCH(boolean cl
     protected void loadOrPrepareLM(boolean closeEarly) {
         for (LMProfile profile : lmPreparationHandler.getLMProfiles())
             if (!getLMProfileVersion(profile.getProfile()).isEmpty()
-                    && !getLMProfileVersion(profile.getProfile()).equals("" + profilesByName.get(profile.getProfile()).getVersion()))
+                    && !getLMProfileVersion(profile.getProfile()).equals("" + getProfileHash(profilesByName.get(profile.getProfile()))))
                 throw new IllegalArgumentException("LM preparation of " + profile.getProfile() + " already exists in storage and doesn't match configuration");
 
         // we load landmark storages that already exist and prepare the other ones
         List lmConfigs = createLMConfigs(lmPreparationHandler.getLMProfiles());
         List loaded = lmPreparationHandler.load(lmConfigs, baseGraph, encodingManager);
-        List loadedConfigs = loaded.stream().map(LandmarkStorage::getLMConfig).collect(Collectors.toList());
+        List loadedConfigs = loaded.stream().map(LandmarkStorage::getLMConfig).toList();
         List configsToPrepare = lmConfigs.stream().filter(c -> !loadedConfigs.contains(c)).collect(Collectors.toList());
         List prepared = prepareLM(closeEarly, configsToPrepare);
 
@@ -1384,7 +1506,7 @@ protected void loadOrPrepareLM(boolean closeEarly) {
             if (loadedLMS.isPresent() && preparedLMS.isPresent())
                 throw new IllegalStateException("LM should be either loaded or prepared, but not both: " + prepProfile);
             else if (preparedLMS.isPresent()) {
-                setLMProfileVersion(lmp.getProfile(), profilesByName.get(lmp.getProfile()).getVersion());
+                setLMProfileVersion(lmp.getProfile(), getProfileHash(profilesByName.get(lmp.getProfile())));
                 landmarks.put(lmp.getProfile(), preparedLMS.get().getLandmarkStorage());
             } else
                 loadedLMS.ifPresent(landmarkStorage -> landmarks.put(lmp.getProfile(), landmarkStorage));
@@ -1495,11 +1617,15 @@ public static JsonFeatureCollection resolveCustomAreas(String customAreasDirecto
         if (!customAreasDirectory.isEmpty()) {
             ObjectMapper mapper = new ObjectMapper().registerModule(new JtsModule());
             try (DirectoryStream stream = Files.newDirectoryStream(Paths.get(customAreasDirectory), "*.{geojson,json}")) {
-                for (Path customAreaFile : stream) {
-                    try (BufferedReader reader = Files.newBufferedReader(customAreaFile, StandardCharsets.UTF_8)) {
-                        globalAreas.getFeatures().addAll(mapper.readValue(reader, JsonFeatureCollection.class).getFeatures());
-                    }
-                }
+                StreamSupport.stream(stream.spliterator(), false)
+                        .sorted(Comparator.comparing(Path::toString))
+                        .forEach(customAreaFile -> {
+                            try (BufferedReader reader = Files.newBufferedReader(customAreaFile, StandardCharsets.UTF_8)) {
+                                globalAreas.getFeatures().addAll(mapper.readValue(reader, JsonFeatureCollection.class).getFeatures());
+                            } catch (IOException e) {
+                                throw new UncheckedIOException(e);
+                            }
+                        });
                 logger.info("Will make " + globalAreas.getFeatures().size() + " areas available to all custom profiles. Found in " + customAreasDirectory);
             } catch (IOException e) {
                 throw new UncheckedIOException(e);
diff --git a/core/src/main/java/com/graphhopper/GraphHopperConfig.java b/core/src/main/java/com/graphhopper/GraphHopperConfig.java
index 06d900229c1..087b87a43b6 100644
--- a/core/src/main/java/com/graphhopper/GraphHopperConfig.java
+++ b/core/src/main/java/com/graphhopper/GraphHopperConfig.java
@@ -19,7 +19,8 @@
 package com.graphhopper;
 
 import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonSetter;
+import com.fasterxml.jackson.annotation.Nulls;
 import com.graphhopper.config.CHProfile;
 import com.graphhopper.config.LMProfile;
 import com.graphhopper.config.Profile;
@@ -39,10 +40,16 @@ public class GraphHopperConfig {
     private List profiles = new ArrayList<>();
     private List chProfiles = new ArrayList<>();
     private List lmProfiles = new ArrayList<>();
+    private List copyrights = new ArrayList<>();
     private final PMap map;
 
     public GraphHopperConfig() {
         this(new PMap());
+        // This includes the required attribution for OpenStreetMap.
+        // Do not hesitate to  mention us and link us in your about page
+        // https://support.graphhopper.com/support/search/solutions?term=attribution
+        copyrights.add("GraphHopper");
+        copyrights.add("OpenStreetMap contributors");
     }
 
     public GraphHopperConfig(GraphHopperConfig otherConfig) {
@@ -50,6 +57,7 @@ public GraphHopperConfig(GraphHopperConfig otherConfig) {
         otherConfig.profiles.forEach(p -> profiles.add(new Profile(p)));
         otherConfig.chProfiles.forEach(p -> chProfiles.add(new CHProfile(p)));
         otherConfig.lmProfiles.forEach(p -> lmProfiles.add(new LMProfile(p)));
+        copyrights.addAll(otherConfig.copyrights);
     }
 
     public GraphHopperConfig(PMap pMap) {
@@ -60,6 +68,7 @@ public List getProfiles() {
         return profiles;
     }
 
+    @JsonSetter(nulls = Nulls.AS_EMPTY)
     public GraphHopperConfig setProfiles(List profiles) {
         this.profiles = profiles;
         return this;
@@ -69,7 +78,7 @@ public List getCHProfiles() {
         return chProfiles;
     }
 
-    @JsonProperty("profiles_ch")
+    @JsonSetter(value = "profiles_ch", nulls = Nulls.AS_EMPTY)
     public GraphHopperConfig setCHProfiles(List chProfiles) {
         this.chProfiles = chProfiles;
         return this;
@@ -79,12 +88,20 @@ public List getLMProfiles() {
         return lmProfiles;
     }
 
-    @JsonProperty("profiles_lm")
+    @JsonSetter(value = "profiles_lm", nulls = Nulls.AS_EMPTY)
     public GraphHopperConfig setLMProfiles(List lmProfiles) {
         this.lmProfiles = lmProfiles;
         return this;
     }
 
+    public List getCopyrights() {
+        return copyrights;
+    }
+
+    public void setCopyrights(List copyrights) {
+        this.copyrights = copyrights;
+    }
+
     // We can add explicit configuration properties to GraphHopperConfig (for example to allow lists or nested objects),
     // everything else is stored in a HashMap
     @JsonAnySetter
diff --git a/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java b/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java
index 396da566589..8fd8d585d5b 100644
--- a/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java
+++ b/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java
@@ -18,7 +18,7 @@
 
 
 /**
- * This class is a partial Copy of the org.apache.commons.lang3.StringUtils
+ * This class is a partial copy of the org.apache.commons.lang3.StringUtils
  * that can be found here: https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java
  * 

* The library can be found here: https://commons.apache.org/proper/commons-lang/ diff --git a/core/src/main/java/com/graphhopper/coll/GHLongLongBTree.java b/core/src/main/java/com/graphhopper/coll/GHLongLongBTree.java index df8c1d3a285..9f836374775 100644 --- a/core/src/main/java/com/graphhopper/coll/GHLongLongBTree.java +++ b/core/src/main/java/com/graphhopper/coll/GHLongLongBTree.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.util.Arrays; +import java.util.function.LongUnaryOperator; /** * An in-memory B-Tree with configurable value size (1-8 bytes). Delete not supported. @@ -118,6 +119,28 @@ public long put(long key, long value) { return rv.oldValue == null ? emptyValue : toLong(rv.oldValue); } + @Override + public long putOrCompute(long key, long valueIfAbsent, LongUnaryOperator computeIfPresent) { + if (valueIfAbsent > maxValue) + throw new IllegalArgumentException("Value " + valueIfAbsent + " exceeded max value: " + maxValue + + ". Increase bytesPerValue (" + bytesPerValue + ")"); + if (valueIfAbsent == emptyValue) + throw new IllegalArgumentException("Value cannot be the 'empty value' " + emptyValue); + + ReturnValue rv = root.putOrCompute(key, valueIfAbsent, computeIfPresent); + if (rv.tree != null) { + height++; + root = rv.tree; + } + if (rv.oldValue == null) { + // successfully inserted (was absent) + size++; + if (size % 1000000 == 0) + optimize(); + } + return rv.oldValue == null ? emptyValue : toLong(rv.oldValue); + } + @Override public long get(long key) { return root.get(key); @@ -306,6 +329,64 @@ ReturnValue put(long key, long newValue) { return downTreeRV; } + /** + * Like put, but uses valueIfAbsent when key is not found, and computeIfPresent when key is found. + * This avoids a separate get+put traversal. + */ + ReturnValue putOrCompute(long key, long valueIfAbsent, LongUnaryOperator computeIfPresent) { + int index = binarySearch(keys, 0, entrySize, key); + if (index >= 0) { + // key exists: compute new value from old value + byte[] oldValue = new byte[bytesPerValue]; + System.arraycopy(values, index * bytesPerValue, oldValue, 0, bytesPerValue); + long oldLong = toLong(oldValue); + long newValue = computeIfPresent.applyAsLong(oldLong); + if (newValue > maxValue) + throw new IllegalArgumentException("Computed value " + newValue + " exceeded max value: " + maxValue + + ". Increase bytesPerValue (" + bytesPerValue + ")"); + if (newValue == emptyValue) + throw new IllegalArgumentException("Computed value cannot be the 'empty value' " + emptyValue); + fromLong(values, newValue, index * bytesPerValue); + return new ReturnValue(oldValue); + } + + // key does not exist: insert valueIfAbsent + index = ~index; + ReturnValue downTreeRV; + if (isLeaf || children[index] == null) { + // insert + downTreeRV = new ReturnValue(null); + downTreeRV.tree = checkSplitEntry(); + if (downTreeRV.tree == null) { + insertKeyValue(index, key, fromLong(valueIfAbsent)); + } else if (index <= splitIndex) { + downTreeRV.tree.children[0].insertKeyValue(index, key, fromLong(valueIfAbsent)); + } else { + downTreeRV.tree.children[1].insertKeyValue(index - splitIndex - 1, key, fromLong(valueIfAbsent)); + } + return downTreeRV; + } + + downTreeRV = children[index].putOrCompute(key, valueIfAbsent, computeIfPresent); + if (downTreeRV.oldValue != null) // only update + return downTreeRV; + + if (downTreeRV.tree != null) { + // split this treeEntry if it is too big + BTreeEntry returnTree, downTree = returnTree = checkSplitEntry(); + if (downTree == null) { + insertTree(index, downTreeRV.tree); + } else if (index <= splitIndex) { + downTree.children[0].insertTree(index, downTreeRV.tree); + } else { + downTree.children[1].insertTree(index - splitIndex - 1, downTreeRV.tree); + } + + downTreeRV.tree = returnTree; + } + return downTreeRV; + } + /** * @return null if nothing to do or a new sub tree if this tree capacity is no longer * sufficient. diff --git a/core/src/main/java/com/graphhopper/coll/GHSortedCollection.java b/core/src/main/java/com/graphhopper/coll/GHSortedCollection.java index 0fb495f0577..8f2095a5889 100644 --- a/core/src/main/java/com/graphhopper/coll/GHSortedCollection.java +++ b/core/src/main/java/com/graphhopper/coll/GHSortedCollection.java @@ -17,9 +17,7 @@ */ package com.graphhopper.coll; -import com.carrotsearch.hppc.LongArrayList; import com.carrotsearch.hppc.cursors.IntCursor; -import com.carrotsearch.hppc.predicates.IntPredicate; import java.util.Iterator; import java.util.Map.Entry; diff --git a/core/src/main/java/com/graphhopper/coll/LongLongMap.java b/core/src/main/java/com/graphhopper/coll/LongLongMap.java index 33cad5705c6..16670588973 100644 --- a/core/src/main/java/com/graphhopper/coll/LongLongMap.java +++ b/core/src/main/java/com/graphhopper/coll/LongLongMap.java @@ -17,12 +17,23 @@ */ package com.graphhopper.coll; +import java.util.function.LongUnaryOperator; + /** * @author Peter Karich */ public interface LongLongMap { long put(long key, long value); + /** + * If the key is absent, inserts valueIfAbsent. + * If the key is present, updates it with computeIfPresent.applyAsLong(currentValue). + * This is done in a single traversal. + * + * @return the previous value, or the empty value if the key was absent + */ + long putOrCompute(long key, long valueIfAbsent, LongUnaryOperator computeIfPresent); + long get(long key); long getSize(); diff --git a/core/src/main/java/com/graphhopper/config/Profile.java b/core/src/main/java/com/graphhopper/config/Profile.java index 0223211d9f3..0ccaa07a932 100644 --- a/core/src/main/java/com/graphhopper/config/Profile.java +++ b/core/src/main/java/com/graphhopper/config/Profile.java @@ -20,9 +20,15 @@ import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.graphhopper.util.CustomModel; import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; +import com.graphhopper.util.TurnCostsConfig; + +import java.util.List; + +import static java.util.Collections.emptyList; /** * Corresponds to an entry of the `profiles` section in `config.yml` and specifies the properties of a routing profile. @@ -33,10 +39,9 @@ * @see LMProfile */ public class Profile { - private String name = "car"; - private String vehicle = "car"; + private String name; + private TurnCostsConfig turnCostsConfig; private String weighting = "custom"; - private boolean turnCosts = false; private PMap hints = new PMap(); public static void validateProfileName(String profileName) { @@ -56,9 +61,8 @@ public Profile(String name) { public Profile(Profile p) { setName(p.getName()); - setVehicle(p.getVehicle()); + setTurnCostsConfig(p.getTurnCostsConfig()); setWeighting(p.getWeighting()); - setTurnCosts(p.isTurnCosts()); hints = new PMap(p.getHints()); } @@ -72,13 +76,14 @@ public Profile setName(String name) { return this; } - public String getVehicle() { - return vehicle; + public Profile setTurnCostsConfig(TurnCostsConfig turnCostsConfig) { + this.turnCostsConfig = turnCostsConfig; + return this; } - public Profile setVehicle(String vehicle) { - this.vehicle = vehicle; - return this; + @JsonProperty("turn_costs") + public TurnCostsConfig getTurnCostsConfig() { + return turnCostsConfig; } public String getWeighting() { @@ -101,13 +106,8 @@ public CustomModel getCustomModel() { return getHints().getObject(CustomModel.KEY, null); } - public boolean isTurnCosts() { - return turnCosts; - } - - public Profile setTurnCosts(boolean turnCosts) { - this.turnCosts = turnCosts; - return this; + public boolean hasTurnCosts() { + return turnCostsConfig != null; } @JsonIgnore @@ -117,13 +117,17 @@ public PMap getHints() { @JsonAnySetter public Profile putHint(String key, Object value) { + if (key.equals("u_turn_costs")) + throw new IllegalArgumentException("u_turn_costs no longer accepted in profile. Use the turn costs configuration instead, see docs/migration/config-migration-08-09.md"); + if (key.equals("vehicle")) + throw new IllegalArgumentException("vehicle no longer accepted in profile, see docs/migration/config-migration-08-09.md"); this.hints.putObject(key, value); return this; } @Override public String toString() { - return createContentString(); + return createContentString(emptyList()); } @Override @@ -134,9 +138,11 @@ public boolean equals(Object o) { return name.equals(profile.name); } - private String createContentString() { + private String createContentString(List excludedHints) { // used to check against stored custom models, see #2026 - return "name=" + name + "|vehicle=" + vehicle + "|weighting=" + weighting + "|turnCosts=" + turnCosts + "|hints=" + hints; + PMap filteredHints = new PMap(hints); + excludedHints.forEach(filteredHints::remove); + return "name=" + name + "|turn_costs={" + turnCostsConfig + "}|weighting=" + weighting + "|hints=" + filteredHints; } @Override @@ -145,6 +151,11 @@ public int hashCode() { } public int getVersion() { - return Helper.staticHashCode(createContentString()); + return getVersion(emptyList()); } + + public int getVersion(List excludedHints) { + return Helper.staticHashCode(createContentString(excludedHints)); + } + } diff --git a/core/src/main/java/com/graphhopper/isochrone/algorithm/ContourBuilder.java b/core/src/main/java/com/graphhopper/isochrone/algorithm/ContourBuilder.java index 0edc2e0be40..9f100b3dd5e 100644 --- a/core/src/main/java/com/graphhopper/isochrone/algorithm/ContourBuilder.java +++ b/core/src/main/java/com/graphhopper/isochrone/algorithm/ContourBuilder.java @@ -13,7 +13,7 @@ the License, or (at your option) any later version. package com.graphhopper.isochrone.algorithm; -import org.locationtech.jts.algorithm.CGAlgorithms; +import org.locationtech.jts.algorithm.Area; import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.prep.PreparedPolygon; import org.locationtech.jts.triangulate.quadedge.Vertex; @@ -125,7 +125,7 @@ private List punchHoles(List rings) { List holes = new ArrayList<>(rings.size() / 2); // 1. Split the polygon list in two: shells and holes (CCW and CW) for (LinearRing ring : rings) { - if (CGAlgorithms.signedArea(ring.getCoordinateSequence()) > 0.0) + if (Area.ofRingSigned(ring.getCoordinateSequence()) > 0.0) holes.add(ring); else shells.add(new PreparedPolygon(geometryFactory.createPolygon(ring))); @@ -157,4 +157,4 @@ private List punchHoles(List rings) { } return punched; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/isochrone/algorithm/JTSTriangulator.java b/core/src/main/java/com/graphhopper/isochrone/algorithm/JTSTriangulator.java index 7e068d91613..1591c5d63d8 100644 --- a/core/src/main/java/com/graphhopper/isochrone/algorithm/JTSTriangulator.java +++ b/core/src/main/java/com/graphhopper/isochrone/algorithm/JTSTriangulator.java @@ -27,16 +27,15 @@ import com.graphhopper.util.PointList; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.Geometry; +import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Polygon; -import org.locationtech.jts.triangulate.ConformingDelaunayTriangulator; -import org.locationtech.jts.triangulate.ConstraintVertex; +import org.locationtech.jts.triangulate.DelaunayTriangulationBuilder; import org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision; import org.locationtech.jts.triangulate.quadedge.Vertex; import java.util.ArrayList; import java.util.Collection; import java.util.function.ToDoubleFunction; -import java.util.stream.Collectors; public class JTSTriangulator implements Triangulator { @@ -63,6 +62,10 @@ public Result triangulate(Snap snap, QueryGraph queryGraph, ShortestPathTree sho PointList innerPoints = edge.fetchWayGeometry(FetchMode.PILLAR_ONLY); if (innerPoints.size() > 0) { int midIndex = innerPoints.size() / 2; + if (innerPoints.size() % 2 == 0 && edge.get(EdgeIteratorState.REVERSE_STATE)) + // For edge-based routing we might have explored the same edge in two different directions. + // Here we make sure we only include the **same** point twice instead of two different ones. + midIndex -= 1; double lat2 = innerPoints.getLat(midIndex); double lon2 = innerPoints.getLon(midIndex); Coordinate site2 = new Coordinate(lon2, lat2); @@ -80,12 +83,10 @@ public Result triangulate(Snap snap, QueryGraph queryGraph, ShortestPathTree sho // But that's okay, the triangulator de-dupes by itself, and it keeps the first z-value it sees, which is // what we want. - Collection constraintVertices = sites.stream().map(ConstraintVertex::new).collect(Collectors.toList()); - ConformingDelaunayTriangulator conformingDelaunayTriangulator = new ConformingDelaunayTriangulator(constraintVertices, tolerance); - conformingDelaunayTriangulator.setConstraints(new ArrayList<>(), new ArrayList<>()); - conformingDelaunayTriangulator.formInitialDelaunay(); - conformingDelaunayTriangulator.enforceConstraints(); - Geometry convexHull = conformingDelaunayTriangulator.getConvexHull(); + DelaunayTriangulationBuilder triangulationBuilder = new DelaunayTriangulationBuilder(); + triangulationBuilder.setSites(sites); + triangulationBuilder.setTolerance(tolerance); + Geometry convexHull = triangulationBuilder.getEdges(new GeometryFactory()).convexHull(); // If there's only one site (and presumably also if the convex hull is otherwise degenerated), // the triangulation only contains the frame, and not the site within the frame. Not sure if I agree with that. @@ -102,7 +103,7 @@ public Result triangulate(Snap snap, QueryGraph queryGraph, ShortestPathTree sho + "Please try a different 'point' or a larger 'time_limit'."); } - QuadEdgeSubdivision tin = conformingDelaunayTriangulator.getSubdivision(); + QuadEdgeSubdivision tin = triangulationBuilder.getSubdivision(); for (Vertex vertex : (Collection) tin.getVertices(true)) { if (tin.isFrameVertex(vertex)) { vertex.setZ(Double.MAX_VALUE); diff --git a/core/src/main/java/com/graphhopper/isochrone/algorithm/ShortestPathTree.java b/core/src/main/java/com/graphhopper/isochrone/algorithm/ShortestPathTree.java index 4ec08dd11db..c89d8ca1e8a 100644 --- a/core/src/main/java/com/graphhopper/isochrone/algorithm/ShortestPathTree.java +++ b/core/src/main/java/com/graphhopper/isochrone/algorithm/ShortestPathTree.java @@ -27,7 +27,6 @@ import com.graphhopper.storage.Graph; import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.GHUtility; -import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Collection; diff --git a/core/src/main/java/com/graphhopper/reader/ReaderElement.java b/core/src/main/java/com/graphhopper/reader/ReaderElement.java index 22f95bf431c..c56a6d3c6e8 100644 --- a/core/src/main/java/com/graphhopper/reader/ReaderElement.java +++ b/core/src/main/java/com/graphhopper/reader/ReaderElement.java @@ -162,12 +162,12 @@ public boolean hasTag(List keyList, Object value) { } /** - * Returns the first existing tag of the specified list where the order is important. + * Returns the first existing value of the specified list of keys where the order is important. * * @return an empty string if nothing found */ - public String getFirstPriorityTag(List restrictions) { - for (String str : restrictions) { + public String getFirstValue(List searchedTags) { + for (String str : searchedTags) { Object value = properties.get(str); if (value != null) return (String) value; @@ -175,6 +175,19 @@ public String getFirstPriorityTag(List restrictions) { return ""; } + /** + * @return -1 if not found + */ + public int getFirstIndex(List searchedTags) { + for (int i = 0; i < searchedTags.size(); i++) { + String str = searchedTags.get(i); + Object value = properties.get(str); + if (value != null) + return i; + } + return -1; + } + public void removeTag(String name) { properties.remove(name); } diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java index 627e4145d45..526ab4e49fa 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractSRTMElevationProvider.java @@ -46,7 +46,7 @@ public abstract class AbstractSRTMElevationProvider extends TileBasedElevationPr public AbstractSRTMElevationProvider(String baseUrl, String cacheDir, String downloaderName, int minLat, int maxLat, int defaultWidth) { super(cacheDir); this.baseUrl = baseUrl; - downloader = new Downloader(downloaderName).setTimeout(10000); + downloader = new Downloader().setTimeout(10000); this.DEFAULT_WIDTH = defaultWidth; this.MIN_LAT = minLat; this.MAX_LAT = maxLat; @@ -156,10 +156,10 @@ private void updateHeightsFromFile(double lat, double lon, DataAccess heights) t heights.flush(); } catch (FileNotFoundException ex) { - logger.warn("File not found for the coordinates for " + lat + "," + lon); + logger.warn("File not found " + heights + " for the coordinates " + lat + "," + lon); throw ex; } catch (Exception ex) { - throw new RuntimeException("There was an issue looking up the coordinates for " + lat + "," + lon, ex); + throw new RuntimeException("There was an issue with " + heights + " looking up the coordinates " + lat + "," + lon, ex); } } diff --git a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java index 5a4ab8e3f68..df792141b12 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/AbstractTiffElevationProvider.java @@ -45,10 +45,10 @@ public abstract class AbstractTiffElevationProvider extends TileBasedElevationPr // Degrees of longitude covered by this tile final int LON_DEGREE; - public AbstractTiffElevationProvider(String baseUrl, String cacheDir, String downloaderName, int width, int height, int latDegree, int lonDegree) { + public AbstractTiffElevationProvider(String baseUrl, String cacheDir, int width, int height, int latDegree, int lonDegree) { super(cacheDir); this.baseUrl = baseUrl; - this.downloader = new Downloader(downloaderName).setTimeout(10000); + this.downloader = new Downloader().setTimeout(10000); this.WIDTH = width; this.HEIGHT = height; this.LAT_DEGREE = latDegree; diff --git a/core/src/main/java/com/graphhopper/reader/dem/CGIARProvider.java b/core/src/main/java/com/graphhopper/reader/dem/CGIARProvider.java index 30392947bc4..7291c93f347 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/CGIARProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/CGIARProvider.java @@ -46,7 +46,6 @@ * @author Peter Karich */ public class CGIARProvider extends AbstractTiffElevationProvider { - private final double invPrecision = 1 / precision; public CGIARProvider() { this(""); @@ -56,7 +55,6 @@ public CGIARProvider(String cacheDir) { // Alternative URLs for the CGIAR data can be found in #346 super("https://srtm.csi.cgiar.org/wp-content/uploads/files/srtm_5x5/TIFF/", cacheDir.isEmpty() ? "/tmp/cgiar" : cacheDir, - "GraphHopper CGIARReader", 6000, 6000, 5, 5); } @@ -117,12 +115,8 @@ Raster readFile(File file, String tifName) { } int down(double val) { - // 'rounding' to closest 5 - int intVal = (int) (val / LAT_DEGREE) * LAT_DEGREE; - if (!(val >= 0 || intVal - val < invPrecision)) - intVal = intVal - LAT_DEGREE; - - return intVal; + // floor to nearest multiple of LAT_DEGREE + return (int) (Math.floor(val / LAT_DEGREE)) * LAT_DEGREE; } @Override @@ -131,13 +125,10 @@ boolean isOutsideSupportedArea(double lat, double lon) { } protected String getFileName(double lat, double lon) { - lon = 1 + (180 + lon) / LAT_DEGREE; - int lonInt = (int) lon; - lat = 1 + (60 - lat) / LAT_DEGREE; - int latInt = (int) lat; - - if (Math.abs(latInt - lat) < invPrecision / LAT_DEGREE) - latInt--; + int minLat = down(lat); + int minLon = down(lon); + int lonInt = 1 + (minLon + 180) / LAT_DEGREE; + int latInt = (60 - minLat) / LAT_DEGREE; // replace String.format as it seems to be slow // String.format("srtm_%02d_%02d", lonInt, latInt); diff --git a/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamer.java b/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamer.java index 7c0bb973960..9337e76e69a 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamer.java +++ b/core/src/main/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamer.java @@ -2,6 +2,8 @@ import com.graphhopper.util.DistanceCalcEarth; import com.graphhopper.util.PointList; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Elevation data is read from DEM tiles that have data points for rectangular tiles usually having an @@ -15,6 +17,7 @@ * @author Peter Karich */ public class EdgeElevationSmoothingRamer { + /** * This method removes elevation fluctuations up to maxElevationDelta. Compared to the smoothMovingAverage function * this method has the advantage that the maximum slope of a PointList never increases (max(abs(slope_i))). @@ -27,20 +30,28 @@ public class EdgeElevationSmoothingRamer { * point of the specified pointList */ public static void smooth(PointList pointList, double maxElevationDelta) { - internSmooth(pointList, 0, pointList.size() - 1, maxElevationDelta); + internSmooth(pointList, 0, pointList.size() - 1, maxElevationDelta, DistanceCalcEarth.calcDistance(pointList, false), 0); } - static void internSmooth(PointList pointList, int fromIndex, int lastIndex, double maxElevationDelta) { + private static final Logger LOGGER = LoggerFactory.getLogger(EdgeElevationSmoothingRamer.class); + + static void internSmooth(PointList pointList, int fromIndex, int lastIndex, double maxElevationDelta, double fullDist2D, int depth) { if (lastIndex - fromIndex < 2) return; + if (depth > 1000) { + // implement stack-based version if this is really a problem in real world, see #3202 + LOGGER.warn("max recursion depth reached, remaining point list: " + pointList); + return; + } + double prevLat = pointList.getLat(fromIndex); double prevLon = pointList.getLon(fromIndex); - double dist2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, pointList.getLat(lastIndex), pointList.getLon(lastIndex)); // in rare cases the first point can be identical to the last for e.g. areas (or for things like man_made=pier which are not explicitly excluded from adding edges) - double averageSlope = dist2D == 0 ? 0 : (pointList.getEle(lastIndex) - pointList.getEle(fromIndex)) / dist2D; + double averageSlope = fullDist2D == 0 ? 0 : (pointList.getEle(lastIndex) - pointList.getEle(fromIndex)) / fullDist2D; double prevAverageSlopeEle = pointList.getEle(fromIndex); + double startDist = 0; double maxEleDelta = -1; int indexWithMaxDelta = -1; for (int i = fromIndex + 1; i < lastIndex; i++) { @@ -48,6 +59,7 @@ static void internSmooth(PointList pointList, int fromIndex, int lastIndex, doub double lon = pointList.getLon(i); double ele = pointList.getEle(i); double tmpDist2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, lat, lon); + startDist += tmpDist2D; double eleFromAverageSlope = averageSlope * tmpDist2D + prevAverageSlopeEle; double tmpEleDelta = Math.abs(ele - eleFromAverageSlope); if (maxEleDelta < tmpEleDelta) { @@ -59,7 +71,7 @@ static void internSmooth(PointList pointList, int fromIndex, int lastIndex, doub prevLon = lon; } - // the maximum elevation change limit filters away especially the smaller high frequent elevation changes, + // The limit for the maximum elevation change filters away especially the smaller high frequent elevation changes, // which is likely the "noise" that we want to remove. if (indexWithMaxDelta < 0 || maxElevationDelta > maxEleDelta) { prevLat = pointList.getLat(fromIndex); @@ -76,8 +88,8 @@ static void internSmooth(PointList pointList, int fromIndex, int lastIndex, doub prevLon = lon; } } else { - internSmooth(pointList, fromIndex, indexWithMaxDelta, maxElevationDelta); - internSmooth(pointList, indexWithMaxDelta, lastIndex, maxElevationDelta); + internSmooth(pointList, fromIndex, indexWithMaxDelta, maxElevationDelta, startDist, depth + 1); + internSmooth(pointList, indexWithMaxDelta, lastIndex, maxElevationDelta, Math.max(0, fullDist2D - startDist), depth + 1); } } } diff --git a/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java index 79da1a8477a..58d32eb825d 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/ElevationProvider.java @@ -24,6 +24,12 @@ */ public interface ElevationProvider { ElevationProvider NOOP = new ElevationProvider() { + + @Override + public ElevationProvider init() { + return this; + } + @Override public double getEle(double lat, double lon) { return Double.NaN; @@ -39,6 +45,8 @@ public boolean canInterpolate() { } }; + ElevationProvider init(); + /** * @return returns the height in meters or Double.NaN if invalid */ diff --git a/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java b/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java index e1ed2fb20ed..a0ffc7edb92 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/GMTEDProvider.java @@ -88,7 +88,6 @@ public GMTEDProvider() { public GMTEDProvider(String cacheDir) { super("https://edcintl.cr.usgs.gov/downloads/sciweb1/shared/topo/downloads/GMTED/Global_tiles_GMTED/075darcsec/mea/", cacheDir.isEmpty() ? "/tmp/gmted" : cacheDir, - "GraphHopper GMTEDReader", 14400, 9600, 20, 30); } diff --git a/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java b/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java index 61b0a2723cd..73da5205095 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java +++ b/core/src/main/java/com/graphhopper/reader/dem/HeightTile.java @@ -18,6 +18,8 @@ package com.graphhopper.reader.dem; import com.graphhopper.storage.DataAccess; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import javax.imageio.ImageIO; import java.awt.*; @@ -32,6 +34,8 @@ * @author Peter Karich */ public class HeightTile { + + private final Logger logger = LoggerFactory.getLogger(getClass()); private final int minLat; private final int minLon; private final int width; @@ -93,8 +97,8 @@ private double linearInterpolate(double a, double b, double f) { } public double getHeight(double lat, double lon) { - double deltaLat = Math.abs(lat - minLat); - double deltaLon = Math.abs(lon - minLon); + double deltaLat = lat - minLat; + double deltaLon = lon - minLon; if (deltaLat > latHigherBound || deltaLat < lowerBound) throw new IllegalStateException("latitude not in boundary of this file:" + lat + "," + lon + ", this:" + this.toString()); if (deltaLon > lonHigherBound || deltaLon < lowerBound) @@ -174,4 +178,4 @@ public BufferedImage getImageFromArray(int[] pixels, int width, int height) { public String toString() { return minLat + "," + minLon; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/MultiSource3ElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/MultiSource3ElevationProvider.java new file mode 100644 index 00000000000..76c41ae6aa6 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/MultiSource3ElevationProvider.java @@ -0,0 +1,135 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import com.graphhopper.storage.DAType; + +/** + * The MultiSource3ElevationProvider mixes different elevation providers to provide the best available elevation data + * for the whole world. + * + * @author ratrun + */ +public class MultiSource3ElevationProvider extends TileBasedElevationProvider { + + // The provider that provides elevation data for Europe + private final TileBasedElevationProvider sonnyProvider; + // Usually a high resolution provider in the SRTM area + private final TileBasedElevationProvider srtmProvider; + // The fallback provider that provides elevation data globally + private final TileBasedElevationProvider globalProvider; + + public MultiSource3ElevationProvider(TileBasedElevationProvider srtmProvider, TileBasedElevationProvider globalProvider, TileBasedElevationProvider sonnyProvider) { + super("_ignored_"); + this.srtmProvider = srtmProvider; + this.globalProvider = globalProvider; + this.sonnyProvider = sonnyProvider; + } + + public MultiSource3ElevationProvider() { + this(new CGIARProvider(), new GMTEDProvider(), new SonnyProvider()); + } + + public MultiSource3ElevationProvider(String cacheDir) { + this(new CGIARProvider(cacheDir), new GMTEDProvider(cacheDir), new SonnyProvider(cacheDir)); + } + + @Override + public ElevationProvider init() { + srtmProvider.init(); + globalProvider.init(); + sonnyProvider.init(); + return this; + } + + @Override + public double getEle(double lat, double lon) { + try { + return sonnyProvider.getEle(lat, lon); + } catch ( Exception ex) { + // Sometimes the cgiar data north of 59.999 equals 0 + if (lat < 59.999 && lat > -56) { + double ele = srtmProvider.getEle(lat, lon); + if (Double.isNaN(ele)) { + // If the SRTM data is not available, use the global provider + ele = globalProvider.getEle(lat, lon); + } + return ele; + } + return globalProvider.getEle(lat, lon); + } + } + + /** + * For the MultiSource3ElevationProvider you have to specify the base URL separated by a ';'. + * The first for cgiar, the second for gmted, the third for sonny + */ + @Override + public MultiSource3ElevationProvider setBaseURL(String baseURL) { + String[] urls = baseURL.split(";"); + if (urls.length != 3) { + throw new IllegalArgumentException("The base url must consist of three urls separated by a ';'. The first for cgiar, the second for gmted"); + } + srtmProvider.setBaseURL(urls[0]); + globalProvider.setBaseURL(urls[1]); + sonnyProvider.setBaseURL(urls[2]); + return this; + } + + @Override + public MultiSource3ElevationProvider setDAType(DAType daType) { + srtmProvider.setDAType(daType); + globalProvider.setDAType(daType); + sonnyProvider.setDAType(daType); + return this; + } + + @Override + public MultiSource3ElevationProvider setInterpolate(boolean interpolate) { + srtmProvider.setInterpolate(interpolate); + globalProvider.setInterpolate(interpolate); + sonnyProvider.setInterpolate(interpolate); + return this; + } + + @Override + public boolean canInterpolate() { + return srtmProvider.canInterpolate() && globalProvider.canInterpolate() && sonnyProvider.canInterpolate(); + } + + @Override + public void release() { + srtmProvider.release(); + globalProvider.release(); + sonnyProvider.release(); + } + + @Override + public MultiSource3ElevationProvider setAutoRemoveTemporaryFiles(boolean autoRemoveTemporary) { + srtmProvider.setAutoRemoveTemporaryFiles(autoRemoveTemporary); + globalProvider.setAutoRemoveTemporaryFiles(autoRemoveTemporary); + sonnyProvider.setAutoRemoveTemporaryFiles(autoRemoveTemporary); + return this; + } + + @Override + public String toString() { + return "multi3"; + } + +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java index 1d80c5183bd..bae174ea61d 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/MultiSourceElevationProvider.java @@ -33,7 +33,7 @@ public class MultiSourceElevationProvider extends TileBasedElevationProvider { private final TileBasedElevationProvider globalProvider; public MultiSourceElevationProvider(TileBasedElevationProvider srtmProvider, TileBasedElevationProvider globalProvider) { - super(srtmProvider.cacheDir.getAbsolutePath()); + super("_ignored_"); this.srtmProvider = srtmProvider; this.globalProvider = globalProvider; } @@ -46,11 +46,23 @@ public MultiSourceElevationProvider(String cacheDir) { this(new CGIARProvider(cacheDir), new GMTEDProvider(cacheDir)); } + @Override + public ElevationProvider init() { + srtmProvider.init(); + globalProvider.init(); + return this; + } + @Override public double getEle(double lat, double lon) { // Sometimes the cgiar data north of 59.999 equals 0 if (lat < 59.999 && lat > -56) { - return srtmProvider.getEle(lat, lon); + double ele = srtmProvider.getEle(lat, lon); + if (Double.isNaN(ele)) { + // If the SRTM data is not available, use the global provider + ele = globalProvider.getEle(lat, lon); + } + return ele; } return globalProvider.getEle(lat, lon); } diff --git a/core/src/main/java/com/graphhopper/reader/dem/PMTilesElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/PMTilesElevationProvider.java new file mode 100644 index 00000000000..062f9786cfd --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/PMTilesElevationProvider.java @@ -0,0 +1,502 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import com.graphhopper.storage.MMapDataAccess; +import org.slf4j.LoggerFactory; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import java.util.ArrayDeque; +import java.util.HashMap; +import java.util.Map; + +/** + * GraphHopper ElevationProvider that reads elevation data directly from a + * PMTiles v3 archive containing terrain-RGB encoded tiles. + *

+ * If a directory of pre-decoded .tile files exists (created from previously runs), + * each file is memory-mapped directly and there is no image decoding, no copying happening. + * Otherwise tiles are decoded from PMTiles on first access and written as .tile + * files so subsequent runs skip decoding. + *

+ * Not thread-safe. + */ +public class PMTilesElevationProvider implements ElevationProvider { + + public enum TerrainEncoding {MAPBOX, TERRARIUM} + + private static class PackedTileData { + private ByteBuffer data; + private final int blockSize; + private final int blocksPerAxis; + private int[] blockOffsets; + private final int payloadOffset; + + PackedTileData(ByteBuffer data, int blockSize, int blocksPerAxis, int[] blockOffsets, int payloadOffset) { + this.data = data; + this.blockSize = blockSize; + this.blocksPerAxis = blocksPerAxis; + if (blockOffsets.length != blocksPerAxis * blocksPerAxis + 1) + throw new IllegalArgumentException("Invalid packed block table length"); + this.blockOffsets = blockOffsets; + this.payloadOffset = payloadOffset; + } + + public short get(int x, int y) { + int blockX = x / blockSize; + int blockY = y / blockSize; + int blockIndex = blockY * blocksPerAxis + blockX; + int blockStart = payloadOffset + blockOffsets[blockIndex]; + int localX = x - blockX * blockSize; + int localY = y - blockY * blockSize; + int idx = localY * blockSize + localX; + int type = data.get(blockStart) & 0xFF; + + if (type == PackedTileCodec.TYPE_SEA) { + return 0; + } else if (type == PackedTileCodec.TYPE_CONST) { + return data.getShort(blockStart + 1); + } else if (type == PackedTileCodec.TYPE_DELTA8) { + short base = data.getShort(blockStart + 1); + int delta = data.get(blockStart + 3 + idx) & 0xFF; + return (short) (base + delta); + } else if (type == PackedTileCodec.TYPE_RAW16) { + return data.getShort(blockStart + 1 + idx * 2); + } + throw new IllegalStateException("Unknown packed block type: " + type); + } + + void release() { + if (data != null && data.isDirect()) // ensure it is not MISSING or SEA or heap allocated + MMapDataAccess.cleanMappedByteBuffer(data); + data = null; + blockOffsets = null; + } + } + + private static final PackedTileData MISSING_TILE = new PackedTileData(null, 1, 1, new int[]{0, 0}, 0) { + @Override + public short get(int x, int y) { + return Short.MIN_VALUE; + } + }; + private static final PackedTileData SEA_LEVEL_TILE = new PackedTileData(null, 1, 1, new int[]{0, 0}, 0) { + @Override + public short get(int x, int y) { + return 0; + } + }; + + private final TerrainEncoding encoding; + private final boolean interpolate; + private final int preferredZoom; + private int zoom; + private long hilbertBase; + private int n; // 1 << zoom + + private final PMTilesReader reader = new PMTilesReader(); + + // Cache of packed tiles, keyed by Hilbert tile ID. Missing (or all-sea) tiles use marker objects. + // On-disk .tile files use the packed block format defined in PackedTileCodex. + private final Map tileBuffers = new HashMap<>(); + + // Last-tile cache: consecutive getEle() calls typically hit the same tile. + private long lastTileId = -1; + private PackedTileData lastTileBuf; + + private int tileSize; + + // Directory for .tile files. If non-null and writable, decoded tiles are persisted + // there so subsequent runs can mmap them without re-decoding. + private File tileDir; + private final String tileDirStr; + + private boolean clearTileFiles = true; + + private final String pmFileStr; + + /** + * @param preferredZoom 10 means ~76m at equator and ~49m in Germany (default). + * 11 means ~38m at equator and ~25m in Germany. + * 12 means ~19m at equator and ~12m in Germany. + * @param tileDir directory for .tile tile cache files. Pre-populated by pmtiles_to_ele.py + * or built lazily on first access. If null, decoded tiles are kept on heap only. + */ + public PMTilesElevationProvider(String pmFile, TerrainEncoding encoding, + boolean interpolate, int preferredZoom, String tileDir) { + this.encoding = encoding; + this.interpolate = interpolate; + this.preferredZoom = preferredZoom; + this.pmFileStr = pmFile; + this.tileDirStr = tileDir; + } + + @Override + public ElevationProvider init() { + try { + reader.open(pmFileStr); + reader.checkWebPSupport(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + this.zoom = preferredZoom > 0 ? preferredZoom : Math.min(reader.header.maxZoom, 11); + if (this.zoom < 1) + throw new IllegalArgumentException("Zoom level must be at least 1, got " + this.zoom); + this.hilbertBase = PMTilesReader.hilbertBase(zoom); + this.n = 1 << zoom; + + if (tileDirStr != null && !tileDirStr.isEmpty()) { + this.tileDir = new File(tileDirStr); + this.tileDir.mkdirs(); + } + return this; + } + + public PMTilesElevationProvider setAutoRemoveTemporaryFiles(boolean clearTileFiles) { + this.clearTileFiles = clearTileFiles; + return this; + } + + @Override + public double getEle(double lat, double lon) { + try { + return sampleElevation(lat, lon); + } catch (Exception e) { + System.err.println("PMTilesElevationProvider.getEle(" + lat + ", " + lon + ") failed: " + e.getMessage()); + return Double.NaN; + } + } + + @Override + public boolean canInterpolate() { + return interpolate; + } + + @Override + public void release() { + for (PackedTileData p : tileBuffers.values()) { + p.release(); + } + tileBuffers.clear(); + lastTileId = -1; + lastTileBuf = null; + reader.close(); + if (clearTileFiles && tileDir != null) { + File[] files = tileDir.listFiles((dir, name) -> name.endsWith(".tile")); + if (files != null) + for (File f : files) f.delete(); + } + } + + private long zxyToTileId(int x, int y) { + return hilbertBase + PMTilesReader.xyToHilbertD(zoom, x, y); + } + + private double sampleElevation(double lat, double lon) throws IOException { + double xTileD = (lon + 180.0) / 360.0 * n; + double latRad = Math.toRadians(lat); + double yTileD = (1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n; + + int tileX = Math.max(0, Math.min(n - 1, (int) Math.floor(xTileD))); + int tileY = Math.max(0, Math.min(n - 1, (int) Math.floor(yTileD))); + + PackedTileData tile = getTileBuffer(zxyToTileId(tileX, tileY), tileX, tileY); + if (tile == MISSING_TILE) return Double.NaN; + if (tile == SEA_LEVEL_TILE) return 0; + + int w = tileSize, h = tileSize; + double px = (xTileD - tileX) * (w - 1); + double py = (yTileD - tileY) * (h - 1); + + if (interpolate) { + int x0 = Math.max(0, Math.min(w - 2, (int) Math.floor(px))); + int y0 = Math.max(0, Math.min(h - 2, (int) Math.floor(py))); + double fx = px - x0, fy = py - y0; + short v00 = tile.get(x0, y0); + short v10 = tile.get(x0 + 1, y0); + short v01 = tile.get(x0, y0 + 1); + short v11 = tile.get(x0 + 1, y0 + 1); + if (v00 == Short.MIN_VALUE || v10 == Short.MIN_VALUE || v01 == Short.MIN_VALUE || v11 == Short.MIN_VALUE) + return Double.NaN; + return v00 * (1 - fx) * (1 - fy) + v10 * fx * (1 - fy) + + v01 * (1 - fx) * fy + v11 * fx * fy; + } else { + int ix = Math.max(0, Math.min(w - 1, (int) Math.round(px))); + int iy = Math.max(0, Math.min(h - 1, (int) Math.round(py))); + short val = tile.get(ix, iy); + if (val == Short.MIN_VALUE) return Double.NaN; + return val; + } + } + + private PackedTileData getTileBuffer(long tileId, int tileX, int tileY) throws IOException { + if (tileId == lastTileId) return lastTileBuf; + + PackedTileData existing = tileBuffers.get(tileId); + if (existing != null) { + lastTileId = tileId; + lastTileBuf = existing; + return existing; + } + + // Try pre-decoded .tile file first + PackedTileData buf = tryMmapTileFile(tileId); + if (buf == null) { + // Decode from PMTiles + byte[] raw = reader.getTileBytes(tileId); + if (raw == null) { + buf = MISSING_TILE; + } else { + byte[] elevBytes = decodeTerrain(raw); + if (elevBytes == null) { + buf = MISSING_TILE; + } else if (elevBytes.length == 0) { + buf = SEA_LEVEL_TILE; + } else { + fillGaps(elevBytes, tileSize, tileX, tileY, n); + buf = persistAndLoad(tileId, elevBytes); + } + } + } + + tileBuffers.put(tileId, buf); + lastTileId = tileId; + lastTileBuf = buf; + return buf; + } + + /** + * Try to mmap an existing .tile file. Returns tile data if the file exists, + * or null if not found (either no tileDir or file not yet decoded). + */ + private PackedTileData tryMmapTileFile(long tileId) throws IOException { + if (tileDir == null) return null; + File f = tileFile(tileId); + if (!f.exists()) return null; + return loadTileData(f); + } + + /** + * Write decoded bytes to a packed .tile file and load it, or keep packed bytes on heap if no tileDir. + */ + private PackedTileData persistAndLoad(long tileId, byte[] elevBytes) throws IOException { + byte[] packed = PackedTileCodec.encodePacked(elevBytes, tileSize, PackedTileCodec.DEFAULT_BLOCK_SIZE); + if (tileDir != null) { + File f = tileFile(tileId); + Files.write(f.toPath(), packed); + return loadTileData(f); + } + // ByteBuffer in heap + ByteBuffer buf = ByteBuffer.wrap(packed).order(ByteOrder.LITTLE_ENDIAN); + return toPackedTileData(buf); + } + + private File tileFile(long tileId) { + return new File(tileDir, tileId + "_" + zoom + ".tile"); + } + + private PackedTileData loadTileData(File f) throws IOException { + try (FileChannel ch = FileChannel.open(f.toPath(), StandardOpenOption.READ)) { + ByteBuffer buf = ch.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); + buf.order(ByteOrder.LITTLE_ENDIAN); + if (!PackedTileCodec.isPackedTile(buf)) { + throw new IOException("Unsupported legacy raw .tile format in " + f + + ". Remove cached .tile files so they can be regenerated as packed tiles."); + } + return toPackedTileData(buf); + } + } + + private PackedTileData toPackedTileData(ByteBuffer buf) { + PackedTileCodec.PackedHeader h = PackedTileCodec.readPackedHeader(buf); + if (tileSize == 0) tileSize = h.tileSize(); // tileSize is set when tile comes from cache + else if (tileSize != h.tileSize()) + throw new IllegalStateException("Inconsistent packed tile size: expected " + tileSize + " but got " + h.tileSize()); + if (tileSize < PackedTileCodec.DEFAULT_BLOCK_SIZE) + throw new IllegalStateException("tileSize must be at least " + PackedTileCodec.DEFAULT_BLOCK_SIZE + ", got " + tileSize); + return new PackedTileData(buf, h.blockSize(), h.blocksPerAxis(), h.blockOffsets(), h.payloadOffset()); + } + + /** + * BFS wavefront fill: replaces Short.MIN_VALUE gap pixels with the average of their + * valid 4-connected neighbors, propagating inward. Only gap pixels reachable from valid + * data are filled; isolated gaps remain as Short.MIN_VALUE. + * See discussion + */ + static void fillGaps(byte[] data, int w, int tileX, int tileY, int n) { + ShortBuffer shorts = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + int total = shorts.capacity(); + int h = total / w; + int[] DX = {-1, 1, 0, 0}; + int[] DY = {0, 0, -1, 1}; + + // Log one line per connected gap area with its lat/lon centroid + boolean[] visited = new boolean[total]; + for (int i = 0; i < total; i++) { + if (shorts.get(i) != Short.MIN_VALUE || visited[i]) continue; + ArrayDeque comp = new ArrayDeque<>(); + comp.add(i); + visited[i] = true; + int count = 0; + long sumPx = 0, sumPy = 0; + while (!comp.isEmpty()) { + int ci = comp.poll(); + count++; + sumPx += ci % w; + sumPy += ci / w; + int cx = ci % w, cy = ci / w; + for (int d = 0; d < 4; d++) { + int nx = cx + DX[d], ny = cy + DY[d]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h) { + int ni = ny * w + nx; + if (shorts.get(ni) == Short.MIN_VALUE && !visited[ni]) { + visited[ni] = true; + comp.add(ni); + } + } + } + } + double cx = (double) sumPx / count; + double cy = (double) sumPy / count; + double lon = ((tileX + cx / w) / n) * 360.0 - 180.0; + double yNorm = (tileY + cy / h) / n; + double lat = Math.toDegrees(Math.atan(Math.sinh(Math.PI * (1 - 2 * yNorm)))); + LoggerFactory.getLogger(PMTilesElevationProvider.class) + .warn("fillGaps: {} pixels at lat={}, lon={}", count, + String.format("%.5f", lat), String.format("%.5f", lon)); + } + + // Seed: gap pixels bordering valid data + boolean[] queued = new boolean[total]; + ArrayDeque queue = new ArrayDeque<>(); + for (int i = 0; i < total; i++) { + if (shorts.get(i) != Short.MIN_VALUE) continue; + int x = i % w, y = i / w; + for (int d = 0; d < 4; d++) { + int nx = x + DX[d], ny = y + DY[d]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h + && shorts.get(ny * w + nx) != Short.MIN_VALUE) { + queue.add(i); + queued[i] = true; + break; + } + } + } + + // BFS: fill each gap pixel with average of valid neighbors + while (!queue.isEmpty()) { + int i = queue.poll(); + if (shorts.get(i) != Short.MIN_VALUE) continue; + int x = i % w, y = i / w; + int sum = 0, cnt = 0; + for (int d = 0; d < 4; d++) { + int nx = x + DX[d], ny = y + DY[d]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h) { + short v = shorts.get(ny * w + nx); + if (v != Short.MIN_VALUE) { + sum += v; + cnt++; + } + } + } + if (cnt == 0) continue; + shorts.put(i, (short) Math.round((double) sum / cnt)); + for (int d = 0; d < 4; d++) { + int nx = x + DX[d], ny = y + DY[d]; + if (nx >= 0 && nx < w && ny >= 0 && ny < h) { + int ni = ny * w + nx; + if (shorts.get(ni) == Short.MIN_VALUE && !queued[ni]) { + queue.add(ni); + queued[ni] = true; + } + } + } + } + } + + /** + * Decodes terrain-RGB image bytes into a little-endian byte[] of short elevation values. + * + * @return byte[] with LE-encoded shorts, empty byte[] if all elevations are exactly 0 (sea level), or null on decode failure. + */ + byte[] decodeTerrain(byte[] imageBytes) throws IOException { + BufferedImage img = ImageIO.read(new ByteArrayInputStream(imageBytes)); + if (img == null) { + // Check if it's a WebP file (RIFF....WEBP magic) + if (imageBytes.length > 12 && imageBytes[0] == 'R' && imageBytes[1] == 'I' + && imageBytes[2] == 'F' && imageBytes[3] == 'F' + && imageBytes[8] == 'W' && imageBytes[9] == 'E' + && imageBytes[10] == 'B' && imageBytes[11] == 'P') { + throw new IOException( + "Tile is WebP format but no WebP ImageIO plugin found. " + + "Add com.github.usefulness:webp-imageio to your classpath."); + } + return null; + } + + int w = img.getWidth(), h = img.getHeight(); + if (w != h) + throw new IOException("Unsupported non-square elevation tile: " + w + "x" + h + ". Expected square terrain tiles."); + if (tileSize == 0) tileSize = w; // tileSize set on first decode + else if (tileSize != w) + throw new IOException("Inconsistent terrain tile size: expected " + tileSize + " but got " + w); + if (tileSize % PackedTileCodec.DEFAULT_BLOCK_SIZE != 0) + throw new IOException("tileSize must be a multiple of blockSize: tileSize=" + tileSize + + ", blockSize=" + PackedTileCodec.DEFAULT_BLOCK_SIZE); + + byte[] elev = new byte[h * w * 2]; + boolean allSeaLevel = true; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int rgb = img.getRGB(x, y); + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + double e; + if (encoding == TerrainEncoding.MAPBOX) { + e = -10000.0 + (r * 65536 + g * 256 + b) * 0.1; + } else { + e = (r * 256.0 + g + b / 256.0) - 32768.0; + } + // Mapbox uses rgb(0,0,0) = -10000 and Terrarium rgb(0,0,0) = -32768 for + // no-data/ocean. No real place is below -1000m, so treat as no-data marker. + short s = e < -1000 ? Short.MIN_VALUE + : (short) Math.max(-32768, Math.min(32767, Math.round(e))); + if (s != 0) allSeaLevel = false; + + // little-endian, matching ByteBuffer.LITTLE_ENDIAN order + int idx = (y * w + x) * 2; + elev[idx] = (byte) (s & 0xFF); + elev[idx + 1] = (byte) ((s >> 8) & 0xFF); + } + } + return allSeaLevel ? new byte[0] : elev; + } +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/PMTilesExtract.java b/core/src/main/java/com/graphhopper/reader/dem/PMTilesExtract.java new file mode 100644 index 00000000000..c9bf9a0e314 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/PMTilesExtract.java @@ -0,0 +1,169 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.*; + +/** + * Diagnostic tool: extracts tiles from a PMTiles v3 file and saves each + * as a grayscale elevation PNG (black=low, white=high). + * + *

Usage: {@code java PMTilesExtract de.pmtiles ./tiles_out [zoom]}

+ */ +class PMTilesExtract { + + public static void main(String[] args) throws Exception { + if (args.length < 2) { + System.out.println("Usage: PMTilesExtract [zoom]"); + System.out.println(" If zoom is omitted, extracts one tile per zoom level at the center."); + System.out.println(" If zoom is given, extracts ALL tiles at that zoom level."); + return; + } + + String pmtilesPath = args[0]; + File outDir = new File(args[1]); + outDir.mkdirs(); + int requestedZoom = args.length >= 3 ? Integer.parseInt(args[2]) : -1; + + PMTilesReader reader = new PMTilesReader(); + reader.open(pmtilesPath); + reader.checkWebPSupport(); + PMTilesReader.Header h = reader.header; + + String[] typeNames = {"unknown", "mvt", "png", "jpeg", "webp", "avif"}; + System.out.println("Tile type: " + typeNames[Math.min(h.tileType, typeNames.length - 1)]); + System.out.println("Zoom: " + h.minZoom + " - " + h.maxZoom); + System.out.printf("Bounds: lon=[%.4f, %.4f] lat=[%.4f, %.4f]%n", + h.minLonE7 / 1e7, h.maxLonE7 / 1e7, h.minLatE7 / 1e7, h.maxLatE7 / 1e7); + System.out.println("Tiles: " + h.numAddressedTiles + " addressed, " + h.numTileEntries + " entries"); + System.out.println("Root directory: " + reader.rootDir.size() + " entries"); + + if (requestedZoom >= 0) { + System.out.println("\nExtracting all tiles at zoom " + requestedZoom + "..."); + int count = extractAllAtZoom(reader, requestedZoom, outDir); + System.out.println("Extracted " + count + " tiles to " + outDir); + } else { + System.out.println("\nExtracting center tile at each zoom level..."); + double centerLon = (h.minLonE7 + h.maxLonE7) / 2.0 / 1e7; + double centerLat = (h.minLatE7 + h.maxLatE7) / 2.0 / 1e7; + extractCenterTiles(reader, centerLat, centerLon, h.minZoom, h.maxZoom, outDir); + } + + reader.close(); + } + + private static void extractCenterTiles(PMTilesReader reader, double centerLat, double centerLon, + int minZoom, int maxZoom, File outDir) throws IOException { + for (int z = minZoom; z <= maxZoom; z++) { + int n = 1 << z; + int tx = (int) ((centerLon + 180.0) / 360.0 * n); + double latRad = Math.toRadians(centerLat); + int ty = (int) ((1.0 - Math.log(Math.tan(latRad) + 1.0 / Math.cos(latRad)) / Math.PI) / 2.0 * n); + tx = Math.max(0, Math.min(n - 1, tx)); + ty = Math.max(0, Math.min(n - 1, ty)); + + long tileId = PMTilesReader.zxyToTileId(z, tx, ty); + byte[] data = reader.getTileBytes(tileId); + + if (data == null) { + System.out.printf(" z=%2d x=%5d y=%5d tileId=%10d -> NOT FOUND%n", z, tx, ty, tileId); + continue; + } + + BufferedImage img = decodeImage(data); + BufferedImage gray = terrainToGrayscale(img); + File outFile = new File(outDir, String.format("z%d_x%d_y%d.png", z, tx, ty)); + ImageIO.write(gray, "png", outFile); + System.out.printf(" z=%2d x=%5d y=%5d tileId=%10d -> %s (%dx%d, %d bytes raw)%n", + z, tx, ty, tileId, outFile.getName(), img.getWidth(), img.getHeight(), data.length); + } + } + + private static int extractAllAtZoom(PMTilesReader reader, int zoom, File outDir) throws IOException { + long base = PMTilesReader.hilbertBase(zoom); + long count = 1L << (2 * zoom); + long endId = base + count; + System.out.printf(" TileId range for z=%d: [%d, %d) (%d tiles)%n", zoom, base, endId, count); + + int extracted = 0; + for (long tileId = base; tileId < endId; tileId++) { + int[] zxy = PMTilesReader.tileIdToZxy(tileId); + byte[] data = reader.getTileBytes(tileId); + if (data == null) continue; + + BufferedImage img = decodeImage(data); + BufferedImage gray = terrainToGrayscale(img); + File outFile = new File(outDir, String.format("z%d_x%d_y%d.png", zxy[0], zxy[1], zxy[2])); + ImageIO.write(gray, "png", outFile); + extracted++; + if (extracted % 100 == 0) System.out.println(" ... " + extracted + " tiles extracted"); + } + return extracted; + } + + // ========================================================================= + // Image decode + terrain-RGB to grayscale + // ========================================================================= + + private static BufferedImage decodeImage(byte[] data) throws IOException { + BufferedImage img = ImageIO.read(new ByteArrayInputStream(data)); + if (img != null) return img; + + String fmt = "unknown"; + if (data.length > 12 && data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F' + && data[8] == 'W' && data[9] == 'E' && data[10] == 'B' && data[11] == 'P') fmt = "WebP"; + else if (data.length > 4 && data[0] == (byte) 0x89 && data[1] == 'P') fmt = "PNG"; + + throw new IOException(fmt + " tile but ImageIO can't decode it (" + data.length + " bytes). " + + "Add to pom.xml: com.github.usefulness:webp-imageio"); + } + + /** Decode Terrarium-encoded terrain-RGB to 16-bit grayscale. Black=low, white=high. */ + private static BufferedImage terrainToGrayscale(BufferedImage img) { + int w = img.getWidth(), h = img.getHeight(); + + // Pass 1: decode elevations, find min/max + float[][] elev = new float[h][w]; + float min = Float.MAX_VALUE, max = -Float.MAX_VALUE; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int rgb = img.getRGB(x, y); + int r = (rgb >> 16) & 0xFF, g = (rgb >> 8) & 0xFF, b = rgb & 0xFF; + float e = (float) ((r * 256.0 + g + b / 256.0) - 32768.0); + elev[y][x] = e; + if (e < min) min = e; + if (e > max) max = e; + } + } + System.out.printf(" elevation: min=%.1fm max=%.1fm%n", min, max); + + // Pass 2: map to 16-bit grayscale + float range = max - min; + if (range < 1) range = 1; + BufferedImage out = new BufferedImage(w, h, BufferedImage.TYPE_USHORT_GRAY); + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int v = Math.max(0, Math.min(65535, (int) ((elev[y][x] - min) / range * 65535))); + out.getRaster().setSample(x, y, 0, v); + } + } + return out; + } +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/PMTilesReader.java b/core/src/main/java/com/graphhopper/reader/dem/PMTilesReader.java new file mode 100644 index 00000000000..2621afee723 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/PMTilesReader.java @@ -0,0 +1,360 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import javax.imageio.ImageIO; +import java.io.*; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.channels.FileChannel; +import java.util.*; +import java.util.zip.GZIPInputStream; + +/** + * Low-level PMTiles v3 archive reader. Handles header parsing, directory + * deserialization, Hilbert curve tile ID mapping, and raw tile byte retrieval. + */ +class PMTilesReader implements Closeable { + + static final int HEADER_LEN = 127; + static final int COMPRESS_GZIP = 2; + + private static final int LEAF_CACHE_SIZE = 1024 * 8; // here larger counts do not increase memory usage much + + private RandomAccessFile raf; + private FileChannel channel; + Header header; + List rootDir; + private final Map> leafCache = new LinkedHashMap<>(LEAF_CACHE_SIZE, 0.75f, true) { + @Override + protected boolean removeEldestEntry(Map.Entry> eldest) { + return size() > LEAF_CACHE_SIZE; + } + }; + + void open(String filePath) throws IOException { + if (header != null) return; + ImageIO.scanForPlugins(); + raf = new RandomAccessFile(filePath, "r"); + channel = raf.getChannel(); + header = readHeader(); + if (header.tileCompression > 1) + throw new IOException("PMTiles tile compression not supported for elevation data, got compression=" + header.tileCompression); + if (header.internalCompression != 0 && header.internalCompression != 1 && header.internalCompression != COMPRESS_GZIP) + throw new IOException("PMTiles internal compression not supported, got compression=" + header.internalCompression + + ". Only none (1) and gzip (2) are supported."); + rootDir = readDirectory(header.rootDirOffset, header.rootDirLength); + } + + @Override + public void close() { + rootDir = null; + header = null; + leafCache.clear(); + try { + if (channel != null) channel.close(); + if (raf != null) raf.close(); + } catch (IOException ignored) { + } + } + + void checkWebPSupport() throws IOException { + if (header.tileType == 4) { + boolean hasWebP = false; + for (String f : ImageIO.getReaderFormatNames()) + if (f.equalsIgnoreCase("webp")) { + hasWebP = true; + break; + } + if (!hasWebP) throw new IOException( + "PMTiles contains WebP tiles but no WebP ImageIO plugin found. " + + "Add com.github.usefulness:webp-imageio to your classpath."); + } + } + + byte[] getTileBytes(long tileId) throws IOException { + return findTile(tileId, rootDir, 0); + } + + private byte[] findTile(long tileId, List dir, int depth) throws IOException { + if (dir == null || dir.isEmpty() || depth > 5) return null; + + // Find the last entry where entry.tileId <= tileId + int lo = 0, hi = dir.size() - 1, idx = -1; + while (lo <= hi) { + int mid = (lo + hi) >>> 1; + if (dir.get(mid).tileId <= tileId) { + idx = mid; + lo = mid + 1; + } else { + hi = mid - 1; + } + } + if (idx < 0) return null; + + DirEntry e = dir.get(idx); + if (e.runLength > 0) { + if (tileId < e.tileId + e.runLength) { + return readBytes(header.tileDataOffset + e.offset, (int) e.length); + } + return null; + } else { + List leafDir = readLeafDirectory(e.offset, e.length); + return findTile(tileId, leafDir, depth + 1); + } + } + + // ========================================================================= + // Hilbert curve: Z/X/Y <-> TileID + // ========================================================================= + + static long hilbertBase(int z) { + // this is the closed form of: + // for (int i = 0; i < z; i++) base += (1L << (2 * i)); + return ((1L << (2 * z)) - 1) / 3; + } + + static long zxyToTileId(int z, int x, int y) { + if (z == 0) return 0; + return hilbertBase(z) + xyToHilbertD(z, x, y); + } + + static int[] tileIdToZxy(long tileId) { + if (tileId == 0) return new int[]{0, 0, 0}; + long acc = 0; + int z = 0; + while (true) { + long numTiles = 1L << (2 * z); + if (acc + numTiles > tileId) { + long[] xy = hilbertDToXY(z, tileId - acc); + return new int[]{z, (int) xy[0], (int) xy[1]}; + } + acc += numTiles; + z++; + } + } + + static long xyToHilbertD(int order, long x, long y) { + long d = 0; + for (int s = (1 << (order - 1)); s > 0; s >>= 1) { + long rx = (x & s) > 0 ? 1 : 0; + long ry = (y & s) > 0 ? 1 : 0; + d += s * (long) s * ((3 * rx) ^ ry); + if (ry == 0) { + if (rx == 1) { + x = s - 1 - x; + y = s - 1 - y; + } + long t = x; + x = y; + y = t; + } + } + return d; + } + + private static long[] hilbertDToXY(int order, long d) { + long x = 0, y = 0; + for (long s = 1; s < (1L << order); s <<= 1) { + long rx = (d / 2) & 1; + long ry = (d ^ rx) & 1; + if (ry == 0) { + if (rx == 1) { + x = s - 1 - x; + y = s - 1 - y; + } + long t = x; + x = y; + y = t; + } + x += s * rx; + y += s * ry; + d >>= 2; + } + return new long[]{x, y}; + } + + // ========================================================================= + // Header parsing + // ========================================================================= + + private Header readHeader() throws IOException { + byte[] buf = readBytes(0, HEADER_LEN); + ByteBuffer bb = ByteBuffer.wrap(buf).order(ByteOrder.LITTLE_ENDIAN); + + byte[] magic = new byte[7]; + bb.get(magic); + if (!Arrays.equals(magic, "PMTiles".getBytes())) + throw new IOException("Not a PMTiles file"); + + Header h = new Header(); + h.version = bb.get() & 0xFF; + if (h.version != 3) + throw new IOException("Only PMTiles v3 supported, got v" + h.version); + + h.rootDirOffset = bb.getLong(); + h.rootDirLength = bb.getLong(); + h.metadataOffset = bb.getLong(); + h.metadataLength = bb.getLong(); + h.leafDirsOffset = bb.getLong(); + h.leafDirsLength = bb.getLong(); + h.tileDataOffset = bb.getLong(); + h.tileDataLength = bb.getLong(); + h.numAddressedTiles = bb.getLong(); + h.numTileEntries = bb.getLong(); + h.numTileContents = bb.getLong(); + + h.clustered = (bb.get() & 0xFF) == 1; + h.internalCompression = bb.get() & 0xFF; + h.tileCompression = bb.get() & 0xFF; + h.tileType = bb.get() & 0xFF; + h.minZoom = bb.get() & 0xFF; + h.maxZoom = bb.get() & 0xFF; + h.minLonE7 = bb.getInt(); + h.minLatE7 = bb.getInt(); + h.maxLonE7 = bb.getInt(); + h.maxLatE7 = bb.getInt(); + h.centerZoom = bb.get() & 0xFF; + h.centerLonE7 = bb.getInt(); + h.centerLatE7 = bb.getInt(); + return h; + } + + // ========================================================================= + // Directory parsing + // ========================================================================= + + private List readLeafDirectory(long offset, long length) throws IOException { + List cached = leafCache.get(offset); + if (cached != null) return cached; + List entries = readDirectory(header.leafDirsOffset + offset, length); + leafCache.put(offset, entries); + return entries; + } + + private List readDirectory(long offset, long length) throws IOException { + byte[] raw = readBytes(offset, (int) length); + if (header.internalCompression == COMPRESS_GZIP) { + raw = gunzip(raw); + } + return deserializeEntries(raw); + } + + private static List deserializeEntries(byte[] data) { + int[] pos = {0}; + int numEntries = (int) readVarint(data, pos); + if (numEntries == 0) return Collections.emptyList(); + + long[] tileIds = new long[numEntries]; + long lastId = 0; + for (int i = 0; i < numEntries; i++) { + lastId += readVarint(data, pos); + tileIds[i] = lastId; + } + + long[] runLengths = new long[numEntries]; + for (int i = 0; i < numEntries; i++) runLengths[i] = readVarint(data, pos); + + long[] lengths = new long[numEntries]; + for (int i = 0; i < numEntries; i++) lengths[i] = readVarint(data, pos); + + // offsets are stored as offset + 1 to make the offset==0 is available and means "continue from previous entry" + long[] offsets = new long[numEntries]; + for (int i = 0; i < numEntries; i++) { + long v = readVarint(data, pos); + if (v == 0 && i > 0) offsets[i] = offsets[i - 1] + lengths[i - 1]; + else if (v < 1) // v==0 only valid if there is a previous entry + throw new IllegalStateException("Invalid directory entry offset at index " + i + ": varint value " + v); + else + offsets[i] = v - 1; + } + + List entries = new ArrayList<>(numEntries); + for (int i = 0; i < numEntries; i++) + entries.add(new DirEntry(tileIds[i], runLengths[i], offsets[i], lengths[i])); + return entries; + } + + // ========================================================================= + // I/O helpers + // ========================================================================= + + private byte[] readBytes(long offset, int length) throws IOException { + ByteBuffer buf = ByteBuffer.allocate(length); + int read = 0; + while (read < length) { + int n = channel.read(buf, offset + read); + if (n < 0) break; + read += n; + } + if (read < length) + throw new IOException("Short read at offset " + offset + ": expected " + length + " bytes but got " + read); + return buf.array(); + } + + static byte[] gunzip(byte[] data) throws IOException { + try (GZIPInputStream gis = new GZIPInputStream(new ByteArrayInputStream(data)); + ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length * 4)) { + byte[] buf = new byte[4096]; + int n; + while ((n = gis.read(buf)) >= 0) bos.write(buf, 0, n); + return bos.toByteArray(); + } + } + + private static long readVarint(byte[] data, int[] pos) { + long result = 0; + int shift = 0; + while (pos[0] < data.length) { + int b = data[pos[0]++] & 0xFF; + result |= (long) (b & 0x7F) << shift; + if ((b & 0x80) == 0) break; + shift += 7; + } + return result; + } + + // ========================================================================= + // Internal types + // ========================================================================= + + static class Header { + int version; + long rootDirOffset, rootDirLength; + long metadataOffset, metadataLength; + long leafDirsOffset, leafDirsLength; + long tileDataOffset, tileDataLength; + long numAddressedTiles, numTileEntries, numTileContents; + boolean clustered; + int internalCompression, tileCompression, tileType; + int minZoom, maxZoom; + int minLonE7, minLatE7, maxLonE7, maxLatE7; + int centerZoom, centerLonE7, centerLatE7; + } + + static class DirEntry { + final long tileId, runLength, offset, length; + + DirEntry(long tileId, long runLength, long offset, long length) { + this.tileId = tileId; + this.runLength = runLength; + this.offset = offset; + this.length = length; + } + } +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/PackedTileCodec.java b/core/src/main/java/com/graphhopper/reader/dem/PackedTileCodec.java new file mode 100644 index 00000000000..cc3db7813eb --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/PackedTileCodec.java @@ -0,0 +1,220 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * This class stores a square tile (default 256x256) in a compressed block format (16x16). + * It is not necessary to decompress before reading as only the type and base value is necessary to + * read a pixel value. Currently only used for pmtiles but could be used for srtm or cgiar too. + */ +final class PackedTileCodec { + /** + * Block type: every elevation sample in this block is 0. + * Block payload size: 1 byte (type only). + */ + static final int TYPE_SEA = 0; + /** + * Block type: every elevation sample in this block has the same int16 value. + * Block payload size: 1 byte type + 2 bytes value. + */ + static final int TYPE_CONST = 1; + /** + * Block type: int16 base value + unsigned byte delta per pixel. + * For each sample: value = base + delta, delta in [0, 255]. + * Block payload size: 1 byte type + 2 bytes base + N bytes deltas. + */ + static final int TYPE_DELTA8 = 2; + /** + * Block type: uncompressed int16 sample values (little-endian), row-major. + * Block payload size: 1 byte type + N*2 bytes. + */ + static final int TYPE_RAW16 = 3; + + static final int DEFAULT_BLOCK_SIZE = 16; + + private static final int VERSION = 1; + // 1-byte header marker/version. + private static final int HEADER_BYTE = VERSION; + + /** + * Packed .tile format (little-endian): + *
+     * byte[0]       version
+     * byte[1]       blockSize (currently 16)
+     * u16[2..3]     tileSize (e.g. 256)
+     * u32[]         (blockCount + 1) block offsets table, relative to payload start
+     * bytes[]       block payloads concatenated
+     * 
+ * The extra final offset allows computing each block length as offsets[i+1]-offsets[i]. + * blockCount is derived as (tileSize / blockSize)^2. + */ + record PackedHeader(int tileSize, int blockSize, int blocksPerAxis, int[] blockOffsets, + int payloadOffset) { + } + + private PackedTileCodec() { + } + + static boolean isPackedTile(ByteBuffer data) { + return data.remaining() >= 1 && (data.get(0) & 0xFF) == HEADER_BYTE; + } + + static PackedHeader readPackedHeader(ByteBuffer data) { + ByteBuffer dup = data.duplicate().order(ByteOrder.LITTLE_ENDIAN); + if (!isPackedTile(dup)) + throw new IllegalArgumentException("Not a packed GH elevation tile"); + + int version = dup.get(0) & 0xFF; + if (version != VERSION) + throw new IllegalArgumentException("Unsupported packed tile version: " + version + ", expected " + VERSION); + + int blockSize = dup.get(1) & 0xFF; + int tileSize = dup.getShort(2) & 0xFFFF; + if (blockSize <= 0) + throw new IllegalArgumentException("Invalid block size: " + blockSize); + if (tileSize <= 0) + throw new IllegalArgumentException("Invalid tile size: " + tileSize); + if (tileSize % blockSize != 0) + throw new IllegalArgumentException("tileSize must be a multiple of blockSize, got tileSize=" + + tileSize + ", blockSize=" + blockSize); + int blocksPerAxis = tileSize / blockSize; + int blockCount = blocksPerAxis * blocksPerAxis; + int offsetTablePos = 4; + int[] blockOffsets = new int[blockCount + 1]; + for (int i = 0; i < blockOffsets.length; i++) { + blockOffsets[i] = dup.getInt(offsetTablePos + i * 4); + } + int payloadOffset = offsetTablePos + blockOffsets.length * 4; + return new PackedHeader(tileSize, blockSize, blocksPerAxis, blockOffsets, payloadOffset); + } + + static byte[] encodePacked(byte[] rawLeShorts, int tileSize, int blockSize) { + if (rawLeShorts.length != tileSize * tileSize * 2) + throw new IllegalArgumentException("Raw tile size mismatch"); + if (tileSize % blockSize != 0) + throw new IllegalArgumentException("tileSize must be a multiple of blockSize, got tileSize=" + + tileSize + ", blockSize=" + blockSize); + + int blocksPerAxis = tileSize / blockSize; + int blockCount = blocksPerAxis * blocksPerAxis; + + byte[][] blockPayload = new byte[blockCount][]; + int[] offsets = new int[blockCount + 1]; + + int offset = 0; + int i = 0; + for (int by = 0; by < blocksPerAxis; by++) { + for (int bx = 0; bx < blocksPerAxis; bx++) { + int x0 = bx * blockSize; + int y0 = by * blockSize; + byte[] block = encodeBlock(rawLeShorts, tileSize, x0, y0, blockSize, blockSize); + blockPayload[i] = block; + offsets[i] = offset; + offset += block.length; + i++; + } + } + offsets[blockCount] = offset; + + int headerLen = 4 + (blockCount + 1) * 4; + ByteBuffer header = ByteBuffer.allocate(headerLen).order(ByteOrder.LITTLE_ENDIAN); + header.put((byte) HEADER_BYTE); + header.put((byte) blockSize); + header.putShort((short) tileSize); + for (int v : offsets) header.putInt(v); + + ByteArrayOutputStream out = new ByteArrayOutputStream(headerLen + offset); + out.write(header.array(), 0, header.array().length); + for (byte[] block : blockPayload) out.write(block, 0, block.length); + return out.toByteArray(); + } + + private static byte[] encodeBlock(byte[] raw, int tileSize, int x0, int y0, int bw, int bh) { + int len = bw * bh; + short[] vals = new short[len]; + + boolean allZero = true; + boolean allSame = true; + short first = 0; + short min = Short.MAX_VALUE; + short max = Short.MIN_VALUE; + + int p = 0; + for (int y = 0; y < bh; y++) { + int row = y0 + y; + for (int x = 0; x < bw; x++) { + int col = x0 + x; + short v = readLeShort(raw, (row * tileSize + col) * 2); + vals[p++] = v; + if (p == 1) first = v; + if (v != 0) allZero = false; + if (v != first) allSame = false; + if (v < min) min = v; + if (v > max) max = v; + } + } + + if (allZero) { + return new byte[]{(byte) TYPE_SEA}; + } + if (allSame) { + ByteBuffer bb = ByteBuffer.allocate(3).order(ByteOrder.LITTLE_ENDIAN); + bb.put((byte) TYPE_CONST); + bb.putShort(first); + return bb.array(); + } + + // DELTA8 can only be used when all values can be represented as: + // value = base + delta, with unsigned 8-bit delta in [0, 255]. + int range = max - min; + if (range <= 255) { + int base = min; + ByteBuffer bb = ByteBuffer.allocate(3 + len).order(ByteOrder.LITTLE_ENDIAN); + bb.put((byte) TYPE_DELTA8); + bb.putShort((short) base); + for (short v : vals) { + int d = v - base; + if (d < 0 || d > 255) { + return encodeRaw16(vals); + } + bb.put((byte) d); + } + return bb.array(); + } + + return encodeRaw16(vals); + } + + private static byte[] encodeRaw16(short[] vals) { + ByteBuffer bb = ByteBuffer.allocate(1 + vals.length * 2).order(ByteOrder.LITTLE_ENDIAN); + bb.put((byte) TYPE_RAW16); + for (short v : vals) bb.putShort(v); + return bb.array(); + } + + // Little Endian + private static short readLeShort(byte[] data, int offset) { + int lo = data[offset] & 0xFF; + int hi = data[offset + 1] & 0xFF; + return (short) (lo | (hi << 8)); + } +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java b/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java index 6c043aa5336..42dbad2f62d 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/SRTMProvider.java @@ -21,6 +21,7 @@ import com.graphhopper.util.Helper; import java.io.*; +import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; /** @@ -50,8 +51,6 @@ public SRTMProvider(String cacheDir) { 60, 1201 ); - // move to explicit calls? - init(); } public static void main(String[] args) throws IOException { @@ -85,7 +84,9 @@ public static void main(String[] args) throws IOException { * The URLs are a bit ugly and so we need to find out which area name a certain lat,lon * coordinate has. */ - private SRTMProvider init() { + @Override + public ElevationProvider init() { + super.init(); try { String strs[] = {"Africa", "Australia", "Eurasia", "Islands", "North_America", "South_America"}; for (String str : strs) { @@ -119,18 +120,17 @@ public String toString() { @Override byte[] readFile(File file) throws IOException { InputStream is = new FileInputStream(file); - ZipInputStream zis = new ZipInputStream(is); - zis.getNextEntry(); - BufferedInputStream buff = new BufferedInputStream(zis); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] buffer = new byte[0xFFFF]; - int len; - while ((len = buff.read(buffer)) > 0) { - os.write(buffer, 0, len); + BufferedInputStream buff = new BufferedInputStream(is, 8 * 1024); + try (ZipInputStream zis = new ZipInputStream(buff)) { + ZipEntry entry = zis.getNextEntry(); + if (entry == null) { + throw new RuntimeException("No entry found in zip file " + file); + } + int bufferSize = (int) Math.max(entry.getSize(), 64 * 1024); + ByteArrayOutputStream os = new ByteArrayOutputStream(bufferSize); + zis.transferTo(os); + return os.toByteArray(); } - os.flush(); - Helper.close(buff); - return os.toByteArray(); } @Override @@ -169,4 +169,4 @@ String getFileName(double lat, double lon) { String getDownloadURL(double lat, double lon) { return getFileName(lat, lon) + ".hgt.zip"; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/SkadiProvider.java b/core/src/main/java/com/graphhopper/reader/dem/SkadiProvider.java index ce42322a796..0c21541f622 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/SkadiProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/SkadiProvider.java @@ -80,15 +80,10 @@ public static void main(String[] args) throws IOException { @Override byte[] readFile(File file) throws IOException { InputStream is = new FileInputStream(file); - GZIPInputStream gzis = new GZIPInputStream(is); - BufferedInputStream buff = new BufferedInputStream(gzis); - ByteArrayOutputStream os = new ByteArrayOutputStream(); - byte[] buffer = new byte[0xFFFF]; - int len; - while ((len = buff.read(buffer)) > 0) { - os.write(buffer, 0, len); - } - os.flush(); + GZIPInputStream gzis = new GZIPInputStream(is, 8 * 1024); + BufferedInputStream buff = new BufferedInputStream(gzis, 16 * 1024); + ByteArrayOutputStream os = new ByteArrayOutputStream(64 * 1024); + buff.transferTo(os); close(buff); return os.toByteArray(); } diff --git a/core/src/main/java/com/graphhopper/reader/dem/SonnyProvider.java b/core/src/main/java/com/graphhopper/reader/dem/SonnyProvider.java new file mode 100644 index 00000000000..2f77f582d5d --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/dem/SonnyProvider.java @@ -0,0 +1,133 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import java.io.*; + +import static com.graphhopper.util.Helper.close; + +/** + * Sonny's LiDAR Digital Terrain Models contains elevation data for Europe with 1 arc second (~30m) accuracy. + * The description is available at https://sonny.4lima.de/. Unfortunately the data is provided on a Google Drive + * https://drive.google.com/drive/folders/0BxphPoRgwhnoWkRoTFhMbTM3RDA?resourcekey=0-wRe5bWl96pwvQ9tAfI9cQg + * Therefore, the data is not available via a direct URL and you have to download it manually. After downloading, + * the data has to be unzipped and placed in the cache directory. The cache directory is expected to contain DTM + * data files with the naming convention like "N49E011.hgt" for the area around 49°N and 11°E. + *

+ * Please note that the data cannot be used for public hosting or redistribution due to the terms of use of the data. See + * https://github.com/graphhopper/graphhopper/issues/2823 + *

+ * + * @author ratrun + */ +public class SonnyProvider extends AbstractSRTMElevationProvider { + + public SonnyProvider() { + this(""); + } + + public SonnyProvider(String cacheDir) { + super("https://drive.google.com/drive/folders/0BxphPoRgwhnoWkRoTFhMbTM3RDA?resourcekey=0-wRe5bWl96pwvQ9tAfI9cQg/", // This base URL cannot be used, as the data is not available via a direct URL + cacheDir.isEmpty() ? "/tmp/sonny" : cacheDir, + "GraphHopper SonnyReader", + -56, + 90, + 3601 + ); + } + + public static void main(String[] args) throws IOException { + SonnyProvider provider = new SonnyProvider(); + // 338 + System.out.println(provider.getEle(49.949784, 11.57517)); + // 462 + System.out.println(provider.getEle(49.968668, 11.575127)); + // 462 + System.out.println(provider.getEle(49.968682, 11.574842)); + // 982 + System.out.println(provider.getEle(47.468668, 14.575127)); + // 1094 + System.out.println(provider.getEle(47.467753, 14.573911)); + // 1925 + System.out.println(provider.getEle(46.468835, 12.578777)); + // 834 + System.out.println(provider.getEle(48.469123, 9.576393)); + // Out of area + try { + System.out.println(provider.getEle(37.5969196, 23.0706507)); + } catch (Exception e) { + System.out.println("Error: Out of area! " + e.getMessage()); + } + + } + + @Override + byte[] readFile(File file) throws IOException { + InputStream is = new FileInputStream(file); + BufferedInputStream buff = new BufferedInputStream(is); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + byte[] buffer = new byte[0xFFFF]; + int len; + while ((len = buff.read(buffer)) > 0) { + os.write(buffer, 0, len); + } + os.flush(); + close(buff); + return os.toByteArray(); + } + + @Override + String getFileName(double lat, double lon) { + String str = ""; + + int minLat = Math.abs(down(lat)); + int minLon = Math.abs(down(lon)); + + if (lat >= 0) + str += "N"; + else + str += "S"; + + if (minLat < 10) + str += "0"; + str += minLat; + + if (lon >= 0) + str += "E"; + else + str += "W"; + + if (minLon < 10) + str += "0"; + if (minLon < 100) + str += "0"; + str += minLon; + return str; + } + + @Override + String getDownloadURL(double lat, double lon) { + return getFileName(lat, lon) + ".hgt"; + } + + @Override + public String toString() { + return "sonny"; + } + +} diff --git a/core/src/main/java/com/graphhopper/reader/dem/TileBasedElevationProvider.java b/core/src/main/java/com/graphhopper/reader/dem/TileBasedElevationProvider.java index c1959392be9..374da4c891f 100644 --- a/core/src/main/java/com/graphhopper/reader/dem/TileBasedElevationProvider.java +++ b/core/src/main/java/com/graphhopper/reader/dem/TileBasedElevationProvider.java @@ -35,7 +35,8 @@ public abstract class TileBasedElevationProvider implements ElevationProvider { final Logger logger = LoggerFactory.getLogger(getClass()); Downloader downloader; - final File cacheDir; + File cacheDir; + final String cacheDirString; String baseUrl; Directory dir; DAType daType = DAType.MMAP; @@ -44,6 +45,11 @@ public abstract class TileBasedElevationProvider implements ElevationProvider { long sleep = 2000; protected TileBasedElevationProvider(String cacheDirString) { + this.cacheDirString = cacheDirString; + } + + @Override + public ElevationProvider init() { File cacheDir = new File(cacheDirString); if (cacheDir.exists() && !cacheDir.isDirectory()) throw new IllegalArgumentException("Cache path has to be a directory"); @@ -52,6 +58,7 @@ protected TileBasedElevationProvider(String cacheDirString) { } catch (IOException ex) { throw new RuntimeException(ex); } + return this; } /** diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMInput.java b/core/src/main/java/com/graphhopper/reader/osm/OSMInput.java index 3e109b06b5c..d923f489064 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMInput.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMInput.java @@ -18,11 +18,100 @@ package com.graphhopper.reader.osm; import com.graphhopper.reader.ReaderElement; +import com.graphhopper.reader.osm.pbf.PbfReader; import javax.xml.stream.XMLStreamException; +import java.io.*; +import java.lang.reflect.Constructor; +import java.util.zip.GZIPInputStream; +import java.util.zip.ZipInputStream; +/** + * Interface for reading OSM data from various file formats. + */ public interface OSMInput extends AutoCloseable { ReaderElement getNext() throws XMLStreamException; - int getUnprocessedElements(); + /** + * Opens an OSM file, automatically detecting the format (PBF or XML) based on file contents. + * + * @param file the OSM file to open + * @param workerThreads number of worker threads for PBF parsing (ignored for XML) + * @param skipOptions options to skip certain element types during parsing + * @return an OSMInput instance for the detected format + */ + static OSMInput open(File file, int workerThreads, SkipOptions skipOptions) throws IOException, XMLStreamException { + DecodedInput decoded = decode(file); + if (decoded.isBinary) { + return new PbfReader(decoded.inputStream, workerThreads, skipOptions).start(); + } else { + return new OSMXmlInput(decoded.inputStream).open(); + } + } + + private static DecodedInput decode(File file) throws IOException { + final String name = file.getName(); + + InputStream ips; + try { + ips = new BufferedInputStream(new FileInputStream(file), 50000); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + ips.mark(10); + + // check file header + byte[] header = new byte[6]; + if (ips.read(header) < 0) + throw new IllegalArgumentException("Input file is not of valid type " + file.getPath()); + + if (header[0] == 31 && header[1] == -117) { + // GZIP + ips.reset(); + return new DecodedInput(new GZIPInputStream(ips, 50000), false); + } else if (header[0] == 0 && header[1] == 0 && header[2] == 0 + && header[4] == 10 && header[5] == 9 + && (header[3] == 13 || header[3] == 14)) { + // PBF + ips.reset(); + return new DecodedInput(ips, true); + } else if (header[0] == 'P' && header[1] == 'K') { + // ZIP + ips.reset(); + ZipInputStream zip = new ZipInputStream(ips); + zip.getNextEntry(); + return new DecodedInput(zip, false); + } else if (name.endsWith(".osm") || name.endsWith(".xml")) { + // Plain XML + ips.reset(); + return new DecodedInput(ips, false); + } else if (name.endsWith(".bz2") || name.endsWith(".bzip2")) { + // BZIP2 - requires optional dependency + String clName = "org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream"; + try { + @SuppressWarnings("unchecked") + Class clazz = (Class) Class.forName(clName); + ips.reset(); + Constructor ctor = clazz.getConstructor(InputStream.class, boolean.class); + return new DecodedInput(ctor.newInstance(ips, true), false); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot instantiate " + clName, e); + } + } else { + throw new IllegalArgumentException("Input file is not of valid type " + file.getPath()); + } + } + + /** + * Helper class to return both the decoded input stream and whether it's binary (PBF) format. + */ + class DecodedInput { + final InputStream inputStream; + final boolean isBinary; + + DecodedInput(InputStream inputStream, boolean isBinary) { + this.inputStream = inputStream; + this.isBinary = isBinary; + } + } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java b/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java deleted file mode 100644 index 1741ee5cdaf..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMInputFile.java +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm; - -import com.graphhopper.reader.ReaderElement; -import com.graphhopper.reader.osm.pbf.PbfReader; -import com.graphhopper.reader.osm.pbf.Sink; - -import javax.xml.stream.XMLInputFactory; -import javax.xml.stream.XMLStreamConstants; -import javax.xml.stream.XMLStreamException; -import javax.xml.stream.XMLStreamReader; -import java.io.*; -import java.lang.reflect.Constructor; -import java.util.ArrayDeque; -import java.util.Queue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.TimeUnit; -import java.util.zip.GZIPInputStream; -import java.util.zip.ZipInputStream; - -/** - * A readable OSM file. - *

- * - * @author Nop - */ -public class OSMInputFile implements Sink, OSMInput { - private static final int MAX_BATCH_SIZE = 1_000; - private final InputStream bis; - private final BlockingQueue itemQueue; - private final Queue itemBatch; - private boolean eof; - // for xml parsing - private XMLStreamReader xmlParser; - // for pbf parsing - private boolean binary = false; - private PbfReader pbfReader; - private Thread pbfReaderThread; - private boolean hasIncomingData; - private int workerThreads = -1; - private SkipOptions skipOptions = SkipOptions.none(); - private OSMFileHeader fileheader; - - public OSMInputFile(File file) throws IOException { - bis = decode(file); - itemQueue = new LinkedBlockingQueue<>(50_000); - itemBatch = new ArrayDeque<>(MAX_BATCH_SIZE); - } - - public OSMInputFile open() throws XMLStreamException { - if (binary) { - openPBFReader(bis); - } else { - openXMLStream(bis); - } - return this; - } - - /** - * Currently only for pbf format. Default is number of cores. - */ - public OSMInputFile setWorkerThreads(int threads) { - workerThreads = threads; - return this; - } - - /** - * Use this to prevent the creation of OSM nodes, ways and/or relations to speed up the file reading process. - * This will only affect the reading of pbf files. - */ - public OSMInputFile setSkipOptions(SkipOptions skipOptions) { - this.skipOptions = skipOptions; - return this; - } - - @SuppressWarnings("unchecked") - private InputStream decode(File file) throws IOException { - final String name = file.getName(); - - InputStream ips = null; - try { - ips = new BufferedInputStream(new FileInputStream(file), 50000); - } catch (FileNotFoundException e) { - throw new RuntimeException(e); - } - ips.mark(10); - - // check file header - byte header[] = new byte[6]; - if (ips.read(header) < 0) - throw new IllegalArgumentException("Input file is not of valid type " + file.getPath()); - - /* can parse bz2 directly with additional lib - if (header[0] == 'B' && header[1] == 'Z') - { - return new CBZip2InputStream(ips); - } - */ - if (header[0] == 31 && header[1] == -117) { - ips.reset(); - return new GZIPInputStream(ips, 50000); - } else if (header[0] == 0 && header[1] == 0 && header[2] == 0 - && header[4] == 10 && header[5] == 9 - && (header[3] == 13 || header[3] == 14)) { - ips.reset(); - binary = true; - return ips; - } else if (header[0] == 'P' && header[1] == 'K') { - ips.reset(); - ZipInputStream zip = new ZipInputStream(ips); - zip.getNextEntry(); - - return zip; - } else if (name.endsWith(".osm") || name.endsWith(".xml")) { - ips.reset(); - return ips; - } else if (name.endsWith(".bz2") || name.endsWith(".bzip2")) { - String clName = "org.apache.commons.compress.compressors.bzip2.BZip2CompressorInputStream"; - try { - Class clazz = Class.forName(clName); - ips.reset(); - Constructor ctor = clazz.getConstructor(InputStream.class, boolean.class); - return ctor.newInstance(ips, true); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot instantiate " + clName, e); - } - } else { - throw new IllegalArgumentException("Input file is not of valid type " + file.getPath()); - } - } - - private void openXMLStream(InputStream in) - throws XMLStreamException { - XMLInputFactory factory = XMLInputFactory.newInstance(); - xmlParser = factory.createXMLStreamReader(in, "UTF-8"); - - int event = xmlParser.next(); - if (event != XMLStreamConstants.START_ELEMENT || !xmlParser.getLocalName().equalsIgnoreCase("osm")) { - throw new IllegalArgumentException("File is not a valid OSM stream"); - } - // See https://wiki.openstreetmap.org/wiki/PBF_Format#Definition_of_the_OSMHeader_fileblock - String timestamp = xmlParser.getAttributeValue(null, "osmosis_replication_timestamp"); - - if (timestamp == null) - timestamp = xmlParser.getAttributeValue(null, "timestamp"); - - if (timestamp != null) { - try { - fileheader = new OSMFileHeader(); - fileheader.setTag("timestamp", timestamp); - } catch (Exception ex) { - } - } - - eof = false; - } - - @Override - public ReaderElement getNext() throws XMLStreamException { - if (eof) - throw new IllegalStateException("EOF reached"); - - ReaderElement item; - if (binary) - item = getNextPBF(); - else - item = getNextXML(); - - if (item != null) - return item; - - eof = true; - return null; - } - - private ReaderElement getNextXML() throws XMLStreamException { - - int event = xmlParser.next(); - if (fileheader != null) { - ReaderElement copyfileheader = fileheader; - fileheader = null; - return copyfileheader; - } - - while (event != XMLStreamConstants.END_DOCUMENT) { - if (event == XMLStreamConstants.START_ELEMENT) { - String idStr = xmlParser.getAttributeValue(null, "id"); - if (idStr != null) { - String name = xmlParser.getLocalName(); - long id = 0; - switch (name.charAt(0)) { - case 'n': - // note vs. node - if ("node".equals(name)) { - id = Long.parseLong(idStr); - return OSMXMLHelper.createNode(id, xmlParser); - } - break; - - case 'w': { - id = Long.parseLong(idStr); - return OSMXMLHelper.createWay(id, xmlParser); - } - case 'r': - id = Long.parseLong(idStr); - return OSMXMLHelper.createRelation(id, xmlParser); - } - } - } - event = xmlParser.next(); - } - xmlParser.close(); - return null; - } - - public boolean isEOF() { - return eof; - } - - @Override - public void close() throws IOException { - try { - if (binary) - pbfReader.close(); - else - xmlParser.close(); - } catch (XMLStreamException ex) { - throw new IOException(ex); - } finally { - eof = true; - bis.close(); - // if exception happened on OSMInputFile-thread we need to shutdown the pbf handling - if (pbfReaderThread != null && pbfReaderThread.isAlive()) - pbfReaderThread.interrupt(); - } - } - - private void openPBFReader(InputStream stream) { - hasIncomingData = true; - if (workerThreads <= 0) - workerThreads = 1; - - pbfReader = new PbfReader(stream, this, workerThreads, skipOptions); - pbfReaderThread = new Thread(pbfReader, "PBF Reader"); - pbfReaderThread.start(); - } - - @Override - public void process(ReaderElement item) { - try { - // blocks if full - itemQueue.put(item); - } catch (InterruptedException ex) { - throw new RuntimeException(ex); - } - } - - public int getUnprocessedElements() { - return itemQueue.size() + itemBatch.size(); - } - - @Override - public void complete() { - hasIncomingData = false; - } - - private ReaderElement getNextPBF() { - while (itemBatch.isEmpty()) { - if (!hasIncomingData && itemQueue.isEmpty()) { - return null; // signal EOF - } - - if (itemQueue.drainTo(itemBatch, MAX_BATCH_SIZE) == 0) { - try { - ReaderElement element = itemQueue.poll(100, TimeUnit.MILLISECONDS); - if (element != null) { - return element; // short circuit - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - return null; // signal EOF - } - } - } - - return itemBatch.poll(); - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java index 7749ce266de..51453fd3116 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMNodeData.java @@ -25,13 +25,13 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.search.KVStorage; import com.graphhopper.storage.Directory; +import com.graphhopper.util.Helper; import com.graphhopper.util.PointAccess; import com.graphhopper.util.PointList; import com.graphhopper.util.shapes.GHPoint3D; import java.util.Collections; import java.util.Map; -import java.util.function.DoubleSupplier; import java.util.function.LongUnaryOperator; import java.util.stream.Collectors; @@ -59,11 +59,11 @@ class OSMNodeData { static final long INTERMEDIATE_NODE = 1; static final long CONNECTION_NODE = 2; - // this map stores our internal node id for each OSM node + // this map stores our internal node id for each OSM node. + // For tower nodes, the value is a negative id (see towerNodeToId). + // For pillar nodes, the value is a packed lat/lon long (see packLatLon). private final LongLongMap idsByOsmNodeIds; - // here we store node coordinates, separated for pillar and tower nodes - private final PillarInfo pillarNodes; private final PointAccess towerNodes; // this map stores an index for each OSM node we keep the node tags of. a value of -1 means there is no entry yet. @@ -75,7 +75,6 @@ class OSMNodeData { private final LongSet nodesToBeSplit; private int nextTowerId = 0; - private long nextPillarId = 0; // we use negative ids to create artificial OSM node ids private long nextArtificialOSMNodeId = -Long.MAX_VALUE; @@ -83,9 +82,9 @@ public OSMNodeData(PointAccess nodeAccess, Directory directory) { // We use a b-tree that can store as many entries as there are longs. A tree is also more // memory efficient, because there is no waste for empty entries, and it also avoids // allocating big arrays when growing the size. - idsByOsmNodeIds = new GHLongLongBTree(200, 5, EMPTY_NODE); + // 8 bytes per value to hold packed lat/lon for pillar nodes (and negative tower IDs) + idsByOsmNodeIds = new GHLongLongBTree(200, 8, EMPTY_NODE); towerNodes = nodeAccess; - pillarNodes = new PillarInfo(towerNodes.is3D(), directory); nodeTagIndicesByOsmNodeIds = new GHLongLongBTree(200, 4, -1); nodesToBeSplit = new LongScatterSet(); @@ -119,11 +118,7 @@ public static boolean isNodeId(long id) { } public void setOrUpdateNodeType(long osmNodeId, long newNodeType, LongUnaryOperator nodeTypeUpdate) { - long curr = idsByOsmNodeIds.get(osmNodeId); - if (curr == EMPTY_NODE) - idsByOsmNodeIds.put(osmNodeId, newNodeType); - else - idsByOsmNodeIds.put(osmNodeId, nodeTypeUpdate.applyAsLong(curr)); + idsByOsmNodeIds.putOrCompute(osmNodeId, newNodeType, nodeTypeUpdate); } /** @@ -146,25 +141,25 @@ public long getNodeTagCapacity() { /** * Stores the given coordinates for the given OSM node ID, but only if a non-empty node type was set for this - * OSM node ID previously. + * OSM node ID previously. Elevation is not stored here — it is looked up later during edge creation. * * @return the node type this OSM node was associated with before this method was called */ - public long addCoordinatesIfMapped(long osmNodeId, double lat, double lon, DoubleSupplier getEle) { + public long addCoordinatesIfMapped(long osmNodeId, double lat, double lon) { long nodeType = idsByOsmNodeIds.get(osmNodeId); if (nodeType == EMPTY_NODE) return nodeType; else if (nodeType == JUNCTION_NODE || nodeType == CONNECTION_NODE) - addTowerNode(osmNodeId, lat, lon, getEle.getAsDouble()); + addTowerNode(osmNodeId, lat, lon); else if (nodeType == INTERMEDIATE_NODE || nodeType == END_NODE) - addPillarNode(osmNodeId, lat, lon, getEle.getAsDouble()); + addPillarNode(osmNodeId, lat, lon); else throw new IllegalStateException("Unknown node type: " + nodeType + ", or coordinates already set. Possibly duplicate OSM node ID: " + osmNodeId); return nodeType; } - private long addTowerNode(long osmId, double lat, double lon, double ele) { - towerNodes.setNode(nextTowerId, lat, lon, ele); + private long addTowerNode(long osmId, double lat, double lon) { + towerNodes.setNode(nextTowerId, lat, lon, Helper.ELE_UNKNOWN); long id = towerNodeToId(nextTowerId); idsByOsmNodeIds.put(osmId, id); nextTowerId++; @@ -173,14 +168,35 @@ private long addTowerNode(long osmId, double lat, double lon, double ele) { return id; } - private long addPillarNode(long osmId, double lat, double lon, double ele) { - long id = pillarNodeToId(nextPillarId); - if (id > idsByOsmNodeIds.getMaxValue()) - throw new IllegalStateException("id for pillar node cannot be bigger than " + idsByOsmNodeIds.getMaxValue()); + /** + * Packs lat/lon into a single positive long. The offsets ensure the packed value is always + * positive and always > 2^32, which avoids collision with tower IDs (negative) and special + * markers (-2 to 2). + */ + static long packLatLon(double lat, double lon) { + // degreeToInt(lat) is in [-900_000_000, 900_000_000], offset to [1, 1_800_000_001] (fits 31 bits) + long latUnsigned = Helper.degreeToInt(lat) + 900_000_001L; + // degreeToInt(lon) is in [-1_800_000_000, 1_800_000_000], offset to [1, 3_600_000_001] (fits 32 bits) + // +1 not really necessary but is there for symmetry + long lonUnsigned = Helper.degreeToInt(lon) + 1_800_000_001L; + return (latUnsigned << 32) | lonUnsigned; + } - pillarNodes.setNode(nextPillarId, lat, lon, ele); + static double unpackLat(long packed) { + // latUnsigned fits in 31 bits, so (int) cast is safe + int latInt = (int) (packed >>> 32) - 900_000_001; + return Helper.intToDegree(latInt); + } + + static double unpackLon(long packed) { + // lonUnsigned can exceed Integer.MAX_VALUE, so subtract in long space first + int lonInt = (int) ((packed & 0xFFFFFFFFL) - 1_800_000_001L); + return Helper.intToDegree(lonInt); + } + + private long addPillarNode(long osmId, double lat, double lon) { + long id = packLatLon(lat, lon); idsByOsmNodeIds.put(osmId, id); - nextPillarId++; return id; } @@ -194,85 +210,76 @@ SegmentNode addCopyOfNode(SegmentNode node) { if (point == null) throw new IllegalStateException("Cannot copy node : " + node.osmNodeId + ", because it is missing"); final long newOsmId = nextArtificialOSMNodeId++; - if (idsByOsmNodeIds.put(newOsmId, INTERMEDIATE_NODE) != EMPTY_NODE) + long id = packLatLon(point.getLat(), point.getLon()); + if (idsByOsmNodeIds.put(newOsmId, id) != EMPTY_NODE) throw new IllegalStateException("Artificial osm node id already exists: " + newOsmId); - long id = addPillarNode(newOsmId, point.getLat(), point.getLon(), point.getEle()); return new SegmentNode(newOsmId, id, node.tags); } long convertPillarToTowerNode(long id, long osmNodeId) { if (!isPillarNode(id)) throw new IllegalArgumentException("Not a pillar node: " + id); - long pillar = idToPillarNode(id); - double lat = pillarNodes.getLat(pillar); - double lon = pillarNodes.getLon(pillar); - double ele = pillarNodes.getEle(pillar); - if (lat == Double.MAX_VALUE || lon == Double.MAX_VALUE) + // Check if already converted: look up current value in BTree + long current = idsByOsmNodeIds.get(osmNodeId); + if (isTowerNode(current)) throw new IllegalStateException("Pillar node was already converted to tower node: " + id); - - pillarNodes.setNode(pillar, Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); - return addTowerNode(osmNodeId, lat, lon, ele); + double lat = unpackLat(id); + double lon = unpackLon(id); + return addTowerNode(osmNodeId, lat, lon); } public GHPoint3D getCoordinates(long id) { if (isTowerNode(id)) { int tower = idToTowerNode(id); - return towerNodes.is3D() - ? new GHPoint3D(towerNodes.getLat(tower), towerNodes.getLon(tower), towerNodes.getEle(tower)) - : new GHPoint3D(towerNodes.getLat(tower), towerNodes.getLon(tower), Double.NaN); + return new GHPoint3D(towerNodes.getLat(tower), towerNodes.getLon(tower), Double.NaN); } else if (isPillarNode(id)) { - long pillar = idToPillarNode(id); - return pillarNodes.is3D() - ? new GHPoint3D(pillarNodes.getLat(pillar), pillarNodes.getLon(pillar), pillarNodes.getEle(pillar)) - : new GHPoint3D(pillarNodes.getLat(pillar), pillarNodes.getLon(pillar), Double.NaN); + return new GHPoint3D(unpackLat(id), unpackLon(id), Double.NaN); } else return null; } public void addCoordinatesToPointList(long id, PointList pointList) { double lat, lon; - double ele = Double.NaN; if (isTowerNode(id)) { int tower = idToTowerNode(id); lat = towerNodes.getLat(tower); lon = towerNodes.getLon(tower); - if (towerNodes.is3D()) - ele = towerNodes.getEle(tower); } else if (isPillarNode(id)) { - long pillar = idToPillarNode(id); - lat = pillarNodes.getLat(pillar); - lon = pillarNodes.getLon(pillar); - if (pillarNodes.is3D()) - ele = pillarNodes.getEle(pillar); + lat = unpackLat(id); + lon = unpackLon(id); } else throw new IllegalArgumentException(); - pointList.add(lat, lon, ele); + // elevation is NaN — filled in later during edge creation + pointList.add(lat, lon, Double.NaN); } public void setTags(ReaderNode node) { int tagIndex = Math.toIntExact(nodeTagIndicesByOsmNodeIds.get(node.getId())); if (tagIndex == -1) { - long pointer = nodeKVStorage.add(node.getTags().entrySet().stream().map(m -> new KVStorage.KeyValue(m.getKey(), - m.getValue() instanceof String ? KVStorage.cutString((String) m.getValue()) : m.getValue())). - collect(Collectors.toList())); - if (pointer > Integer.MAX_VALUE) + long pointer = nodeKVStorage.add(node.getTags().entrySet().stream().collect( + Collectors.toMap(Map.Entry::getKey, // same key + e -> new KVStorage.KValue(e.getValue() instanceof String ? KVStorage.cutString((String) e.getValue()) : e.getValue())))); + // Shift right to use 4x more address space (pointers are 4-byte aligned) + long shiftedPointer = pointer >> KVStorage.ALIGNMENT_SHIFT; + if (shiftedPointer > Integer.MAX_VALUE) throw new IllegalStateException("Too many key value pairs are stored in node tags, was " + pointer); - nodeTagIndicesByOsmNodeIds.put(node.getId(), (int) pointer); + nodeTagIndicesByOsmNodeIds.put(node.getId(), (int) shiftedPointer); } else { throw new IllegalStateException("Cannot add tags twice, duplicate node OSM ID: " + node.getId()); } } public Map getTags(long osmNodeId) { - int tagIndex = Math.toIntExact(nodeTagIndicesByOsmNodeIds.get(osmNodeId)); - if (tagIndex < 0) + int shiftedIndex = Math.toIntExact(nodeTagIndicesByOsmNodeIds.get(osmNodeId)); + if (shiftedIndex < 0) return Collections.emptyMap(); + // Shift left to restore the actual byte offset + long tagIndex = (long) shiftedIndex << KVStorage.ALIGNMENT_SHIFT; return nodeKVStorage.getMap(tagIndex); } public void release() { idsByOsmNodeIds.clear(); - pillarNodes.clear(); nodeTagIndicesByOsmNodeIds.clear(); nodeKVStorage.clear(); nodesToBeSplit.clear(); @@ -288,14 +295,6 @@ public int idToTowerNode(long id) { return Math.toIntExact(-id - 3); } - public long pillarNodeToId(long pillarId) { - return pillarId + 3; - } - - public long idToPillarNode(long id) { - return id - 3; - } - public boolean setSplitNode(long osmNodeId) { return nodesToBeSplit.add(osmNodeId); } diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java index 7df36852efb..1a83b8f9ea0 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMReader.java @@ -17,11 +17,8 @@ */ package com.graphhopper.reader.osm; -import com.carrotsearch.hppc.IntIntMap; +import com.carrotsearch.hppc.BitSet; import com.carrotsearch.hppc.LongArrayList; -import com.carrotsearch.hppc.LongHashSet; -import com.carrotsearch.hppc.LongSet; -import com.carrotsearch.hppc.cursors.LongCursor; import com.graphhopper.coll.GHLongLongHashMap; import com.graphhopper.reader.ReaderElement; import com.graphhopper.reader.ReaderNode; @@ -39,8 +36,6 @@ import com.graphhopper.routing.util.CustomArea; import com.graphhopper.routing.util.FerrySpeedCalculator; import com.graphhopper.routing.util.OSMParsers; -import com.graphhopper.routing.util.countryrules.CountryRule; -import com.graphhopper.routing.util.countryrules.CountryRuleFactory; import com.graphhopper.routing.util.parsers.RestrictionSetter; import com.graphhopper.search.KVStorage; import com.graphhopper.storage.BaseGraph; @@ -60,9 +55,10 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static com.graphhopper.search.KVStorage.KeyValue.*; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.util.GHUtility.OSM_WARNING_LOGGER; import static com.graphhopper.util.Helper.nf; +import static com.graphhopper.util.Parameters.Details.*; import static java.util.Collections.emptyList; /** @@ -89,10 +85,9 @@ public class OSMReader { private final RestrictionSetter restrictionSetter; private ElevationProvider eleProvider = ElevationProvider.NOOP; private AreaIndex areaIndex; - private CountryRuleFactory countryRuleFactory = null; private File osmFile; private final RamerDouglasPeucker simplifyAlgo = new RamerDouglasPeucker(); - + private int bugCounter = 0; private final IntsRef tempRelFlags; private Date osmDataDate; private long zeroCounter = 0; @@ -103,11 +98,11 @@ public class OSMReader { public OSMReader(BaseGraph baseGraph, OSMParsers osmParsers, OSMReaderConfig config) { this.baseGraph = baseGraph; - this.edgeIntAccess = baseGraph.createEdgeIntAccess(); + this.edgeIntAccess = baseGraph.getEdgeAccess(); this.config = config; this.nodeAccess = baseGraph.getNodeAccess(); this.osmParsers = osmParsers; - this.restrictionSetter = new RestrictionSetter(baseGraph); + this.restrictionSetter = new RestrictionSetter(baseGraph, osmParsers.getRestrictionTagParsers().stream().map(RestrictionTagParser::getTurnRestrictionEnc).toList()); simplifyAlgo.setMaxDistance(config.getMaxWayPointDistance()); simplifyAlgo.setElevationMaxDistance(config.getElevationMaxWayPointDistance()); @@ -146,11 +141,6 @@ public OSMReader setElevationProvider(ElevationProvider eleProvider) { return this; } - public OSMReader setCountryRuleFactory(CountryRuleFactory countryRuleFactory) { - this.countryRuleFactory = countryRuleFactory; - return this; - } - public void readGraph() throws IOException { if (osmParsers == null) throw new IllegalStateException("Tag parsers were not set."); @@ -165,7 +155,6 @@ public void readGraph() throws IOException { throw new IllegalStateException("BaseGraph must be initialize before we can read OSM"); WaySegmentParser waySegmentParser = new WaySegmentParser.Builder(baseGraph.getNodeAccess(), baseGraph.getDirectory()) - .setElevationProvider(eleProvider) .setWayFilter(this::acceptWay) .setSplitNodeFilter(this::isBarrierNode) .setWayPreprocessor(this::preprocessWay) @@ -175,7 +164,7 @@ public void readGraph() throws IOException { .setWorkerThreads(config.getWorkerThreads()) .build(); waySegmentParser.readOSM(osmFile); - osmDataDate = waySegmentParser.getTimeStamp(); + osmDataDate = waySegmentParser.getTimestamp(); if (baseGraph.getNodes() == 0) throw new RuntimeException("Graph after reading OSM must not be empty"); releaseEverythingExceptRestrictionData(); @@ -192,6 +181,11 @@ public Date getDataDate() { return osmDataDate; } + private double lookupElevation(double lat, double lon) { + double ele = eleProvider.getEle(lat, lon); + return Double.isNaN(ele) ? config.getDefaultElevation() : ele; + } + /** * This method is called for each way during the first and second pass of the {@link WaySegmentParser}. All OSM * ways that are not accepted here and all nodes that are not referenced by any such way will be ignored. @@ -293,12 +287,6 @@ protected void setArtificialWayTags(PointList pointList, ReaderWay way, double d way.setTag("country", country); way.setTag("country_state", state); - if (countryRuleFactory != null) { - CountryRule countryRule = countryRuleFactory.getCountryRule(country); - if (countryRule != null) - way.setTag("country_rule", countryRule); - } - // also add all custom areas as artificial tag way.setTag("custom_areas", customAreas); } @@ -321,14 +309,26 @@ protected void addEdge(int fromIndex, int toIndex, PointList pointList, ReaderWa if (pointList.size() != nodeTags.size()) throw new AssertionError("there should be as many maps of node tags as there are points. node tags: " + nodeTags.size() + ", points: " + pointList.size()); - // todo: in principle it should be possible to delay elevation calculation so we do not need to store - // elevations during import (saves memory in pillar info during import). also note that we already need to - // to do some kind of elevation processing (bridge+tunnel interpolation in GraphHopper class, maybe this can - // go together - if (pointList.is3D()) { + // fill in all elevations (deferred from node scanning for cache-friendliness in elevation provider) + int last = pointList.size() - 1; + for (int i = 0; i <= last; i++) { + double ele; + if (i == 0 || i == last) { + // tower node: reuse elevation if already looked up by a previous edge + int towerIndex = i == 0 ? fromIndex : toIndex; + ele = nodeAccess.getEle(towerIndex); + if (ele == Helper.ELE_UNKNOWN) { + ele = lookupElevation(pointList.getLat(i), pointList.getLon(i)); + nodeAccess.setNode(towerIndex, pointList.getLat(i), pointList.getLon(i), ele); + } + } else { + ele = lookupElevation(pointList.getLat(i), pointList.getLon(i)); + } + pointList.setElevation(i, ele); + } // sample points along long edges - if (config.getLongEdgeSamplingDistance() < Double.MAX_VALUE) + if (config.getLongEdgeSamplingDistance() < Double.MAX_VALUE && !isFerry(way)) pointList = EdgeSampling.sample(pointList, config.getLongEdgeSamplingDistance(), distCalc, eleProvider); // smooth the elevation before calculating the distance because the distance will be incorrect if calculated afterwards @@ -352,9 +352,9 @@ else if (!config.getElevationSmoothing().isEmpty()) distance = 0.001; } - double maxDistance = (Integer.MAX_VALUE - 1) / 1000d; + double maxDistance = BaseGraph.MAX_DIST_METERS; if (Double.isNaN(distance)) { - LOGGER.warn("Bug in OSM or GraphHopper. Illegal tower node distance " + distance + " reset to 1m, osm way " + way.getId()); + LOGGER.warn("Bug in OSM or GraphHopper (" + bugCounter++ + "). Illegal tower node distance " + distance + " reset to 1m, osm way " + way.getId()); distance = 1; } @@ -362,17 +362,20 @@ else if (!config.getElevationSmoothing().isEmpty()) // Too large is very rare and often the wrong tagging. See #435 // so we can avoid the complexity of splitting the way for now (new towernodes would be required, splitting up geometry etc) // For example this happens here: https://www.openstreetmap.org/way/672506453 (Cape Town - Tristan da Cunha ferry) - LOGGER.warn("Bug in OSM or GraphHopper. Too big tower node distance " + distance + " reset to large value, osm way " + way.getId()); + LOGGER.warn("Bug in OSM or GraphHopper (" + bugCounter++ + "). Too big tower node distance " + distance + " reset to large value, osm way " + way.getId()); distance = maxDistance; } + if (bugCounter > 30) + throw new IllegalStateException("Too many bugs in OSM or GraphHopper encountered " + bugCounter); + setArtificialWayTags(pointList, way, distance, nodeTags); IntsRef relationFlags = getRelFlagsMap(way.getId()); EdgeIteratorState edge = baseGraph.edge(fromIndex, toIndex).setDistance(distance); osmParsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, relationFlags); - List list = way.getTag("key_values", Collections.emptyList()); - if (!list.isEmpty()) - edge.setKeyValues(list); + Map map = way.getTag("key_values", Collections.emptyMap()); + if (!map.isEmpty()) + edge.setKeyValues(map); // If the entire way is just the first and last point, do not waste space storing an empty way geometry if (pointList.size() > 2) { @@ -383,7 +386,7 @@ else if (!config.getElevationSmoothing().isEmpty()) edge.setWayGeometry(pointList.shallowCopy(1, pointList.size() - 1, false)); } - checkDistance(edge); + checkDistance(way.getId(), edge); restrictedWaysToEdgesMap.putIfReserved(way.getId(), edge.getEdge()); } @@ -393,17 +396,19 @@ private void checkCoordinates(int nodeIndex, GHPoint point) { throw new IllegalStateException("Suspicious coordinates for node " + nodeIndex + ": (" + nodeAccess.getLat(nodeIndex) + "," + nodeAccess.getLon(nodeIndex) + ") vs. (" + point + ")"); } - private void checkDistance(EdgeIteratorState edge) { + private void checkDistance(long readerWayId, EdgeIteratorState edge) { final double tolerance = 1; final double edgeDistance = edge.getDistance(); - final double geometryDistance = distCalc.calcDistance(edge.fetchWayGeometry(FetchMode.ALL)); + PointList pointList = edge.fetchWayGeometry(FetchMode.ALL); + final double geometryDistance = distCalc.calcDistance(pointList); if (Double.isInfinite(edgeDistance)) - throw new IllegalStateException("Infinite edge distance should never occur, as we are supposed to limit each distance to the maximum distance we can store, #435"); + throw new IllegalStateException("Infinite edge distance should never occur, as we are supposed to limit each distance to the maximum distance we can store, #435. wayId=" + readerWayId); else if (edgeDistance > 2_000_000) - LOGGER.warn("Very long edge detected: " + edge + " dist: " + edgeDistance); + LOGGER.warn("Very long edge detected: " + edge + " ( wayId=" + readerWayId + "), dist: " + edgeDistance); else if (Math.abs(edgeDistance - geometryDistance) > tolerance) - throw new IllegalStateException("Suspicious distance for edge: " + edge + " " + edgeDistance + " vs. " + geometryDistance - + ", difference: " + (edgeDistance - geometryDistance)); + throw new IllegalStateException("Suspicious distance for edge: " + edge + + " ( wayId=" + readerWayId + ") " + edgeDistance + " vs. " + geometryDistance + + ", difference: " + (edgeDistance - geometryDistance) + ", geometry: " + pointList); } /** @@ -412,9 +417,9 @@ else if (Math.abs(edgeDistance - geometryDistance) > tolerance) * the duration tag when it is present. The latter cannot be done on a per-edge basis, because the duration tag * refers to the duration of the entire way. */ - protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier coordinateSupplier) { - // storing the road name does not yet depend on the flagEncoder so manage it directly - List list = new ArrayList<>(); + protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier coordinateSupplier, + WaySegmentParser.NodeTagSupplier nodeTagSupplier) { + Map map = new LinkedHashMap<>(); if (config.isParseWayNames()) { // http://wiki.openstreetmap.org/wiki/Key:name String name = ""; @@ -422,44 +427,77 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier name = fixWayName(way.getTag("name:" + config.getPreferredLanguage())); if (name.isEmpty()) name = fixWayName(way.getTag("name")); + if (name.isEmpty()) + name = fixWayName(way.getTag("is_sidepath:of:name")); if (!name.isEmpty()) - list.add(new KVStorage.KeyValue(STREET_NAME, name)); + map.put(STREET_NAME, new KValue(name)); // http://wiki.openstreetmap.org/wiki/Key:ref String refName = fixWayName(way.getTag("ref")); if (!refName.isEmpty()) - list.add(new KVStorage.KeyValue(STREET_REF, refName)); + map.put(STREET_REF, new KValue(refName)); if (way.hasTag("destination:ref")) { - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref")))); + map.put(STREET_DESTINATION_REF, new KValue(fixWayName(way.getTag("destination:ref")))); } else { - if (way.hasTag("destination:ref:forward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref:forward")), true, false)); - if (way.hasTag("destination:ref:backward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION_REF, fixWayName(way.getTag("destination:ref:backward")), false, true)); + String fwdStr = fixWayName(way.getTag("destination:ref:forward")); + String bwdStr = fixWayName(way.getTag("destination:ref:backward")); + if (!fwdStr.isEmpty() || !bwdStr.isEmpty()) + map.put(STREET_DESTINATION_REF, new KValue(fwdStr.isEmpty() ? null : fwdStr, bwdStr.isEmpty() ? null : bwdStr)); } if (way.hasTag("destination")) { - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination")))); + map.put(STREET_DESTINATION, new KValue(fixWayName(way.getTag("destination")))); } else { - if (way.hasTag("destination:forward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination:forward")), true, false)); - if (way.hasTag("destination:backward")) - list.add(new KVStorage.KeyValue(STREET_DESTINATION, fixWayName(way.getTag("destination:backward")), false, true)); + String fwdStr = fixWayName(way.getTag("destination:forward")); + String bwdStr = fixWayName(way.getTag("destination:backward")); + if (!fwdStr.isEmpty() || !bwdStr.isEmpty()) + map.put(STREET_DESTINATION, new KValue(fwdStr.isEmpty() ? null : fwdStr, bwdStr.isEmpty() ? null : bwdStr)); + } + + // copy node name of motorway_junction + LongArrayList nodes = way.getNodes(); + if (!nodes.isEmpty() && (way.hasTag("highway", "motorway") || way.hasTag("highway", "motorway_link"))) { + // index 0 assumes oneway=yes + Map nodeTags = nodeTagSupplier.getTags(nodes.get(0)); + String nodeName = (String) nodeTags.getOrDefault("name", ""); + if (!nodeName.isEmpty() && "motorway_junction".equals(nodeTags.getOrDefault("highway", ""))) + map.put(MOTORWAY_JUNCTION, new KValue(nodeName)); } } - way.setTag("key_values", list); + + if (way.getTags().size() > 1) // at least highway tag + for (Map.Entry entry : way.getTags().entrySet()) { + if (entry.getKey().endsWith(":conditional") && entry.getValue() instanceof String && + // for now reduce index size a bit and focus on access tags + !entry.getKey().startsWith("maxspeed") && !entry.getKey().startsWith("maxweight")) { + // remove spaces as they unnecessarily increase the unique number of values: + String value = KVStorage.cutString(((String) entry.getValue()). + replace(" ", "").replace("bicycle", "bike")); + String key = entry.getKey().replace(':', '_').replace("bicycle", "bike"); + boolean fwd = key.contains("forward"); + boolean bwd = key.contains("backward"); + if (!value.isEmpty()) { + if (fwd == bwd) + map.put(key, new KValue(value)); + else + map.put(key, new KValue(fwd ? value : null, bwd ? value : null)); + } + } + } + + way.setTag("key_values", map); if (!isCalculateWayDistance(way)) return; - double distance = calcDistance(way, coordinateSupplier); + double distance = calc2DDistance(way, coordinateSupplier); if (Double.isNaN(distance)) { // Some nodes were missing, and we cannot determine the distance. This can happen when ways are only // included partially in an OSM extract. In this case we cannot calculate the speed either, so we return. LOGGER.warn("Could not determine distance for OSM way: " + way.getId()); return; } - way.setTag("way_distance", distance); + way.setTag("way_distance_2d", distance); // For ways with a duration tag we determine the average speed. This is needed for e.g. ferry routes, because // the duration tag is only valid for the entire way, and it would be wrong to use it after splitting the way @@ -479,8 +517,7 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier return; } - double speedInKmPerHour = distance / 1000 / (durationInSeconds / 60.0 / 60.0); - if (speedInKmPerHour < 0.1d) { + if (distance / 1000 / (durationInSeconds / 60.0 / 60.0) < 0.1d) { // Often there are mapping errors like duration=30:00 (30h) instead of duration=00:30 (30min). In this case we // ignore the duration tag. If no such cases show up anymore, because they were fixed, maybe raise the limit to find some more. OSM_WARNING_LOGGER.warn("Unrealistic low speed calculated from duration. Maybe the duration is too long, or it is applied to a way that only represents a part of the connection? OSM way: " @@ -491,7 +528,7 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier // tag will be present if 1) isCalculateWayDistance was true for this way, 2) no OSM nodes were missing // such that the distance could actually be calculated, 3) there was a duration tag we could parse, and 4) the // derived speed was not unrealistically slow. - way.setTag("speed_from_duration", speedInKmPerHour); + way.setTag("duration_in_seconds", durationInSeconds); } static String fixWayName(String str) { @@ -502,25 +539,21 @@ static String fixWayName(String str) { } /** - * @return the distance of the given way or NaN if some nodes were missing + * @return the 2D distance of the given way or NaN if some nodes were missing */ - private double calcDistance(ReaderWay way, WaySegmentParser.CoordinateSupplier coordinateSupplier) { + static double calc2DDistance(ReaderWay way, WaySegmentParser.CoordinateSupplier coordinateSupplier) { LongArrayList nodes = way.getNodes(); // every way has at least two nodes according to our acceptWay function GHPoint3D prevPoint = coordinateSupplier.getCoordinate(nodes.get(0)); if (prevPoint == null) return Double.NaN; - boolean is3D = !Double.isNaN(prevPoint.ele); + // Use 2D distance: pillar node elevation is not yet available during preprocessing double distance = 0; for (int i = 1; i < nodes.size(); i++) { GHPoint3D point = coordinateSupplier.getCoordinate(nodes.get(i)); if (point == null) return Double.NaN; - if (Double.isNaN(point.ele) == is3D) - throw new IllegalStateException("There should be elevation data for either all points or no points at all. OSM way: " + way.getId()); - distance += is3D - ? distCalc.calcDist3D(prevPoint.lat, prevPoint.lon, prevPoint.ele, point.lat, point.lon, point.ele) - : distCalc.calcDist(prevPoint.lat, prevPoint.lon, point.lat, point.lon); + distance += DistanceCalcEarth.DIST_EARTH.calcDist(prevPoint.lat, prevPoint.lon, point.lat, point.lon); prevPoint = point; } return distance; @@ -541,7 +574,7 @@ protected void preprocessRelations(ReaderRelation relation) { } } - Arrays.stream(RestrictionConverter.getRestrictedWayIds(relation)) + Arrays.stream(OSMRestrictionConverter.getRestrictedWayIds(relation)) .forEach(restrictedWaysToEdgesMap::reserve); } @@ -551,8 +584,8 @@ protected void preprocessRelations(ReaderRelation relation) { */ protected void processRelation(ReaderRelation relation, LongToIntFunction getIdForOSMNodeId) { if (turnCostStorage != null) - if (RestrictionConverter.isTurnRestriction(relation)) { - long osmViaNode = RestrictionConverter.getViaNodeIfViaNodeRestriction(relation); + if (OSMRestrictionConverter.isTurnRestriction(relation)) { + long osmViaNode = OSMRestrictionConverter.getViaNodeIfViaNodeRestriction(relation); if (osmViaNode >= 0) { int viaNode = getIdForOSMNodeId.applyAsInt(osmViaNode); // only include the restriction if the corresponding node wasn't excluded @@ -567,55 +600,54 @@ protected void processRelation(ReaderRelation relation, LongToIntFunction getIdF } private void addRestrictionsToGraph() { + if (turnCostStorage == null) + return; + StopWatch sw = StopWatch.started(); // The OSM restriction format is explained here: https://wiki.openstreetmap.org/wiki/Relation:restriction - List> restrictions = new ArrayList<>(restrictionRelations.size()); + List> restrictionRelationsWithTopology = new ArrayList<>(restrictionRelations.size()); for (ReaderRelation restrictionRelation : restrictionRelations) { try { - // convert the OSM relation topology to the graph representation. this only needs to be done once for all + // Build the topology of the OSM relation in the graph representation. This only needs to be done once for all // vehicle types (we also want to print warnings only once) - restrictions.add(RestrictionConverter.convert(restrictionRelation, baseGraph, restrictedWaysToEdgesMap::getEdges)); + restrictionRelationsWithTopology.add(OSMRestrictionConverter.buildRestrictionTopologyForGraph(baseGraph, restrictionRelation, restrictedWaysToEdgesMap::getEdges)); } catch (OSMRestrictionException e) { warnOfRestriction(restrictionRelation, e); } } - // The restriction type depends on the vehicle, or at least not all restrictions affect every vehicle type. - // We handle the restrictions for one vehicle after another. - for (RestrictionTagParser restrictionTagParser : osmParsers.getRestrictionTagParsers()) { - LongSet directedViaWaysUsedByRestrictions = new LongHashSet(); - List> restrictionsWithType = new ArrayList<>(restrictions.size()); - for (Triple r : restrictions) { - if (r.second == null) - // this relation was found to be invalid by another restriction tag parser already - continue; - try { + // It is important to set the restrictions for all parsers/encoded values at once to make + // sure the resulting turn restrictions do not interfere. + List restrictions = new ArrayList<>(); + // For every restriction we set flags that indicate the validity for the different parsers + List encBits = new ArrayList<>(); + for (Triple r : restrictionRelationsWithTopology) { + try { + BitSet bits = new BitSet(osmParsers.getRestrictionTagParsers().size()); + RestrictionType restrictionType = null; + for (int i = 0; i < osmParsers.getRestrictionTagParsers().size(); i++) { + RestrictionTagParser restrictionTagParser = osmParsers.getRestrictionTagParsers().get(i); RestrictionTagParser.Result res = restrictionTagParser.parseRestrictionTags(r.first.getTags()); if (res == null) - // this relation is ignored by the current restriction tag parser + // this relation is ignored by this restriction tag parser continue; - RestrictionConverter.checkIfCompatibleWithRestriction(r.second, res.getRestriction()); - // we ignore via-way restrictions that share the same via-way in the same direction, because these would require adding - // multiple artificial edges, see here: https://github.com/graphhopper/graphhopper/pull/2689#issuecomment-1306769694 and #2907 - if (r.second.isViaWayRestriction()) - for (LongCursor viaWay : r.third.getViaWays()) { - // We simply use the first and last via-node to determine the direction of the way, but for multiple via-ways maybe we need to reconsider this! - long directedViaWay = viaWay.value * (r.second.getViaNodes().get(0) < r.second.getViaNodes().get(r.second.getViaNodes().size() - 1) ? +1 : -1); - if (!directedViaWaysUsedByRestrictions.add(directedViaWay)) - throw new OSMRestrictionException("has a member with role 'via' (" + viaWay.value + ") that is also used as 'via' member by another restriction in the same direction. GraphHopper cannot handle this."); - } - restrictionsWithType.add(new Pair<>(r.second, res.getRestrictionType())); - } catch (OSMRestrictionException e) { - warnOfRestriction(r.first, e); - // we only want to print a warning once for each restriction relation, so we make sure this - // restriction is ignored for the other vehicles - r.second = null; + OSMRestrictionConverter.checkIfTopologyIsCompatibleWithRestriction(r.second, res.getRestriction()); + if (restrictionType != null && res.getRestrictionType() != restrictionType) + // so far we restrict ourselves to restriction relations that use the same type for all vehicles + throw new OSMRestrictionException("has different restriction type for different vehicles."); + restrictionType = res.getRestrictionType(); + bits.set(i); + } + if (bits.cardinality() > 0) { + List tmpRestrictions = OSMRestrictionConverter.buildRestrictionsForOSMRestriction(baseGraph, r.second, restrictionType); + restrictions.addAll(tmpRestrictions); + tmpRestrictions.forEach(__ -> encBits.add(RestrictionSetter.copyEncBits(bits))); } + } catch (OSMRestrictionException e) { + warnOfRestriction(r.first, e); } - restrictionSetter.setRestrictions(restrictionsWithType, restrictionTagParser.getTurnRestrictionEnc()); } - } - - public IntIntMap getArtificialEdgesByEdges() { - return restrictionSetter.getArtificialEdgesByEdges(); + restrictionSetter.setRestrictions(restrictions, encBits); + LOGGER.info("Finished adding turn restrictions. total turn cost entries: {}, took: {}", + Helper.nf(baseGraph.getTurnCostStorage().getTurnCostsCount()), sw.stop().getTimeString()); } private static void warnOfRestriction(ReaderRelation restrictionRelation, OSMRestrictionException e) { diff --git a/core/src/main/java/com/graphhopper/reader/osm/RestrictionConverter.java b/core/src/main/java/com/graphhopper/reader/osm/OSMRestrictionConverter.java similarity index 56% rename from core/src/main/java/com/graphhopper/reader/osm/RestrictionConverter.java rename to core/src/main/java/com/graphhopper/reader/osm/OSMRestrictionConverter.java index 67f2451e732..df97b0d1404 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/RestrictionConverter.java +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMRestrictionConverter.java @@ -18,21 +18,31 @@ package com.graphhopper.reader.osm; +import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.LongArrayList; +import com.carrotsearch.hppc.LongHashSet; import com.carrotsearch.hppc.cursors.IntCursor; import com.carrotsearch.hppc.cursors.LongCursor; import com.graphhopper.reader.ReaderElement; import com.graphhopper.reader.ReaderRelation; +import com.graphhopper.routing.util.parsers.RestrictionSetter; import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.EdgeExplorer; +import com.graphhopper.util.EdgeIterator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.function.LongFunction; -public class RestrictionConverter { - private static final Logger LOGGER = LoggerFactory.getLogger(RestrictionConverter.class); +import static com.graphhopper.reader.osm.RestrictionType.NO; +import static com.graphhopper.reader.osm.RestrictionType.ONLY; + +public class OSMRestrictionConverter { + private static final Logger LOGGER = LoggerFactory.getLogger(OSMRestrictionConverter.class); private static final long[] EMPTY_LONG_ARRAY_LIST = new long[0]; public static boolean isTurnRestriction(ReaderRelation relation) { @@ -59,14 +69,14 @@ public static long getViaNodeIfViaNodeRestriction(ReaderRelation relation) { } /** - * OSM restriction relations specify turn restrictions between OSM ways (of course). This method converts such a - * relation into a 'graph' representation, where the turn restrictions are specified in terms of edge/node IDs instead + * OSM restriction relations specify turn restrictions between OSM ways (of course). This method rebuilds the + * topology of such a relation in the graph representation, where the turn restrictions are specified in terms of edge/node IDs instead * of OSM IDs. * * @throws OSMRestrictionException if the given relation is either not valid in some way and/or cannot be handled and * shall be ignored */ - public static Triple convert(ReaderRelation relation, BaseGraph baseGraph, LongFunction> edgesByWay) throws OSMRestrictionException { + public static Triple buildRestrictionTopologyForGraph(BaseGraph baseGraph, ReaderRelation relation, LongFunction> edgesByWay) throws OSMRestrictionException { if (!isTurnRestriction(relation)) throw new IllegalArgumentException("expected a turn restriction: " + relation.getTags()); RestrictionMembers restrictionMembers = extractMembers(relation); @@ -76,19 +86,29 @@ public static Triple conve // that are actually part of the given relation WayToEdgeConverter wayToEdgeConverter = new WayToEdgeConverter(baseGraph, edgesByWay); if (restrictionMembers.isViaWay()) { + if (containsDuplicateWays(restrictionMembers)) + // For now let's ignore all via-way restrictions with duplicate from/to/via-members + // until we find cases where this is too strict. + throw new OSMRestrictionException("contains duplicate from-/via-/to-members"); WayToEdgeConverter.EdgeResult res = wayToEdgeConverter .convertForViaWays(restrictionMembers.getFromWays(), restrictionMembers.getViaWays(), restrictionMembers.getToWays()); - return new Triple<>(relation, GraphRestriction.way(res.getFromEdges(), res.getViaEdges(), res.getToEdges(), res.getNodes()), restrictionMembers); + return new Triple<>(relation, RestrictionTopology.way(res.getFromEdges(), res.getViaEdges(), res.getToEdges(), res.getNodes()), restrictionMembers); } else { int viaNode = relation.getTag("graphhopper:via_node", -1); if (viaNode < 0) throw new IllegalStateException("For some reason we did not set graphhopper:via_node for this relation: " + relation.getId()); WayToEdgeConverter.NodeResult res = wayToEdgeConverter .convertForViaNode(restrictionMembers.getFromWays(), viaNode, restrictionMembers.getToWays()); - return new Triple<>(relation, GraphRestriction.node(res.getFromEdges(), viaNode, res.getToEdges()), restrictionMembers); + return new Triple<>(relation, RestrictionTopology.node(res.getFromEdges(), viaNode, res.getToEdges()), restrictionMembers); } } + private static boolean containsDuplicateWays(RestrictionMembers restrictionMembers) { + LongArrayList allWays = restrictionMembers.getAllWays(); + LongHashSet uniqueWays = new LongHashSet(allWays); + return uniqueWays.size() != allWays.size(); + } + private static boolean membersExist(RestrictionMembers members, LongFunction> edgesByWay, ReaderRelation relation) { for (LongCursor c : members.getAllWays()) if (!edgesByWay.apply(c.value).hasNext()) { @@ -99,7 +119,7 @@ private static boolean membersExist(RestrictionMembers members, LongFunction 1 && !"no_entry".equals(restriction)) throw new OSMRestrictionException("has multiple members with role 'from' even though it is not a 'no_entry' restriction"); if (g.getToEdges().size() > 1 && !"no_exit".equals(restriction)) @@ -179,4 +199,99 @@ private static void checkTags(LongArrayList fromWays, LongArrayList toWays, Map< if (toWays.size() > 1 && !hasNoExit) throw new OSMRestrictionException("has multiple members with role 'to' even though it is not a 'no_exit' restriction"); } + + /** + * Converts an OSM restriction to (multiple) single 'no' restrictions to be fed into {@link RestrictionSetter} + */ + public static List buildRestrictionsForOSMRestriction( + BaseGraph baseGraph, RestrictionTopology topology, RestrictionType type) { + List result = new ArrayList<>(); + if (type == NO) { + if (topology.isViaWayRestriction()) { + for (IntCursor fromEdge : topology.getFromEdges()) + for (IntCursor toEdge : topology.getToEdges()) { + IntArrayList edges = new IntArrayList(topology.getViaEdges().size()+2); + edges.add(fromEdge.value); + edges.addAll(topology.getViaEdges()); + edges.add(toEdge.value); + result.add(RestrictionSetter.createViaEdgeRestriction(edges)); + } + } else { + for (IntCursor fromEdge : topology.getFromEdges()) + for (IntCursor toEdge : topology.getToEdges()) + result.add(RestrictionSetter.createViaNodeRestriction(fromEdge.value, topology.getViaNodes().get(0), toEdge.value)); + } + } else if (type == ONLY) { + if (topology.getFromEdges().size() > 1 || topology.getToEdges().size() > 1) + throw new IllegalArgumentException("'Only' restrictions with multiple from- or to- edges are not supported"); + if (topology.isViaWayRestriction()) + result.addAll(createRestrictionsForViaEdgeOnlyRestriction(baseGraph, topology)); + else + result.addAll(createRestrictionsForViaNodeOnlyRestriction(baseGraph.createEdgeExplorer(), + topology.getFromEdges().get(0), topology.getViaNodes().get(0), topology.getToEdges().get(0))); + } else + throw new IllegalArgumentException("Unexpected restriction type: " + type); + return result; + } + + private static IntArrayList collectEdges(RestrictionTopology r) { + IntArrayList result = new IntArrayList(r.getViaEdges().size() + 2); + result.add(r.getFromEdges().get(0)); + result.addAll(r.getViaEdges()); + result.add(r.getToEdges().get(0)); + return result; + } + + private static List createRestrictionsForViaNodeOnlyRestriction(EdgeExplorer edgeExplorer, int fromEdge, int viaNode, int toEdge) { + List result = new ArrayList<>(); + EdgeIterator iter = edgeExplorer.setBaseNode(viaNode); + while (iter.next()) { + // deny all turns except the one to the to-edge, and (for performance reasons, see below) + // except the u-turn back to the from-edge + if (iter.getEdge() != toEdge && iter.getEdge() != fromEdge) + result.add(RestrictionSetter.createViaNodeRestriction(fromEdge, viaNode, iter.getEdge())); + } + return result; + } + + private static List createRestrictionsForViaEdgeOnlyRestriction(BaseGraph graph, RestrictionTopology topology) { + // For via-way ONLY restrictions we have to turn from the from-edge onto the first via-edge, + // continue with the next via-edge(s) and finally turn onto the to-edge. So we cannot branch + // out anywhere. If we don't start with the from-edge the restriction does not apply at all. + // c.f. https://github.com/valhalla/valhalla/discussions/4764 + if (topology.getViaEdges().isEmpty()) + throw new IllegalArgumentException("Via-edge restrictions must have at least one via-edge"); + final EdgeExplorer explorer = graph.createEdgeExplorer(); + IntArrayList edges = collectEdges(topology); + List result = + createRestrictionsForViaNodeOnlyRestriction(explorer, edges.get(0), topology.getViaNodes().get(0), edges.get(1)); + for (int i = 2; i < edges.size(); i++) { + EdgeIterator iter = explorer.setBaseNode(topology.getViaNodes().get(i - 1)); + while (iter.next()) { + if (iter.getEdge() != edges.get(i) && + // We deny u-turns within via-way 'only' restrictions unconditionally (see below), so no need + // to restrict them here as well + iter.getEdge() != edges.get(i - 1) + ) { + IntArrayList restriction = new IntArrayList(i + 1); + for (int j = 0; j < i; j++) + restriction.add(edges.get(j)); + restriction.add(iter.getEdge()); + if (restriction.size() == 3 && restriction.get(0) == restriction.get(restriction.size() - 1)) + // To prevent an exception in RestrictionSetter we need to prevent unambiguous + // restrictions like a-b-a. Maybe we even need to exclude other cases as well, + // but so far they did not occur. + continue; + result.add(RestrictionSetter.createViaEdgeRestriction(restriction)); + } + } + } + // explicitly deny all u-turns along the via-way 'only' restriction + // todo: currently disabled! we skip u-turn restrictions to improve reading performance, + // because so far they are ignored anyway! https://github.com/graphhopper/graphhopper/issues/2570 +// for (int i = 0; i < edges.size() - 1; i++) { +// result.add(RestrictionSetter.createViaNodeRestriction(edges.get(i), topology.getViaNodes().get(i), edges.get(i))); +// } + return result; + } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/OSMXmlInput.java b/core/src/main/java/com/graphhopper/reader/osm/OSMXmlInput.java new file mode 100644 index 00000000000..23cec2d0278 --- /dev/null +++ b/core/src/main/java/com/graphhopper/reader/osm/OSMXmlInput.java @@ -0,0 +1,120 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.osm; + +import com.graphhopper.reader.ReaderElement; + +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.IOException; +import java.io.InputStream; + +/** + * OSM input implementation for XML format (.osm, .osm.gz, etc.) + */ +class OSMXmlInput implements OSMInput { + private final InputStream inputStream; + private XMLStreamReader xmlParser; + private boolean eof; + private OSMFileHeader fileheader; + + OSMXmlInput(InputStream inputStream) { + this.inputStream = inputStream; + } + + OSMXmlInput open() throws XMLStreamException { + XMLInputFactory factory = XMLInputFactory.newInstance(); + xmlParser = factory.createXMLStreamReader(inputStream, "UTF-8"); + + int event = xmlParser.next(); + if (event != XMLStreamConstants.START_ELEMENT || !xmlParser.getLocalName().equalsIgnoreCase("osm")) { + throw new IllegalArgumentException("File is not a valid OSM stream"); + } + // See https://wiki.openstreetmap.org/wiki/PBF_Format#Definition_of_the_OSMHeader_fileblock + String timestamp = xmlParser.getAttributeValue(null, "osmosis_replication_timestamp"); + + if (timestamp == null) + timestamp = xmlParser.getAttributeValue(null, "timestamp"); + + if (timestamp != null) { + fileheader = new OSMFileHeader(); + fileheader.setTag("timestamp", timestamp); + } + + eof = false; + return this; + } + + @Override + public ReaderElement getNext() throws XMLStreamException { + if (eof) + throw new IllegalStateException("EOF reached"); + + int event = xmlParser.next(); + if (fileheader != null) { + ReaderElement copyfileheader = fileheader; + fileheader = null; + return copyfileheader; + } + + while (event != XMLStreamConstants.END_DOCUMENT) { + if (event == XMLStreamConstants.START_ELEMENT) { + String idStr = xmlParser.getAttributeValue(null, "id"); + if (idStr != null) { + String name = xmlParser.getLocalName(); + long id; + switch (name.charAt(0)) { + case 'n': + // note vs. node + if ("node".equals(name)) { + id = Long.parseLong(idStr); + return OSMXMLHelper.createNode(id, xmlParser); + } + break; + + case 'w': { + id = Long.parseLong(idStr); + return OSMXMLHelper.createWay(id, xmlParser); + } + case 'r': + id = Long.parseLong(idStr); + return OSMXMLHelper.createRelation(id, xmlParser); + } + } + } + event = xmlParser.next(); + } + xmlParser.close(); + eof = true; + return null; + } + + @Override + public void close() throws IOException { + try { + xmlParser.close(); + } catch (XMLStreamException ex) { + throw new IOException(ex); + } finally { + eof = true; + inputStream.close(); + } + } +} diff --git a/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java b/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java deleted file mode 100644 index 3aca5a49a44..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/PillarInfo.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm; - -import com.graphhopper.storage.DataAccess; -import com.graphhopper.storage.Directory; -import com.graphhopper.util.Helper; -import com.graphhopper.util.PointAccess; - -/** - * This class helps to store lat,lon,ele for every node parsed in OSMReader - *

- * - * @author Peter Karich - */ -public class PillarInfo { - private static final int LAT = 0 * 4, LON = 1 * 4, ELE = 2 * 4; - private final boolean enabled3D; - private final DataAccess da; - private final int rowSizeInBytes; - private final Directory dir; - - public PillarInfo(boolean enabled3D, Directory dir) { - this.enabled3D = enabled3D; - this.dir = dir; - this.da = dir.create("tmp_pillar_info").create(100); - this.rowSizeInBytes = getDimension() * 4; - } - - public boolean is3D() { - return enabled3D; - } - - public int getDimension() { - return enabled3D ? 3 : 2; - } - - public void ensureNode(long nodeId) { - long tmp = nodeId * rowSizeInBytes; - da.ensureCapacity(tmp + rowSizeInBytes); - } - - public void setNode(long nodeId, double lat, double lon, double ele) { - ensureNode(nodeId); - long tmp = nodeId * rowSizeInBytes; - da.setInt(tmp + LAT, Helper.degreeToInt(lat)); - da.setInt(tmp + LON, Helper.degreeToInt(lon)); - - if (is3D()) - da.setInt(tmp + ELE, Helper.eleToInt(ele)); - } - - public double getLat(long id) { - int intVal = da.getInt(id * rowSizeInBytes + LAT); - return Helper.intToDegree(intVal); - } - - public double getLon(long id) { - int intVal = da.getInt(id * rowSizeInBytes + LON); - return Helper.intToDegree(intVal); - } - - public double getEle(long id) { - if (!is3D()) - return Double.NaN; - - int intVal = da.getInt(id * rowSizeInBytes + ELE); - return Helper.intToEle(intVal); - } - - public void clear() { - dir.remove(da.getName()); - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/GraphRestriction.java b/core/src/main/java/com/graphhopper/reader/osm/RestrictionTopology.java similarity index 80% rename from core/src/main/java/com/graphhopper/reader/osm/GraphRestriction.java rename to core/src/main/java/com/graphhopper/reader/osm/RestrictionTopology.java index 81f25262029..7a67b95068b 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/GraphRestriction.java +++ b/core/src/main/java/com/graphhopper/reader/osm/RestrictionTopology.java @@ -32,34 +32,34 @@ * This class only contains the 'topology' of the restriction. The {@link RestrictionType} is handled separately, * because opposite to the type the topology does not depend on the vehicle type. */ -public class GraphRestriction { +public class RestrictionTopology { private final boolean isViaWayRestriction; private final IntArrayList viaNodes; private final IntArrayList fromEdges; private final IntArrayList viaEdges; private final IntArrayList toEdges; - public static GraphRestriction node(int fromEdge, int viaNode, int toEdge) { + public static RestrictionTopology node(int fromEdge, int viaNode, int toEdge) { return node(IntArrayList.from(fromEdge), viaNode, IntArrayList.from(toEdge)); } - public static GraphRestriction node(IntArrayList fromEdges, int viaNode, IntArrayList toEdges) { - return new GraphRestriction(false, IntArrayList.from(viaNode), fromEdges, null, toEdges); + public static RestrictionTopology node(IntArrayList fromEdges, int viaNode, IntArrayList toEdges) { + return new RestrictionTopology(false, IntArrayList.from(viaNode), fromEdges, null, toEdges); } - public static GraphRestriction way(int fromEdge, int viaEdge, int toEdge, IntArrayList viaNodes) { + public static RestrictionTopology way(int fromEdge, int viaEdge, int toEdge, IntArrayList viaNodes) { return way(fromEdge, IntArrayList.from(viaEdge), toEdge, viaNodes); } - public static GraphRestriction way(int fromEdge, IntArrayList viaEdges, int toEdge, IntArrayList viaNodes) { + public static RestrictionTopology way(int fromEdge, IntArrayList viaEdges, int toEdge, IntArrayList viaNodes) { return way(IntArrayList.from(fromEdge), viaEdges, IntArrayList.from(toEdge), viaNodes); } - public static GraphRestriction way(IntArrayList fromEdges, IntArrayList viaEdges, IntArrayList toEdges, IntArrayList viaNodes) { - return new GraphRestriction(true, viaNodes, fromEdges, viaEdges, toEdges); + public static RestrictionTopology way(IntArrayList fromEdges, IntArrayList viaEdges, IntArrayList toEdges, IntArrayList viaNodes) { + return new RestrictionTopology(true, viaNodes, fromEdges, viaEdges, toEdges); } - private GraphRestriction(boolean isViaWayRestriction, IntArrayList viaNodes, IntArrayList fromEdges, IntArrayList viaEdges, IntArrayList toEdges) { + private RestrictionTopology(boolean isViaWayRestriction, IntArrayList viaNodes, IntArrayList fromEdges, IntArrayList viaEdges, IntArrayList toEdges) { if (fromEdges.size() > 1 && toEdges.size() > 1) throw new IllegalArgumentException("fromEdges and toEdges cannot be size > 1 at the same time"); if (fromEdges.isEmpty() || toEdges.isEmpty()) diff --git a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java index 391975a00da..5cc660cd971 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WaySegmentParser.java @@ -23,7 +23,6 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.storage.Directory; import com.graphhopper.util.Helper; import com.graphhopper.util.PointAccess; @@ -38,9 +37,7 @@ import java.io.IOException; import java.text.ParseException; import java.util.*; -import java.util.function.Consumer; -import java.util.function.LongToIntFunction; -import java.util.function.Predicate; +import java.util.function.*; import static com.graphhopper.reader.osm.OSMNodeData.*; import static com.graphhopper.util.Helper.nf; @@ -65,10 +62,9 @@ public class WaySegmentParser { private static final Logger LOGGER = LoggerFactory.getLogger(WaySegmentParser.class); private static final Set INCLUDE_IF_NODE_TAGS = new HashSet<>(Arrays.asList("barrier", "highway", "railway", "crossing", "ford")); - private ElevationProvider elevationProvider = ElevationProvider.NOOP; private Predicate wayFilter = way -> true; private Predicate splitNodeFilter = node -> false; - private WayPreprocessor wayPreprocessor = (way, supplier) -> { + private WayPreprocessor wayPreprocessor = (way, coordinateSupplier, nodeTagSupplier) -> { }; private Consumer relationPreprocessor = relation -> { }; @@ -112,13 +108,14 @@ public void readOSM(File osmFile) { LOGGER.info("Finished reading OSM file." + " pass1: " + (int) sw1.getSeconds() + "s, " + " pass2: " + (int) sw2.getSeconds() + "s, " + - " total: " + (int) (sw1.getSeconds() + sw2.getSeconds()) + "s"); + " total: " + (int) (sw1.getSeconds() + sw2.getSeconds()) + "s" + + " memory: " + Helper.getMemInfo()); } /** * @return the timestamp read from the OSM file, or null if nothing was read yet */ - public Date getTimeStamp() { + public Date getTimestamp() { return timestamp; } @@ -206,7 +203,7 @@ public void handleNode(ReaderNode node) { LOGGER.info("pass2 - processed nodes: " + nf(nodeCounter) + ", accepted nodes: " + nf(acceptedNodes) + ", " + Helper.getMemInfo()); - long nodeType = nodeData.addCoordinatesIfMapped(node.getId(), node.getLat(), node.getLon(), () -> elevationProvider.getEle(node)); + long nodeType = nodeData.addCoordinatesIfMapped(node.getId(), node.getLat(), node.getLon()); if (nodeType == EMPTY_NODE) return; @@ -252,7 +249,7 @@ public void handleWay(ReaderWay way) { List segment = new ArrayList<>(way.getNodes().size()); for (LongCursor node : way.getNodes()) segment.add(new SegmentNode(node.value, nodeData.getId(node.value), nodeData.getTags(node.value))); - wayPreprocessor.preprocessWay(way, osmNodeId -> nodeData.getCoordinates(nodeData.getId(osmNodeId))); + wayPreprocessor.preprocessWay(way, osmNodeId -> nodeData.getCoordinates(nodeData.getId(osmNodeId)), osmNodeId -> nodeData.getTags(osmNodeId)); splitWayAtJunctionsAndEmptySections(segment, way); } @@ -400,15 +397,13 @@ private void readOSM(File file, ReaderElementHandler handler, SkipOptions skipOp while ((elem = osmInput.getNext()) != null) handler.handleElement(elem); handler.onFinish(); - if (osmInput.getUnprocessedElements() > 0) - throw new IllegalStateException("There were some remaining elements in the reader queue " + osmInput.getUnprocessedElements()); } catch (Exception e) { throw new RuntimeException("Could not parse OSM file: " + file.getAbsolutePath(), e); } } protected OSMInput openOsmInputFile(File osmFile, SkipOptions skipOptions) throws XMLStreamException, IOException { - return new OSMInputFile(osmFile).setWorkerThreads(workerThreads).setSkipOptions(skipOptions).open(); + return OSMInput.open(osmFile, workerThreads, skipOptions); } public static class Builder { @@ -422,14 +417,6 @@ public Builder(PointAccess pointAccess, Directory directory) { waySegmentParser = new WaySegmentParser(new OSMNodeData(pointAccess, directory)); } - /** - * @param elevationProvider used to determine the elevation of an OSM node - */ - public Builder setElevationProvider(ElevationProvider elevationProvider) { - waySegmentParser.elevationProvider = elevationProvider; - return this; - } - /** * @param wayFilter return true for OSM ways that should be considered and false otherwise */ @@ -541,10 +528,14 @@ public interface WayPreprocessor { * of this node. If elevation is disabled it will be NaN. Returns null if no such OSM * node exists. */ - void preprocessWay(ReaderWay way, CoordinateSupplier coordinateSupplier); + void preprocessWay(ReaderWay way, CoordinateSupplier coordinateSupplier, NodeTagSupplier nodeTagSupplier); } public interface CoordinateSupplier { GHPoint3D getCoordinate(long osmNodeId); } + + public interface NodeTagSupplier { + Map getTags(long osmNodeId); + } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java b/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java index e12d1adb9ac..3c009369c30 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java @@ -56,9 +56,9 @@ public NodeResult convertForViaNode(LongArrayList fromWays, int viaNode, LongArr result.fromEdges.add(e.value); }); if (result.fromEdges.size() < fromWays.size()) - throw new OSMRestrictionException("has from member ways that aren't adjacent to the via member node"); + throw new OSMRestrictionException("has from-member ways that aren't adjacent to the via-member node"); else if (result.fromEdges.size() > fromWays.size()) - throw new OSMRestrictionException("has from member ways that aren't split at the via member node"); + throw new OSMRestrictionException("has from-member ways that aren't split at the via-member node"); for (LongCursor toWay : toWays) edgesByWay.apply(toWay.value).forEachRemaining(e -> { @@ -66,9 +66,9 @@ else if (result.fromEdges.size() > fromWays.size()) result.toEdges.add(e.value); }); if (result.toEdges.size() < toWays.size()) - throw new OSMRestrictionException("has to member ways that aren't adjacent to the via member node"); + throw new OSMRestrictionException("has to-member ways that aren't adjacent to the via-member node"); else if (result.toEdges.size() > toWays.size()) - throw new OSMRestrictionException("has to member ways that aren't split at the via member node"); + throw new OSMRestrictionException("has to-member ways that aren't split at the via-member node"); return result; } @@ -113,38 +113,47 @@ public EdgeResult convertForViaWays(LongArrayList fromWays, LongArrayList viaWay for (LongCursor toWay : toWays) findEdgeChain(fromWay.value, viaWays, toWay.value, solutions); if (solutions.size() < fromWays.size() * toWays.size()) - throw new OSMRestrictionException("has from/to member ways that aren't connected with the via member way(s)"); + throw new OSMRestrictionException("has disconnected member ways"); else if (solutions.size() > fromWays.size() * toWays.size()) - throw new OSMRestrictionException("has from/to member ways that aren't split at the via member way(s)"); - return buildResult(solutions, new EdgeResult(fromWays.size(), viaWays.size(), toWays.size())); + throw new OSMRestrictionException("has member ways that do not form a unique path"); + return buildResult(solutions, fromWays, viaWays, toWays); } - private static EdgeResult buildResult(List edgeChains, EdgeResult result) { - for (IntArrayList edgeChain : edgeChains) { - result.fromEdges.add(edgeChain.get(0)); - if (result.nodes.isEmpty()) { - // the via-edges and nodes are the same for edge chain - for (int i = 1; i < edgeChain.size() - 3; i += 2) { - result.nodes.add(edgeChain.get(i)); - result.viaEdges.add(edgeChain.get(i + 1)); - } - result.nodes.add(edgeChain.get(edgeChain.size() - 2)); - } - result.toEdges.add(edgeChain.get(edgeChain.size() - 1)); + private static EdgeResult buildResult(List edgeChains, LongArrayList fromWays, LongArrayList viaWays, LongArrayList toWays) { + EdgeResult result = new EdgeResult(fromWays.size(), viaWays.size(), toWays.size()); + // we get multiple edge chains, but they are expected to be identical except for their first or last members + IntArrayList firstChain = edgeChains.get(0); + result.fromEdges.add(firstChain.get(0)); + for (int i = 1; i < firstChain.size() - 3; i += 2) { + result.nodes.add(firstChain.get(i)); + result.viaEdges.add(firstChain.get(i + 1)); } + result.nodes.add(firstChain.get(firstChain.size() - 2)); + result.toEdges.add(firstChain.get(firstChain.size() - 1)); + // We keep the first/last elements of all chains in case there are multiple from/to ways + List otherChains = edgeChains.subList(1, edgeChains.size()); + if (fromWays.size() > 1) { + if (otherChains.stream().anyMatch(chain -> chain.get(chain.size() - 1) != firstChain.get(firstChain.size() - 1))) + throw new IllegalArgumentException("edge chains were supposed to be the same except for their first elements, but got: " + edgeChains + " - for: " + fromWays + ", " + viaWays + ", " + toWays); + otherChains.forEach(chain -> result.fromEdges.add(chain.get(0))); + } else if (toWays.size() > 1) { + if (otherChains.stream().anyMatch(chain -> chain.get(0) != firstChain.get(0))) + throw new IllegalArgumentException("edge chains were supposed to be the same except for their last elements, but got: " + edgeChains + " - for: " + fromWays + ", " + viaWays + ", " + toWays); + otherChains.forEach(chain -> result.toEdges.add(chain.get(chain.size() - 1))); + } else if (!otherChains.isEmpty()) + throw new IllegalStateException("If there are multiple chains there must be either multiple from- or to-ways."); return result; } - private void findEdgeChain(long fromWay, LongArrayList viaWays, long toWay, List solutions) throws OSMRestrictionException { - // For each edge chain there must be one edge associated with the from-way, one for each via-way and one + private void findEdgeChain(long fromWay, LongArrayList viaWays, long toWay, List solutions) { + // For each edge chain there must be one edge associated with the from-way, at least one for each via-way and one // associated with the to-way. We use DFS with backtracking to find all edge chains that connect an edge // associated with the from-way with one associated with the to-way. IntArrayList viaEdgesForViaWays = new IntArrayList(viaWays.size()); for (LongCursor c : viaWays) { Iterator iterator = edgesByWay.apply(c.value); viaEdgesForViaWays.add(iterator.next().value); - if (iterator.hasNext()) - throw new OSMRestrictionException("has via member way that isn't split at adjacent ways: " + c.value); + iterator.forEachRemaining(i -> viaEdgesForViaWays.add(i.value)); } IntArrayList toEdges = listFromIterator(edgesByWay.apply(toWay)); diff --git a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspector.java b/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspector.java deleted file mode 100644 index eec6ac9ed42..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspector.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.conditional; - -import com.graphhopper.reader.ReaderWay; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.*; - -/** - * Inspects the conditional tags of an OSMWay according to the given conditional tags. - *

- * - * @author Robin Boldt - */ -public class ConditionalOSMTagInspector implements ConditionalTagInspector { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final List tagsToCheck; - private final ConditionalParser permitParser, restrictiveParser; - // enabling by default makes noise but could improve OSM data - private boolean enabledLogs; - - public ConditionalOSMTagInspector(Calendar value, List tagsToCheck, - Set restrictiveValues, Set permittedValues) { - this(Arrays.asList(new DateRangeParser(value)), tagsToCheck, restrictiveValues, permittedValues, false); - } - - public ConditionalOSMTagInspector(List valueParsers, List tagsToCheck, - Set restrictiveValues, Set permittedValues, boolean enabledLogs) { - this.tagsToCheck = new ArrayList<>(tagsToCheck.size()); - for (String tagToCheck : tagsToCheck) { - this.tagsToCheck.add(tagToCheck + ":conditional"); - } - - this.enabledLogs = enabledLogs; - - // enable for debugging purposes only as this is too much - boolean logUnsupportedFeatures = false; - this.permitParser = new ConditionalParser(permittedValues, logUnsupportedFeatures); - this.restrictiveParser = new ConditionalParser(restrictiveValues, logUnsupportedFeatures); - for (ConditionalValueParser cvp : valueParsers) { - permitParser.addConditionalValueParser(cvp); - restrictiveParser.addConditionalValueParser(cvp); - } - } - - public void addValueParser(ConditionalValueParser vp) { - permitParser.addConditionalValueParser(vp); - restrictiveParser.addConditionalValueParser(vp); - } - - @Override - public boolean isRestrictedWayConditionallyPermitted(ReaderWay way) { - return applies(way, true); - } - - @Override - public boolean isPermittedWayConditionallyRestricted(ReaderWay way) { - return applies(way, false); - } - - protected boolean applies(ReaderWay way, boolean checkPermissiveValues) { - for (int index = 0; index < tagsToCheck.size(); index++) { - String tagToCheck = tagsToCheck.get(index); - String val = way.getTag(tagToCheck); - if (val == null || val.isEmpty()) - continue; - - try { - if (checkPermissiveValues) { - if (permitParser.checkCondition(val)) - return true; - } else { - if (restrictiveParser.checkCondition(val)) - return true; - } - - } catch (Exception e) { - if (enabledLogs) { - // log only if no date ala 21:00 as currently date and numbers do not support time precise restrictions - if (!val.contains(":")) - logger.warn("for way " + way.getId() + " could not parse the conditional value '" + val + "' of tag '" + tagToCheck + "'. Exception:" + e.getMessage()); - } - } - } - return false; - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalParser.java b/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalParser.java deleted file mode 100644 index 1b67a29e08e..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalParser.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.conditional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; -import java.util.Set; - -/** - * Parses a conditional tag according to - * http://wiki.openstreetmap.org/wiki/Conditional_restrictions. - *

- * - * @author Robin Boldt - */ -public class ConditionalParser { - private final Logger logger = LoggerFactory.getLogger(getClass()); - private final Set restrictedTags; - private final List valueParsers = new ArrayList<>(5); - private final boolean enabledLogs; - - public ConditionalParser(Set restrictedTags) { - this(restrictedTags, false); - } - - public ConditionalParser(Set restrictedTags, boolean enabledLogs) { - // use map => key & type (date vs. double) - this.restrictedTags = restrictedTags; - this.enabledLogs = enabledLogs; - } - - public static ConditionalValueParser createNumberParser(final String assertKey, final Number obj) { - return new ConditionalValueParser() { - @Override - public ConditionState checkCondition(String conditionalValue) throws ParseException { - int indexLT = conditionalValue.indexOf("<"); - if (indexLT > 0 && conditionalValue.length() > 2) { - final String key = conditionalValue.substring(0, indexLT).trim(); - if (!assertKey.equals(key)) - return ConditionState.INVALID; - - if (conditionalValue.charAt(indexLT + 1) == '=') - indexLT++; - final double value = parseNumber(conditionalValue.substring(indexLT + 1)); - if (obj.doubleValue() < value) - return ConditionState.TRUE; - else - return ConditionState.FALSE; - } - - int indexGT = conditionalValue.indexOf(">"); - if (indexGT > 0 && conditionalValue.length() > 2) { - final String key = conditionalValue.substring(0, indexGT).trim(); - if (!assertKey.equals(key)) - return ConditionState.INVALID; - - // for now just ignore equals sign - if (conditionalValue.charAt(indexGT + 1) == '=') - indexGT++; - - final double value = parseNumber(conditionalValue.substring(indexGT + 1)); - if (obj.doubleValue() > value) - return ConditionState.TRUE; - else - return ConditionState.FALSE; - } - - return ConditionState.INVALID; - } - }; - } - - /** - * This method adds a new value parser. The one added last has a higher priority. - */ - public ConditionalParser addConditionalValueParser(ConditionalValueParser vp) { - valueParsers.add(0, vp); - return this; - } - - public ConditionalParser setConditionalValueParser(ConditionalValueParser vp) { - valueParsers.clear(); - valueParsers.add(vp); - return this; - } - - public boolean checkCondition(String conditionalTag) throws ParseException { - if (conditionalTag == null || conditionalTag.isEmpty() || !conditionalTag.contains("@")) - return false; - - if (conditionalTag.contains(";")) { - if (enabledLogs) - logger.warn("We do not support multiple conditions yet: " + conditionalTag); - return false; - } - - String[] conditionalArr = conditionalTag.split("@"); - - if (conditionalArr.length != 2) - throw new IllegalStateException("could not split this condition: " + conditionalTag); - - String restrictiveValue = conditionalArr[0].trim(); - if (!restrictedTags.contains(restrictiveValue)) - return false; - - String conditionalValue = conditionalArr[1]; - conditionalValue = conditionalValue.replace('(', ' '); - conditionalValue = conditionalValue.replace(')', ' '); - conditionalValue = conditionalValue.trim(); - - for (ConditionalValueParser valueParser : valueParsers) { - ConditionalValueParser.ConditionState c = valueParser.checkCondition(conditionalValue); - if (c.isValid()) - return c.isCheckPassed(); - } - return false; - } - - protected static double parseNumber(String str) { - int untilIndex = str.length() - 1; - for (; untilIndex >= 0; untilIndex--) { - if (Character.isDigit(str.charAt(untilIndex))) - break; - } - return Double.parseDouble(str.substring(0, untilIndex + 1)); - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/conditional/DateRangeParser.java b/core/src/main/java/com/graphhopper/reader/osm/conditional/DateRangeParser.java index dc2d865dab7..eb2676e8a2e 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/conditional/DateRangeParser.java +++ b/core/src/main/java/com/graphhopper/reader/osm/conditional/DateRangeParser.java @@ -48,7 +48,7 @@ public class DateRangeParser implements ConditionalValueParser { private Calendar date; - public DateRangeParser() { + DateRangeParser() { this(createCalendar()); } @@ -104,9 +104,9 @@ static ParsedCalendar parseDateString(String dateString) throws ParseException { return parsedCalendar; } - public DateRange getRange(String dateRangeString) throws ParseException { + public static DateRange getRange(String dateRangeString) throws ParseException { if (dateRangeString == null || dateRangeString.isEmpty()) - throw new IllegalArgumentException("Passing empty Strings is not allowed"); + return null; String[] dateArr = dateRangeString.split("-"); if (dateArr.length > 2 || dateArr.length < 1) @@ -122,7 +122,11 @@ public DateRange getRange(String dateRangeString) throws ParseException { // to = new ParsedCalendar(from.parseType, (Calendar) from.parsedCalendar.clone()); to = parseDateString(dateArr[0]); - return new DateRange(from, to); + try { + return new DateRange(from, to); + } catch (IllegalArgumentException ex) { + return null; + } } @Override diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoder.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoder.java index 13b41b5b2c1..1177e353cd8 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoder.java +++ b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoder.java @@ -1,4 +1,20 @@ -// This software is released into the Public Domain. See copying.txt for details. +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.reader.osm.pbf; import com.carrotsearch.hppc.LongIndexedContainer; @@ -21,36 +37,43 @@ import java.util.zip.Inflater; /** - * Converts PBF block data into decoded entities ready to be passed into an Osmosis pipeline. This - * class is designed to be passed into a pool of worker threads to allow multi-threaded decoding. - *

- * - * @author Brett Henderson + * Synchronous PBF blob decoder that returns decoded entities directly. + * This is a refactored version of PbfBlobDecoder that doesn't use callbacks. */ -public class PbfBlobDecoder implements Runnable { +public class PbfBlobDecoder { private static final Logger log = LoggerFactory.getLogger(PbfBlobDecoder.class); - private final boolean checkData = false; + private static final boolean CHECK_DATA = false; + private final String blobType; private final byte[] rawBlob; - private final PbfBlobDecoderListener listener; - private List decodedEntities; private final SkipOptions skipOptions; + private List decodedEntities; - /** - * Creates a new instance. - *

- * - * @param blobType The type of blob. - * @param rawBlob The raw data of the blob. - * @param listener The listener for receiving decoding results. - */ - public PbfBlobDecoder(String blobType, byte[] rawBlob, PbfBlobDecoderListener listener, SkipOptions skipOptions) { + public PbfBlobDecoder(String blobType, byte[] rawBlob, SkipOptions skipOptions) { this.blobType = blobType; this.rawBlob = rawBlob; - this.listener = listener; this.skipOptions = skipOptions; } + /** + * Decode the blob and return the list of entities. + */ + public List decode() { + decodedEntities = new ArrayList<>(); + try { + if ("OSMHeader".equals(blobType)) { + processOsmHeader(readBlobContent()); + } else if ("OSMData".equals(blobType)) { + processOsmPrimitives(readBlobContent()); + } else if (log.isDebugEnabled()) { + log.debug("Skipping unrecognised blob type " + blobType); + } + } catch (IOException e) { + throw new RuntimeException("Unable to process PBF blob", e); + } + return decodedEntities; + } + private byte[] readBlobContent() throws IOException { Fileformat.Blob blob = Fileformat.Blob.parseFrom(rawBlob); byte[] blobData; @@ -80,20 +103,15 @@ private byte[] readBlobContent() throws IOException { private void processOsmHeader(byte[] data) throws InvalidProtocolBufferException { Osmformat.HeaderBlock header = Osmformat.HeaderBlock.parseFrom(data); - // Build the list of active and unsupported features in the file. List supportedFeatures = Arrays.asList("OsmSchema-V0.6", "DenseNodes"); List unsupportedFeatures = new ArrayList<>(); for (String feature : header.getRequiredFeaturesList()) { - if (supportedFeatures.contains(feature)) { - } else { + if (!supportedFeatures.contains(feature)) { unsupportedFeatures.add(feature); } } - // We can't continue if there are any unsupported features. We wait - // until now so that we can display all unsupported features instead of - // just the first one we encounter. - if (unsupportedFeatures.size() > 0) { + if (!unsupportedFeatures.isEmpty()) { throw new RuntimeException("PBF file contains unsupported features " + unsupportedFeatures); } @@ -101,32 +119,28 @@ private void processOsmHeader(byte[] data) throws InvalidProtocolBufferException long milliSecondDate = header.getOsmosisReplicationTimestamp(); fileheader.setTag("timestamp", Helper.createFormatter().format(new Date(milliSecondDate * 1000))); decodedEntities.add(fileheader); - - // Build a new bound object which corresponds to the header. -/* - Bound bound; - if (header.hasBbox()) { - HeaderBBox bbox = header.getBbox(); - bound = new Bound(bbox.getRight() * COORDINATE_SCALING_FACTOR, bbox.getLeft() * COORDINATE_SCALING_FACTOR, - bbox.getTop() * COORDINATE_SCALING_FACTOR, bbox.getBottom() * COORDINATE_SCALING_FACTOR, - header.getSource()); - } else { - bound = new Bound(header.getSource()); - } - - // Add the bound object to the results. - decodedEntities.add(new BoundContainer(bound)); - */ } - private Map buildTags(List keys, List values, PbfFieldDecoder fieldDecoder) { + private void processOsmPrimitives(byte[] data) throws InvalidProtocolBufferException { + Osmformat.PrimitiveBlock block = Osmformat.PrimitiveBlock.parseFrom(data); + PbfFieldDecoder fieldDecoder = new PbfFieldDecoder(block); - // Ensure parallel lists are of equal size. - if (checkData) { - if (keys.size() != values.size()) { - throw new RuntimeException("Number of tag keys (" + keys.size() + ") and tag values (" - + values.size() + ") don't match"); + for (Osmformat.PrimitiveGroup primitiveGroup : block.getPrimitivegroupList()) { + if (!skipOptions.isSkipNodes()) { + processNodes(primitiveGroup.getDense(), fieldDecoder); + processNodes(primitiveGroup.getNodesList(), fieldDecoder); } + if (!skipOptions.isSkipWays()) + processWays(primitiveGroup.getWaysList(), fieldDecoder); + if (!skipOptions.isSkipRelations()) + processRelations(primitiveGroup.getRelationsList(), fieldDecoder); + } + } + + private Map buildTags(List keys, List values, PbfFieldDecoder fieldDecoder) { + if (CHECK_DATA && keys.size() != values.size()) { + throw new RuntimeException("Number of tag keys (" + keys.size() + ") and tag values (" + + values.size() + ") don't match"); } Iterator keyIterator = keys.iterator(); @@ -146,12 +160,10 @@ private Map buildTags(List keys, List values, private void processNodes(List nodes, PbfFieldDecoder fieldDecoder) { for (Osmformat.Node node : nodes) { Map tags = buildTags(node.getKeysList(), node.getValsList(), fieldDecoder); - - ReaderNode osmNode = new ReaderNode(node.getId(), fieldDecoder.decodeLatitude(node - .getLat()), fieldDecoder.decodeLatitude(node.getLon())); + ReaderNode osmNode = new ReaderNode(node.getId(), + fieldDecoder.decodeLatitude(node.getLat()), + fieldDecoder.decodeLatitude(node.getLon())); osmNode.setTags(tags); - - // Add the bound object to the results. decodedEntities.add(osmNode); } } @@ -161,89 +173,44 @@ private void processNodes(Osmformat.DenseNodes nodes, PbfFieldDecoder fieldDecod List latList = nodes.getLatList(); List lonList = nodes.getLonList(); - // Ensure parallel lists are of equal size. - if (checkData) { - if ((idList.size() != latList.size()) || (idList.size() != lonList.size())) { - throw new RuntimeException("Number of ids (" + idList.size() + "), latitudes (" + latList.size() - + "), and longitudes (" + lonList.size() + ") don't match"); - } + if (CHECK_DATA && ((idList.size() != latList.size()) || (idList.size() != lonList.size()))) { + throw new RuntimeException("Number of ids (" + idList.size() + "), latitudes (" + latList.size() + + "), and longitudes (" + lonList.size() + ") don't match"); } Iterator keysValuesIterator = nodes.getKeysValsList().iterator(); - /* - Osmformat.DenseInfo denseInfo; - if (nodes.hasDenseinfo()) { - denseInfo = nodes.getDenseinfo(); - } else { - denseInfo = null; - } - */ long nodeId = 0; long latitude = 0; long longitude = 0; -// int userId = 0; -// int userSid = 0; -// long timestamp = 0; -// long changesetId = 0; + for (int i = 0; i < idList.size(); i++) { - // Delta decode node fields. nodeId += idList.get(i); latitude += latList.get(i); longitude += lonList.get(i); - /* - if (denseInfo != null) { - // Delta decode dense info fields. - userId += denseInfo.getUid(i); - userSid += denseInfo.getUserSid(i); - timestamp += denseInfo.getTimestamp(i); - changesetId += denseInfo.getChangeset(i); - - // Build the user, but only if one exists. - OsmUser user; - if (userId >= 0) { - user = new OsmUser(userId, fieldDecoder.decodeString(userSid)); - } else { - user = OsmUser.NONE; - } - - entityData = new CommonEntityData(nodeId, denseInfo.getVersion(i), - fieldDecoder.decodeTimestamp(timestamp), user, changesetId); - } else { - entityData = new CommonEntityData(nodeId, EMPTY_VERSION, EMPTY_TIMESTAMP, OsmUser.NONE, - EMPTY_CHANGESET); - } - */ - // Build the tags. The key and value string indexes are sequential - // in the same PBF array. Each set of tags is delimited by an index - // with a value of 0. Map tags = null; while (keysValuesIterator.hasNext()) { int keyIndex = keysValuesIterator.next(); if (keyIndex == 0) { break; } - if (checkData) { - if (!keysValuesIterator.hasNext()) { - throw new RuntimeException( - "The PBF DenseInfo keys/values list contains a key with no corresponding value."); - } + if (CHECK_DATA && !keysValuesIterator.hasNext()) { + throw new RuntimeException( + "The PBF DenseInfo keys/values list contains a key with no corresponding value."); } int valueIndex = keysValuesIterator.next(); if (tags == null) { - // divide by 2 as key&value, multiple by 2 because of the better approximation tags = new HashMap<>(Math.max(3, 2 * (nodes.getKeysValsList().size() / 2) / idList.size())); } - tags.put(fieldDecoder.decodeString(keyIndex), fieldDecoder.decodeString(valueIndex)); } - ReaderNode node = new ReaderNode(nodeId, fieldDecoder.decodeLatitude(latitude), fieldDecoder.decodeLongitude(longitude)); + ReaderNode node = new ReaderNode(nodeId, + fieldDecoder.decodeLatitude(latitude), + fieldDecoder.decodeLongitude(longitude)); node.setTags(tags); - - // Add the bound object to the results. decodedEntities.add(node); } } @@ -254,9 +221,6 @@ private void processWays(List ways, PbfFieldDecoder fieldDecoder) ReaderWay osmWay = new ReaderWay(way.getId()); osmWay.setTags(tags); - // Build up the list of way nodes for the way. The node ids are - // delta encoded meaning that each id is stored as a delta against - // the previous one. long nodeId = 0; LongIndexedContainer wayNodes = osmWay.getNodes(); for (long nodeIdOffset : way.getRefsList()) { @@ -268,25 +232,33 @@ private void processWays(List ways, PbfFieldDecoder fieldDecoder) } } + private void processRelations(List relations, PbfFieldDecoder fieldDecoder) { + for (Osmformat.Relation relation : relations) { + Map tags = buildTags(relation.getKeysList(), relation.getValsList(), fieldDecoder); + + ReaderRelation osmRelation = new ReaderRelation(relation.getId()); + osmRelation.setTags(tags); + + buildRelationMembers(osmRelation, relation.getMemidsList(), relation.getRolesSidList(), + relation.getTypesList(), fieldDecoder); + + decodedEntities.add(osmRelation); + } + } + private void buildRelationMembers(ReaderRelation relation, - List memberIds, List memberRoles, List memberTypes, + List memberIds, List memberRoles, + List memberTypes, PbfFieldDecoder fieldDecoder) { - - // Ensure parallel lists are of equal size. - if (checkData) { - if ((memberIds.size() != memberRoles.size()) || (memberIds.size() != memberTypes.size())) { - throw new RuntimeException("Number of member ids (" + memberIds.size() + "), member roles (" - + memberRoles.size() + "), and member types (" + memberTypes.size() + ") don't match"); - } + if (CHECK_DATA && ((memberIds.size() != memberRoles.size()) || (memberIds.size() != memberTypes.size()))) { + throw new RuntimeException("Number of member ids (" + memberIds.size() + "), member roles (" + + memberRoles.size() + "), and member types (" + memberTypes.size() + ") don't match"); } Iterator memberIdIterator = memberIds.iterator(); Iterator memberRoleIterator = memberRoles.iterator(); Iterator memberTypeIterator = memberTypes.iterator(); - // Build up the list of relation members for the way. The member ids are - // delta encoded meaning that each id is stored as a delta against - // the previous one. long refId = 0; while (memberIdIterator.hasNext()) { Osmformat.Relation.MemberType memberType = memberTypeIterator.next(); @@ -298,73 +270,10 @@ private void buildRelationMembers(ReaderRelation relation, } else if (memberType == Osmformat.Relation.MemberType.RELATION) { entityType = ReaderElement.Type.RELATION; } - if (checkData) { - if (entityType == ReaderElement.Type.NODE && memberType != Osmformat.Relation.MemberType.NODE) { - throw new RuntimeException("Member type of " + memberType + " is not supported."); - } - } - ReaderRelation.Member member = new ReaderRelation.Member(entityType, refId, fieldDecoder.decodeString(memberRoleIterator.next())); + ReaderRelation.Member member = new ReaderRelation.Member(entityType, refId, + fieldDecoder.decodeString(memberRoleIterator.next())); relation.add(member); } } - - private void processRelations(List relations, PbfFieldDecoder fieldDecoder) { - for (Osmformat.Relation relation : relations) { - Map tags = buildTags(relation.getKeysList(), relation.getValsList(), fieldDecoder); - - ReaderRelation osmRelation = new ReaderRelation(relation.getId()); - osmRelation.setTags(tags); - - buildRelationMembers(osmRelation, relation.getMemidsList(), relation.getRolesSidList(), - relation.getTypesList(), fieldDecoder); - - // Add the bound object to the results. - decodedEntities.add(osmRelation); - } - } - - private void processOsmPrimitives(byte[] data) throws InvalidProtocolBufferException { - Osmformat.PrimitiveBlock block = Osmformat.PrimitiveBlock.parseFrom(data); - PbfFieldDecoder fieldDecoder = new PbfFieldDecoder(block); - - for (Osmformat.PrimitiveGroup primitiveGroup : block.getPrimitivegroupList()) { - if (!skipOptions.isSkipNodes()) { - processNodes(primitiveGroup.getDense(), fieldDecoder); - processNodes(primitiveGroup.getNodesList(), fieldDecoder); - } - if (!skipOptions.isSkipWays()) - processWays(primitiveGroup.getWaysList(), fieldDecoder); - if (!skipOptions.isSkipRelations()) - processRelations(primitiveGroup.getRelationsList(), fieldDecoder); - } - } - - private void runAndTrapExceptions() { - try { - decodedEntities = new ArrayList<>(); - if ("OSMHeader".equals(blobType)) { - processOsmHeader(readBlobContent()); - - } else if ("OSMData".equals(blobType)) { - processOsmPrimitives(readBlobContent()); - - } else if (log.isDebugEnabled()) - log.debug("Skipping unrecognised blob type " + blobType); - } catch (IOException e) { - throw new RuntimeException("Unable to process PBF blob", e); - } - } - - @Override - public void run() { - try { - runAndTrapExceptions(); - listener.complete(decodedEntities); - - } catch (RuntimeException e) { - // exception is properly rethrown in PbfDecoder.sendResultsToSink - listener.error(e); - } - } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoderListener.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoderListener.java deleted file mode 100644 index 423608e9c50..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobDecoderListener.java +++ /dev/null @@ -1,27 +0,0 @@ -// This software is released into the Public Domain. See copying.txt for details. -package com.graphhopper.reader.osm.pbf; - -import com.graphhopper.reader.ReaderElement; - -import java.util.List; - -/** - * Instances of this interface are used to receive results from PBFBlobDecoder. - *

- * - * @author Brett Henderson - */ -public interface PbfBlobDecoderListener { - /** - * Provides the listener with the list of decoded entities. - *

- * - * @param decodedEntities The decoded entities. - */ - void complete(List decodedEntities); - - /** - * Notifies the listener that an error occurred during processing. - */ - void error(Exception ex); -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobResult.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobResult.java deleted file mode 100644 index 78f98a27cd7..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfBlobResult.java +++ /dev/null @@ -1,84 +0,0 @@ -// This software is released into the Public Domain. See copying.txt for details. -package com.graphhopper.reader.osm.pbf; - -import com.graphhopper.reader.ReaderElement; - -import java.util.List; - -/** - * Stores the results for a decoded Blob. - *

- * - * @author Brett Henderson - */ -public class PbfBlobResult { - private List entities; - private boolean complete; - private boolean success; - private Exception ex; - - /** - * Creates a new instance. - */ - public PbfBlobResult() { - complete = false; - success = false; - ex = new RuntimeException("no success result stored"); - } - - /** - * Stores the results of a successful blob decoding operation. - *

- * - * @param decodedEntities The entities from the blob. - */ - public void storeSuccessResult(List decodedEntities) { - entities = decodedEntities; - complete = true; - success = true; - } - - /** - * Stores a failure result for a blob decoding operation. - */ - public void storeFailureResult(Exception ex) { - complete = true; - success = false; - this.ex = ex; - } - - /** - * Gets the complete flag. - *

- * - * @return True if complete. - */ - public boolean isComplete() { - return complete; - } - - /** - * Gets the success flag. This is only valid after complete becomes true. - *

- * - * @return True if successful. - */ - public boolean isSuccess() { - return success; - } - - public Exception getException() { - return ex; - } - - /** - * Gets the entities decoded from the blob. This is only valid after complete becomes true, and - * if success is true. - *

- * - * @return The list of decoded entities. - */ - public List getEntities() { - return entities; - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfDecoder.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfDecoder.java deleted file mode 100644 index 396c0c505ac..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfDecoder.java +++ /dev/null @@ -1,168 +0,0 @@ -// This software is released into the Public Domain. See copying.txt for details. -package com.graphhopper.reader.osm.pbf; - -import com.graphhopper.reader.ReaderElement; -import com.graphhopper.reader.osm.SkipOptions; - -import java.util.LinkedList; -import java.util.List; -import java.util.Queue; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * Decodes all blocks from a PBF stream using worker threads, and passes the results to the - * downstream sink. - *

- * - * @author Brett Henderson - */ -public class PbfDecoder { - private final PbfStreamSplitter streamSplitter; - private final ExecutorService executorService; - private final int maxPendingBlobs; - private final Sink sink; - private final Lock lock; - private final Condition dataWaitCondition; - private final Queue blobResults; - private final SkipOptions skipOptions; - - /** - * Creates a new instance. - *

- * - * @param streamSplitter The PBF stream splitter providing the source of blobs to be decoded. - * @param executorService The executor service managing the thread pool. - * @param maxPendingBlobs The maximum number of blobs to have in progress at any point in time. - * @param sink The sink to send all decoded entities to. - */ - public PbfDecoder(PbfStreamSplitter streamSplitter, ExecutorService executorService, int maxPendingBlobs, - Sink sink, SkipOptions skipOptions) { - this.streamSplitter = streamSplitter; - this.executorService = executorService; - this.maxPendingBlobs = maxPendingBlobs; - this.sink = sink; - this.skipOptions = skipOptions; - - // Create the thread synchronisation primitives. - lock = new ReentrantLock(); - dataWaitCondition = lock.newCondition(); - - // Create the queue of blobs being decoded. - blobResults = new LinkedList<>(); - } - - /** - * Any thread can call this method when they wish to wait until an update has been performed by - * another thread. - */ - private void waitForUpdate() { - try { - dataWaitCondition.await(); - } catch (InterruptedException e) { - throw new RuntimeException("Thread was interrupted.", e); - } - } - - /** - * Any thread can call this method when they wish to signal another thread that an update has - * occurred. - */ - private void signalUpdate() { - dataWaitCondition.signal(); - } - - private void sendResultsToSink(int targetQueueSize) { - while (blobResults.size() > targetQueueSize) { - // Get the next result from the queue and wait for it to complete. - PbfBlobResult blobResult = blobResults.remove(); - while (!blobResult.isComplete()) { - // The thread hasn't finished processing yet so wait for an - // update from another thread before checking again. - waitForUpdate(); - } - - if (!blobResult.isSuccess()) { - throw new RuntimeException("A PBF decoding worker thread failed, aborting.", blobResult.getException()); - } - - // Send the processed entities to the sink. We can release the lock - // for the duration of processing to allow worker threads to post - // their results. - lock.unlock(); - try { - for (ReaderElement entity : blobResult.getEntities()) { - sink.process(entity); - } - } finally { - lock.lock(); - } - } - } - - private void processBlobs() { - // Process until the PBF stream is exhausted. - while (streamSplitter.hasNext()) { - // Obtain the next raw blob from the PBF stream. - PbfRawBlob rawBlob = streamSplitter.next(); - - // Create the result object to capture the results of the decoded - // blob and add it to the blob results queue. - final PbfBlobResult blobResult = new PbfBlobResult(); - blobResults.add(blobResult); - - // Create the listener object that will update the blob results - // based on an event fired by the blob decoder. - PbfBlobDecoderListener decoderListener = new PbfBlobDecoderListener() { - @Override - public void error(Exception ex) { - lock.lock(); - try { - // System.out.println("ERROR: " + new Date()); - blobResult.storeFailureResult(ex); - signalUpdate(); - - } finally { - lock.unlock(); - } - } - - @Override - public void complete(List decodedEntities) { - lock.lock(); - try { - blobResult.storeSuccessResult(decodedEntities); - signalUpdate(); - - } finally { - lock.unlock(); - } - } - }; - - // Create the blob decoder itself and execute it on a worker thread. - PbfBlobDecoder blobDecoder = new PbfBlobDecoder(rawBlob.getType(), rawBlob.getData(), decoderListener, skipOptions); - executorService.execute(blobDecoder); - - // If the number of pending blobs has reached capacity we must begin - // sending results to the sink. This method will block until blob - // decoding is complete. - sendResultsToSink(maxPendingBlobs - 1); - } - - // There are no more entities available in the PBF stream, so send all remaining data to the sink. - sendResultsToSink(0); - } - - public void run() { - lock.lock(); - try { - processBlobs(); - - } finally { - lock.unlock(); - } - } -} diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfReader.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfReader.java index 02313f5f2bc..ca54f6b64c9 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfReader.java +++ b/core/src/main/java/com/graphhopper/reader/osm/pbf/PbfReader.java @@ -1,68 +1,198 @@ -// This software is released into the Public Domain. See copying.txt for details. +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.reader.osm.pbf; +import com.graphhopper.reader.ReaderElement; +import com.graphhopper.reader.osm.OSMInput; import com.graphhopper.reader.osm.SkipOptions; import java.io.DataInputStream; +import java.io.IOException; import java.io.InputStream; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.*; +import java.util.concurrent.*; /** - * An OSM data source reading from a PBF file. The entire contents of the file are read. + * Pipelined PBF reader: blobs.map(decode).flatten *

- * - * @author Brett Henderson + * - Reader thread: splits stream into blobs + * - Coordinator thread: submits blobs to workers, queues decoded results in order + * - Worker threads: decode blobs in parallel + * - Consumer: iterates through queued results via getNext() */ -public class PbfReader implements Runnable { - private Throwable throwable; +public class PbfReader implements OSMInput { + private static final PbfRawBlob END_OF_STREAM = new PbfRawBlob("END", new byte[0]); + private final InputStream inputStream; - private final Sink sink; private final int workers; private final SkipOptions skipOptions; - /** - * Creates a new instance. - *

- * - * @param in The file to read. - * @param workers The number of worker threads for decoding PBF blocks. - */ - public PbfReader(InputStream in, Sink sink, int workers, SkipOptions skipOptions) { + private final BlockingQueue blobQueue; + private final BlockingQueue> resultQueue; + + private volatile Throwable readerException; + private volatile Throwable coordinatorException; + private volatile boolean coordinatorDone; + private boolean eof; + + private Iterator currentBatch = Collections.emptyIterator(); + private Thread readerThread; + private Thread coordinatorThread; + private ExecutorService decoderExecutor; + + public PbfReader(InputStream in, int workers, SkipOptions skipOptions) { this.inputStream = in; - this.sink = sink; this.workers = workers; this.skipOptions = skipOptions; + this.blobQueue = new ArrayBlockingQueue<>(workers * 2); + this.resultQueue = new LinkedBlockingQueue<>(workers * 2); + } + + public PbfReader start() { + decoderExecutor = Executors.newFixedThreadPool(workers); + readerThread = new Thread(this::runReader, "PBF-IO-Reader"); + coordinatorThread = new Thread(this::runCoordinator, "PBF-Coordinator"); + readerThread.start(); + coordinatorThread.start(); + return this; } @Override - public void run() { - ExecutorService executorService = Executors.newFixedThreadPool(workers); - // Create a stream splitter to break the PBF stream into blobs. - PbfStreamSplitter streamSplitter = new PbfStreamSplitter(new DataInputStream(inputStream)); + public ReaderElement getNext() { + if (eof) + throw new IllegalStateException("EOF reached"); + + while (!currentBatch.hasNext()) { + if (coordinatorDone && resultQueue.isEmpty()) { + checkExceptions(); + eof = true; + return null; + } + try { + List batch = resultQueue.poll(100, TimeUnit.MILLISECONDS); + if (batch != null) { + currentBatch = batch.iterator(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + eof = true; + return null; + } + } + return currentBatch.next(); + } + + private void runReader() { + try { + PbfStreamSplitter splitter = new PbfStreamSplitter(new DataInputStream(inputStream)); + try { + while (splitter.hasNext()) { + blobQueue.put(splitter.next()); + } + } finally { + splitter.release(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } catch (Throwable t) { + readerException = t; + } finally { + try { + blobQueue.put(END_OF_STREAM); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + } + private void runCoordinator() { try { - // Process all blobs of data in the stream using threads from the - // executor service. We allow the decoder to issue an extra blob - // than there are workers to ensure there is another blob - // immediately ready for processing when a worker thread completes. - // The main thread is responsible for splitting blobs from the - // request stream, and sending decoded entities to the sink. - PbfDecoder pbfDecoder = new PbfDecoder(streamSplitter, executorService, workers + 1, sink, skipOptions); - pbfDecoder.run(); + Deque>> pending = new ArrayDeque<>(); + int maxPending = workers + 1; + + while (true) { + // Fill pending queue + while (pending.size() < maxPending) { + PbfRawBlob blob = blobQueue.poll(50, TimeUnit.MILLISECONDS); + if (blob == null) { + checkReaderException(); + break; + } + if (blob == END_OF_STREAM) { + drainAll(pending); + return; + } + pending.addLast(decoderExecutor.submit(() -> + new PbfBlobDecoder(blob.getType(), blob.getData(), skipOptions).decode())); + } + + checkReaderException(); + // Send completed results (in order) + while (!pending.isEmpty() && pending.peekFirst().isDone()) { + resultQueue.put(pending.pollFirst().get()); + } + + // If full, block on first result + if (pending.size() >= maxPending && !pending.isEmpty()) { + resultQueue.put(pending.pollFirst().get()); + } + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); } catch (Throwable t) { - // properly propagate exception inside Thread, #2269 - throwable = t; + coordinatorException = t; } finally { - sink.complete(); - executorService.shutdownNow(); - streamSplitter.release(); + coordinatorDone = true; + decoderExecutor.shutdownNow(); + } + } + + private void drainAll(Deque>> pending) + throws ExecutionException, InterruptedException { + while (!pending.isEmpty()) { + resultQueue.put(pending.pollFirst().get()); } } - public void close() { - if (throwable != null) - throw new RuntimeException("Unable to read PBF file.", throwable); + private void checkReaderException() { + if (readerException != null) { + throw new RuntimeException("PBF reader thread failed", readerException); + } + } + + private void checkExceptions() { + if (readerException != null) { + throw new RuntimeException("Unable to read PBF file.", readerException); + } + if (coordinatorException != null) { + throw new RuntimeException("Unable to read PBF file.", coordinatorException); + } + } + + @Override + public void close() throws IOException { + checkExceptions(); + eof = true; + if (readerThread != null && readerThread.isAlive()) + readerThread.interrupt(); + if (coordinatorThread != null && coordinatorThread.isAlive()) + coordinatorThread.interrupt(); + inputStream.close(); } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/pbf/Sink.java b/core/src/main/java/com/graphhopper/reader/osm/pbf/Sink.java deleted file mode 100644 index 1c1841273e9..00000000000 --- a/core/src/main/java/com/graphhopper/reader/osm/pbf/Sink.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.pbf; - -import com.graphhopper.reader.ReaderElement; - -/** - * @author Nop - */ -public interface Sink { - void process(ReaderElement item); - - void complete(); -} diff --git a/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java index e9c9ad76eb4..2fdc8995ef4 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java @@ -233,7 +233,7 @@ protected void updateEntry(SPTEntry entry, int edge, int adjNode, int incEdge, d } protected boolean accept(RoutingCHEdgeIteratorState edge, SPTEntry currEdge, boolean reverse) { - // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable, + // for edge-based traversal we leave it for calcTurnWeight to decide whether or not a u-turn is acceptable, // but for node-based traversal we exclude such a turn for performance reasons already here if (!traversalMode.isEdgeBased() && edge.getEdge() == getIncomingEdge(currEdge)) return false; @@ -247,7 +247,7 @@ protected double calcWeight(RoutingCHEdgeIteratorState iter, SPTEntry currEdge, @Override protected double getInEdgeWeight(SPTEntry entry) { - return graph.getEdgeIteratorState(getIncomingEdge(entry), entry.adjNode).getWeight(false); + throw new UnsupportedOperationException(); } @Override @@ -292,10 +292,6 @@ public boolean accept(RoutingCHEdgeIteratorState edgeState) { if (base >= maxNodes || adj >= maxNodes) return true; - // minor performance improvement: shortcuts in wrong direction are disconnected, so no need to exclude them - if (edgeState.isShortcut()) - return true; - return graph.getLevel(base) <= graph.getLevel(adj); } } diff --git a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java index bad1d20bf47..19d6331fca2 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java @@ -18,8 +18,10 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntObjectMap; +import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.TraversalMode; +import com.graphhopper.routing.weighting.QueryGraphWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; import com.graphhopper.storage.NodeAccess; @@ -48,6 +50,8 @@ public abstract class AbstractNonCHBidirAlgo extends AbstractBidirAlgo implement public AbstractNonCHBidirAlgo(Graph graph, Weighting weighting, TraversalMode tMode) { super(tMode); + if (graph instanceof QueryGraph && !(weighting instanceof QueryGraphWeighting)) + throw new IllegalStateException("Weighting must use QueryGraphWeighting"); this.weighting = weighting; if (weighting.hasTurnCosts() && !tMode.isEdgeBased()) throw new IllegalStateException("Weightings supporting turn costs cannot be used with node-based traversal mode"); @@ -211,7 +215,7 @@ protected Path extractPath() { } protected boolean accept(EdgeIteratorState iter, int prevOrNextEdgeId) { - // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable, + // for edge-based traversal we leave it for calcTurnWeight to decide whether or not a u-turn is acceptable, // but for node-based traversal we exclude such a turn for performance reasons already here if (!traversalMode.isEdgeBased() && iter.getEdge() == prevOrNextEdgeId) return false; diff --git a/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java b/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java index 7fd22c7c494..dc0bd05344e 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java @@ -17,7 +17,9 @@ */ package com.graphhopper.routing; +import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.util.TraversalMode; +import com.graphhopper.routing.weighting.QueryGraphWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; import com.graphhopper.storage.NodeAccess; @@ -49,6 +51,8 @@ public abstract class AbstractRoutingAlgorithm implements RoutingAlgorithm { public AbstractRoutingAlgorithm(Graph graph, Weighting weighting, TraversalMode traversalMode) { if (weighting.hasTurnCosts() && !traversalMode.isEdgeBased()) throw new IllegalStateException("Weightings supporting turn costs cannot be used with node-based traversal mode"); + if (graph instanceof QueryGraph && !(weighting instanceof QueryGraphWeighting)) + throw new IllegalStateException("Weighting must use QueryGraphWeighting"); this.weighting = weighting; this.traversalMode = traversalMode; this.graph = graph; @@ -67,7 +71,7 @@ public void setTimeoutMillis(long timeoutMillis) { } protected boolean accept(EdgeIteratorState iter, int prevOrNextEdgeId) { - // for edge-based traversal we leave it for TurnWeighting to decide whether or not a u-turn is acceptable, + // for edge-based traversal we leave it for calcTurnWeight to decide whether or not a u-turn is acceptable, // but for node-based traversal we exclude such a turn for performance reasons already here return traversalMode.isEdgeBased() || iter.getEdge() != prevOrNextEdgeId; } diff --git a/core/src/main/java/com/graphhopper/routing/AlternativeRoute.java b/core/src/main/java/com/graphhopper/routing/AlternativeRoute.java index 605fbc44ef1..fefad3ad6a4 100644 --- a/core/src/main/java/com/graphhopper/routing/AlternativeRoute.java +++ b/core/src/main/java/com/graphhopper/routing/AlternativeRoute.java @@ -132,6 +132,8 @@ public List calcAlternatives(int from, int to) { @Override public List calcPaths(int from, int to) { + checkAlreadyRun(); + setupFinishTime(); List alternatives = calcAlternatives(from, to); List paths = new ArrayList<>(alternatives.size()); for (AlternativeInfo a : alternatives) { @@ -262,7 +264,8 @@ public boolean apply(final int traversalId, final SPTEntry fromSPTEntry) { return true; // (1) skip too long paths - final double weight = fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath(); + final double weight = fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath() + + weighting.calcTurnWeight(fromSPTEntry.edge, fromSPTEntry.adjNode, toSPTEntry.edge); if (weight > maxWeight) return true; @@ -341,7 +344,7 @@ public boolean apply(final int traversalId, final SPTEntry fromSPTEntry) { Collections.sort(alternatives, ALT_COMPARATOR); if (alternatives.get(0) != bestAlt) - throw new IllegalStateException("best path should be always first entry"); + throw new IllegalStateException("best path should be always first entry " + bestAlt.path.getWeight() + " vs " + alternatives.get(0).path.getWeight()); if (alternatives.size() > maxPaths) alternatives.subList(maxPaths, alternatives.size()).clear(); diff --git a/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java b/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java index fd199d57ae9..fa20a1a1de6 100644 --- a/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java +++ b/core/src/main/java/com/graphhopper/routing/AlternativeRouteCH.java @@ -101,7 +101,7 @@ List calcAlternatives(final int s, final int t) { } PotentialAlternativeInfo potentialAlternativeInfo = new PotentialAlternativeInfo(); potentialAlternativeInfo.v = v; - potentialAlternativeInfo.weight = 2 * (fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath()) + preliminaryShare; + potentialAlternativeInfo.weight = 0.2 * (fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath()) + preliminaryShare; potentialAlternativeInfos.add(potentialAlternativeInfo); return true; }); @@ -235,7 +235,7 @@ private static Path concat(Graph graph, Path svPath, Path vtPath) { path.setFromNode(svPath.calcNodes().get(0)); path.setEndNode(vtPath.getEndNode()); path.setWeight(svPath.getWeight() + vtPath.getWeight()); - path.setDistance(svPath.getDistance() + vtPath.getDistance()); + path.addDistance_mm(svPath.getDistance_mm() + vtPath.getDistance_mm()); path.addTime(svPath.getTime() + vtPath.getTime()); path.setFound(true); return path; diff --git a/core/src/main/java/com/graphhopper/routing/AlternativeRouteEdgeCH.java b/core/src/main/java/com/graphhopper/routing/AlternativeRouteEdgeCH.java index 6c7bfaca665..1f4d294da8b 100644 --- a/core/src/main/java/com/graphhopper/routing/AlternativeRouteEdgeCH.java +++ b/core/src/main/java/com/graphhopper/routing/AlternativeRouteEdgeCH.java @@ -21,6 +21,7 @@ import com.carrotsearch.hppc.IntIndexedContainer; import com.carrotsearch.hppc.cursors.IntCursor; import com.carrotsearch.hppc.predicates.IntObjectPredicate; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; import com.graphhopper.storage.RoutingCHGraph; import com.graphhopper.util.EdgeIteratorState; @@ -110,7 +111,7 @@ List calcAlternatives(final int s, final int t) { PotentialAlternativeInfo potentialAlternativeInfo = new PotentialAlternativeInfo(); potentialAlternativeInfo.v = fromSPTEntry.adjNode; potentialAlternativeInfo.edgeIn = getIncomingEdge(fromSPTEntry); - potentialAlternativeInfo.weight = 2 * (fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath()) + preliminaryShare; + potentialAlternativeInfo.weight = 0.2 * (fromSPTEntry.getWeightOfVisitedPath() + toSPTEntry.getWeightOfVisitedPath()) + preliminaryShare; potentialAlternativeInfos.add(potentialAlternativeInfo); return true; }); @@ -131,7 +132,12 @@ List calcAlternatives(final int s, final int t) { DijkstraBidirectionEdgeCHNoSOD vtRouter = new DijkstraBidirectionEdgeCHNoSOD(graph); final Path uvtPath = vtRouter.calcPath(u, t, tailSv, ANY_EDGE); - Path path = concat(graph.getBaseGraph(), suvPath, uvtPath); + if (!uvtPath.isFound()) + // we were looking for the s->u->v->(x->)t path, but there might be a turn restriction + // at u->v->x in which case uvtPath is not found. If we do not stop here we might return + // an alternative that does not even reach t, and has a lower weight than the best path. + continue; + Path path = concat(graph.getBaseGraph(), graph.getBaseGraph().wrapWeighting(graph.getWeighting()), suvPath, uvtPath); extraVisitedNodes += vtRouter.getVisitedNodes(); double sharedDistanceWithShortest = sharedDistanceWithShortest(path); @@ -237,22 +243,22 @@ private EdgeIteratorState getNextNodeTMetersAway(Path path, int vIndex, double T return edges.get(i - 1); } - private static Path concat(Graph graph, Path suvPath, Path uvtPath) { + private static Path concat(Graph graph, Weighting weighting, Path suvPath, Path uvtPath) { assert suvPath.isFound(); assert uvtPath.isFound(); Path path = new Path(graph); - path.setFromNode(suvPath.calcNodes().get(0)); + path.setFromNode(suvPath.getFromNode()); path.getEdges().addAll(suvPath.getEdges()); + if (uvtPath.getEdges().isEmpty()) + throw new IllegalStateException("uvtPath.getEdges() should not be empty"); Iterator uvtPathI = uvtPath.getEdges().iterator(); - if (!uvtPathI.hasNext()) { // presumably v == t, has been known to happen, no test yet - return suvPath; - } - uvtPathI.next(); // skip u-v edge + int uvEdge = uvtPathI.next().value;// skip u-v edge uvtPathI.forEachRemaining(edge -> path.addEdge(edge.value)); + EdgeIteratorState vuEdgeState = graph.getEdgeIteratorState(uvEdge, uvtPath.getFromNode()); path.setEndNode(uvtPath.getEndNode()); - path.setWeight(suvPath.getWeight() + uvtPath.getWeight()); - path.setDistance(suvPath.getDistance() + uvtPath.getDistance()); - path.addTime(suvPath.getTime() + uvtPath.getTime()); + path.setWeight(suvPath.getWeight() + uvtPath.getWeight() - weighting.calcEdgeWeight(vuEdgeState, true)); + path.addDistance_mm(suvPath.getDistance_mm() + uvtPath.getDistance_mm() - vuEdgeState.getDistance_mm()); + path.addTime(suvPath.getTime() + uvtPath.getTime() - weighting.calcEdgeMillis(vuEdgeState, true)); path.setFound(true); return path; } @@ -274,6 +280,11 @@ public static class PotentialAlternativeInfo { public int v; public int edgeIn; double weight; + + @Override + public String toString() { + return "node=" + v + ", edgeIn=" + edgeIn + ", weight=" + weight; + } } public static class AlternativeInfo { diff --git a/core/src/main/java/com/graphhopper/routing/CurbsideAutoHelper.java b/core/src/main/java/com/graphhopper/routing/CurbsideAutoHelper.java new file mode 100644 index 00000000000..f934f6bc532 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/CurbsideAutoHelper.java @@ -0,0 +1,50 @@ +package com.graphhopper.routing; + +import com.graphhopper.routing.ev.Country; +import com.graphhopper.routing.ev.EnumEncodedValue; +import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.util.DirectedEdgeFilter; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.storage.index.Snap; +import com.graphhopper.util.EdgeIteratorState; + +import java.util.function.Function; + +import static com.graphhopper.util.Parameters.Curbsides.*; + +public class CurbsideAutoHelper { + + /** + * Resolve AUTO curbsides based on road class and country. It will return ANY for one-ways. + * Later maybe lanes and max_speed. + * @param edgeFilter required to determine if one-way. + * @param em retrieves road class and country encoded values from this look up + * @return a function that takes the Snap as input and returns ANY, RIGHT or LEFT. I.e. resolves AUTO. + */ + public static Function createResolver(final DirectedEdgeFilter edgeFilter, final EncodingManager em) { + EnumEncodedValue roadClassEnc = em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EnumEncodedValue countryEnc = em.hasEncodedValue(Country.KEY) ? em.getEnumEncodedValue(Country.KEY, Country.class) : null; + + return snap -> { + EdgeIteratorState edge = snap.getClosestEdge(); + + // do not force curbside for 'smaller roads' (for now not configurable) + RoadClass roadClass = edge.get(roadClassEnc); + if (roadClass != RoadClass.PRIMARY && roadClass != RoadClass.SECONDARY && roadClass != RoadClass.TRUNK) + return CURBSIDE_ANY; + + // do not force curbside for one-ways + if (!edgeFilter.accept(edge, false) || !edgeFilter.accept(edge, true)) + return CURBSIDE_ANY; + + // do not force curbside for 'smaller roads' regarding lanes and max_speed + // note: lane count in OSM is for the entire road - not just for one direction + // TODO LATER: 'lanes' is 1 if OSM tag is missing, which might be rather misleading in this case +// if (lanesEnc != null && edge.get(lanesEnc) < 2 && maxSpeedEnc != null && edge.get(maxSpeedEnc) <= 50) +// return CURBSIDE_ANY; + + // could be different per point + return countryEnc == null || edge.get(countryEnc).isRightHandTraffic() ? CURBSIDE_RIGHT : CURBSIDE_LEFT; + }; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/DefaultBidirPathExtractor.java b/core/src/main/java/com/graphhopper/routing/DefaultBidirPathExtractor.java index 14e6c65b355..725e62c94af 100644 --- a/core/src/main/java/com/graphhopper/routing/DefaultBidirPathExtractor.java +++ b/core/src/main/java/com/graphhopper/routing/DefaultBidirPathExtractor.java @@ -109,7 +109,7 @@ protected void onBwdTreeRoot(int node) { protected void onEdge(int edge, int adjNode, boolean reverse, int prevOrNextEdge) { EdgeIteratorState edgeState = graph.getEdgeIteratorState(edge, adjNode); - path.addDistance(edgeState.getDistance()); + path.addDistance_mm(edgeState.getDistance_mm()); path.addTime(GHUtility.calcMillisWithTurnMillis(weighting, edgeState, reverse, prevOrNextEdge)); path.addEdge(edge); } diff --git a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java index accbf5653d8..761379a6127 100644 --- a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java +++ b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java @@ -19,21 +19,21 @@ package com.graphhopper.routing; import com.graphhopper.config.Profile; -import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.TurnRestriction; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.VehicleEncodedValues; import com.graphhopper.routing.weighting.DefaultTurnCostProvider; import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.routing.weighting.custom.CustomWeighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.CustomModel; import com.graphhopper.util.PMap; import com.graphhopper.util.Parameters; +import com.graphhopper.util.TurnCostsConfig; import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; -import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS; +import static com.graphhopper.routing.weighting.custom.CustomModelParser.createWeightingParameters; import static com.graphhopper.util.Helper.toLowerCase; public class DefaultWeightingFactory implements WeightingFactory { @@ -55,35 +55,36 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis hints.putAll(profile.getHints()); hints.putAll(requestHints); - final String vehicle = profile.getVehicle(); - TurnCostProvider turnCostProvider; - if (profile.isTurnCosts() && !disableTurnCosts) { - BooleanEncodedValue turnRestrictionEnc = encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(vehicle)); - if (turnRestrictionEnc == null) - throw new IllegalArgumentException("Vehicle " + vehicle + " does not support turn costs"); - int uTurnCosts = hints.getInt(Parameters.Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS); - turnCostProvider = new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage(), uTurnCosts); - } else { - turnCostProvider = NO_TURN_COST_PROVIDER; - } - String weightingStr = toLowerCase(profile.getWeighting()); if (weightingStr.isEmpty()) throw new IllegalArgumentException("You have to specify a weighting"); Weighting weighting = null; - BooleanEncodedValue accessEnc = encodingManager.getBooleanEncodedValue(VehicleAccess.key(vehicle)); - DecimalEncodedValue speedEnc = encodingManager.getDecimalEncodedValue(VehicleSpeed.key(vehicle)); - DecimalEncodedValue priorityEnc = encodingManager.hasEncodedValue(VehiclePriority.key(vehicle)) - ? encodingManager.getDecimalEncodedValue(VehiclePriority.key(vehicle)) - : null; if (CustomWeighting.NAME.equalsIgnoreCase(weightingStr)) { final CustomModel queryCustomModel = requestHints.getObject(CustomModel.KEY, null); + if (profile.getTurnCostsConfig() != null && !profile.getTurnCostsConfig().isAllowTurnPenaltyInRequest() && queryCustomModel != null && !queryCustomModel.getTurnPenalty().isEmpty()) + throw new IllegalArgumentException("The turn_penalty feature is not supported per request for " + profile.getName() + ". Set 'allow_turn_penalty_in_request' to true in the 'turn_costs' option in the config.yml."); + final CustomModel mergedCustomModel = CustomModel.merge(profile.getCustomModel(), queryCustomModel); if (requestHints.has(Parameters.Routing.HEADING_PENALTY)) mergedCustomModel.setHeadingPenalty(requestHints.getDouble(Parameters.Routing.HEADING_PENALTY, Parameters.Routing.DEFAULT_HEADING_PENALTY)); - weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, - priorityEnc, encodingManager, turnCostProvider, mergedCustomModel); + + CustomWeighting.Parameters parameters = createWeightingParameters(mergedCustomModel, encodingManager); + final TurnCostProvider turnCostProvider; + if (profile.hasTurnCosts() && !disableTurnCosts) { + BooleanEncodedValue turnRestrictionEnc = encodingManager.getTurnBooleanEncodedValue(TurnRestriction.key(profile.getName())); + if (turnRestrictionEnc == null) + throw new IllegalArgumentException("Cannot find turn restriction encoded value for " + profile.getName()); + int uTurnCosts = hints.getInt(Parameters.Routing.U_TURN_COSTS, profile.getTurnCostsConfig().getUTurnCosts()); + TurnCostsConfig tcConfig = new TurnCostsConfig(profile.getTurnCostsConfig()).setUTurnCosts(uTurnCosts); + turnCostProvider = new DefaultTurnCostProvider(turnRestrictionEnc, graph, tcConfig, parameters.getTurnPenaltyMapping()); + } else { + if (!mergedCustomModel.getTurnPenalty().isEmpty() && !disableTurnCosts) + throw new IllegalArgumentException("The turn_penalty feature is not supported for " + profile.getName() + ". You have to enable this in 'turn_costs' in config.yml."); + turnCostProvider = NO_TURN_COST_PROVIDER; + } + weighting = new CustomWeighting(turnCostProvider, parameters); + } else if ("shortest".equalsIgnoreCase(weightingStr)) { throw new IllegalArgumentException("Instead of weighting=shortest use weighting=custom with a high distance_influence"); } else if ("fastest".equalsIgnoreCase(weightingStr)) { @@ -100,8 +101,4 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis return weighting; } - - public boolean isOutdoorVehicle(String name) { - return VehicleEncodedValues.OUTDOOR_VEHICLES.contains(name); - } } diff --git a/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java b/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java index 7de2ab6626e..0416d968b77 100644 --- a/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java +++ b/core/src/main/java/com/graphhopper/routing/DijkstraOneToMany.java @@ -67,6 +67,7 @@ public DijkstraOneToMany(Graph graph, Weighting weighting, TraversalMode tMode) @Override public Path calcPath(int from, int to) { + setupFinishTime(); fromNode = from; endNode = findEndNode(from, to); if (endNode < 0 || isWeightLimitExceeded()) { @@ -84,7 +85,7 @@ public Path calcPath(int from, int to) { break; } EdgeIteratorState edgeState = graph.getEdgeIteratorState(edge, node); - path.addDistance(edgeState.getDistance()); + path.addDistance_mm(edgeState.getDistance_mm()); // todo: we do not yet account for turn times here! path.addTime(weighting.calcEdgeMillis(edgeState, false)); path.addEdge(edge); diff --git a/core/src/main/java/com/graphhopper/routing/DirectionResolverResult.java b/core/src/main/java/com/graphhopper/routing/DirectionResolverResult.java index 9bc4cc1a5a6..a2ed51188b6 100644 --- a/core/src/main/java/com/graphhopper/routing/DirectionResolverResult.java +++ b/core/src/main/java/com/graphhopper/routing/DirectionResolverResult.java @@ -72,7 +72,7 @@ public static int getOutEdge(DirectionResolverResult directionResolverResult, St case CURBSIDE_ANY: return ANY_EDGE; default: - throw new IllegalArgumentException("Unknown value for " + CURBSIDE + " : '" + curbside + "'. allowed: " + CURBSIDE_LEFT + ", " + CURBSIDE_RIGHT + ", " + CURBSIDE_ANY); + throw new IllegalArgumentException("Unknown value for " + CURBSIDE + " : '" + curbside + "'. allowed: " + CURBSIDE_LEFT + ", " + CURBSIDE_RIGHT + ", " + CURBSIDE_ANY + ", " + CURBSIDE_AUTO); } } @@ -88,7 +88,7 @@ public static int getInEdge(DirectionResolverResult directionResolverResult, Str case CURBSIDE_ANY: return ANY_EDGE; default: - throw new IllegalArgumentException("Unknown value for '" + CURBSIDE + " : " + curbside + "'. allowed: " + CURBSIDE_LEFT + ", " + CURBSIDE_RIGHT + ", " + CURBSIDE_ANY); + throw new IllegalArgumentException("Unknown value for '" + CURBSIDE + " : " + curbside + "'. allowed: " + CURBSIDE_LEFT + ", " + CURBSIDE_RIGHT + ", " + CURBSIDE_ANY + ", " + CURBSIDE_AUTO); } } diff --git a/core/src/main/java/com/graphhopper/routing/FlexiblePathCalculator.java b/core/src/main/java/com/graphhopper/routing/FlexiblePathCalculator.java index 6ee206cf4e6..53cfc298170 100644 --- a/core/src/main/java/com/graphhopper/routing/FlexiblePathCalculator.java +++ b/core/src/main/java/com/graphhopper/routing/FlexiblePathCalculator.java @@ -65,8 +65,7 @@ private List calcPaths(int from, int to, EdgeRestrictions edgeRestrictions // this compatible with edge-based routing we would have to use edge keys instead of edge ids. either way a // better approach seems to be making the weighting (or the algorithm for that matter) aware of the unfavored // edges directly without changing the graph - for (IntCursor c : edgeRestrictions.getUnfavoredEdges()) - queryGraph.unfavorVirtualEdge(c.value); + queryGraph.unfavorVirtualEdges(edgeRestrictions.getUnfavoredEdges()); List paths; if (edgeRestrictions.getSourceOutEdge() != ANY_EDGE || edgeRestrictions.getTargetInEdge() != ANY_EDGE) { @@ -108,4 +107,4 @@ public Weighting getWeighting() { public void setWeighting(Weighting weighting) { this.weighting = weighting; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java index db22126a898..ea05d9ebf07 100644 --- a/core/src/main/java/com/graphhopper/routing/HeadingResolver.java +++ b/core/src/main/java/com/graphhopper/routing/HeadingResolver.java @@ -19,10 +19,8 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntArrayList; -import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.Graph; import com.graphhopper.util.*; -import com.graphhopper.util.shapes.GHPoint; public class HeadingResolver { private final EdgeExplorer edgeExplorer; diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java b/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java index b63f89c7cf9..fa97fbe195f 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsFromEdges.java @@ -24,7 +24,7 @@ import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; -import static com.graphhopper.search.KVStorage.KeyValue.*; +import static com.graphhopper.util.Parameters.Details.*; /** * This class calculates instructions from the edges in a Path. @@ -44,6 +44,8 @@ public class InstructionsFromEdges implements Path.EdgeVisitor { private final BooleanEncodedValue roundaboutEnc; private final BooleanEncodedValue roadClassLinkEnc; private final EnumEncodedValue roadClassEnc; + private final EnumEncodedValue roadEnvEnc; + private final IntEncodedValue lanesEnc; private final DecimalEncodedValue maxSpeedEnc; /* @@ -77,6 +79,7 @@ public class InstructionsFromEdges implements Path.EdgeVisitor { private boolean prevInRoundabout; private String prevDestinationAndRef; private String prevName; + private RoadEnvironment prevRoadEnv; private String prevInstructionName; private static final int MAX_U_TURN_DISTANCE = 35; @@ -85,15 +88,20 @@ public InstructionsFromEdges(Graph graph, Weighting weighting, EncodedValueLooku InstructionList ways) { this.weighting = weighting; this.roundaboutEnc = evLookup.getBooleanEncodedValue(Roundabout.KEY); + this.roadEnvEnc = evLookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); this.roadClassEnc = evLookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); this.roadClassLinkEnc = evLookup.getBooleanEncodedValue(RoadClassLink.KEY); this.maxSpeedEnc = evLookup.getDecimalEncodedValue(MaxSpeed.KEY); + this.lanesEnc = evLookup.hasEncodedValue(Lanes.KEY) ? evLookup.getIntEncodedValue(Lanes.KEY) : null; this.nodeAccess = graph.getNodeAccess(); this.ways = ways; prevNode = -1; prevInRoundabout = false; prevName = null; - outEdgeExplorer = graph.createEdgeExplorer(edge -> Double.isFinite(weighting.calcEdgeWeight(edge, false))); + prevRoadEnv = null; + + BooleanEncodedValue carAccessEnc = evLookup.getBooleanEncodedValue(VehicleAccess.key("car")); + outEdgeExplorer = graph.createEdgeExplorer(edge -> edge.get(carAccessEnc)); allExplorer = graph.createEdgeExplorer(); } @@ -144,6 +152,9 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { final String ref = (String) edge.getValue(STREET_REF); final String destination = (String) edge.getValue(STREET_DESTINATION); // getValue is fast if it does not exist in edge final String destinationRef = (String) edge.getValue(STREET_DESTINATION_REF); + final String motorwayJunction = (String) edge.getValue(MOTORWAY_JUNCTION); + final RoadEnvironment roadEnv = edge.get(roadEnvEnc); + if ((prevInstruction == null) && (!isRoundabout)) // very first instruction (if not in Roundabout) { int sign = Instruction.CONTINUE_ON_STREET; @@ -151,12 +162,16 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { prevInstruction.setExtraInfo(STREET_REF, ref); prevInstruction.setExtraInfo(STREET_DESTINATION, destination); prevInstruction.setExtraInfo(STREET_DESTINATION_REF, destinationRef); + prevInstruction.setExtraInfo(MOTORWAY_JUNCTION, motorwayJunction); + prevInstruction.setExtraInfo("ferry", InstructionsHelper.createFerryInfo(roadEnv, prevRoadEnv)); + double startLat = nodeAccess.getLat(baseNode); double startLon = nodeAccess.getLon(baseNode); double heading = AngleCalc.ANGLE_CALC.calcAzimuth(startLat, startLon, latitude, longitude); prevInstruction.setExtraInfo("heading", Helper.round(heading, 2)); ways.add(prevInstruction); prevName = name; + prevRoadEnv = roadEnv; prevDestinationAndRef = destination + destinationRef; } else if (isRoundabout) { @@ -191,6 +206,7 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { { prevOrientation = AngleCalc.ANGLE_CALC.calcOrientation(prevLat, prevLon, latitude, longitude); prevName = name; + prevRoadEnv = roadEnv; prevDestinationAndRef = destination + destinationRef; } prevInstruction = roundaboutInstruction; @@ -213,6 +229,8 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { prevInstruction.setExtraInfo(STREET_REF, ref); prevInstruction.setExtraInfo(STREET_DESTINATION, destination); prevInstruction.setExtraInfo(STREET_DESTINATION_REF, destinationRef); + prevInstruction.setExtraInfo(MOTORWAY_JUNCTION, motorwayJunction); + prevInstruction.setExtraInfo("ferry", InstructionsHelper.createFerryInfo(roadEnv, prevRoadEnv)); // calc angle between roundabout entrance and exit double orientation = AngleCalc.ANGLE_CALC.calcOrientation(prevLat, prevLon, latitude, longitude); @@ -232,6 +250,7 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { prevInstructionName = prevName; prevName = name; + prevRoadEnv = roadEnv; prevDestinationAndRef = destination + destinationRef; } else { @@ -257,7 +276,7 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { && (Math.abs(sign) == Instruction.TURN_SLIGHT_RIGHT || Math.abs(sign) == Instruction.TURN_RIGHT || Math.abs(sign) == Instruction.TURN_SHARP_RIGHT) && (Math.abs(prevInstruction.getSign()) == Instruction.TURN_SLIGHT_RIGHT || Math.abs(prevInstruction.getSign()) == Instruction.TURN_RIGHT || Math.abs(prevInstruction.getSign()) == Instruction.TURN_SHARP_RIGHT) && Double.isFinite(weighting.calcEdgeWeight(edge, false)) != Double.isFinite(weighting.calcEdgeWeight(edge, true)) - && InstructionsHelper.isNameSimilar(prevInstructionName, name)) { + && InstructionsHelper.isSameName(prevInstructionName, name)) { // Chances are good that this is a u-turn, we only need to check if the orientation matches GHPoint point = InstructionsHelper.getPointForOrientationCalculation(edge, nodeAccess); double lat = point.getLat(); @@ -273,7 +292,6 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { uTurnType = Instruction.U_TURN_RIGHT; } } - } if (isUTurn) { @@ -289,10 +307,13 @@ public void next(EdgeIteratorState edge, int index, int prevEdgeId) { prevInstruction.setExtraInfo(STREET_REF, ref); prevInstruction.setExtraInfo(STREET_DESTINATION, destination); prevInstruction.setExtraInfo(STREET_DESTINATION_REF, destinationRef); + prevInstruction.setExtraInfo(MOTORWAY_JUNCTION, motorwayJunction); + prevInstruction.setExtraInfo("ferry", InstructionsHelper.createFerryInfo(roadEnv, prevRoadEnv)); } // Update the prevName, since we don't always create an instruction on name changes the previous // name can be an old name. This leads to incorrect turn instructions due to name changes prevName = name; + prevRoadEnv = roadEnv; prevDestinationAndRef = destination + destinationRef; } @@ -335,6 +356,9 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN if (edge.getEdge() == prevEdge.getEdge()) // this is the simplest turn to recognize, a plain u-turn. return Instruction.U_TURN_UNKNOWN; + RoadEnvironment roadEnv = edge.get(roadEnvEnc); + if (InstructionsHelper.isToFerry(roadEnv, prevRoadEnv)) return Instruction.FERRY; + GHPoint point = InstructionsHelper.getPointForOrientationCalculation(edge, nodeAccess); double lat = point.getLat(); double lon = point.getLon(); @@ -342,16 +366,17 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN int sign = InstructionsHelper.calculateSign(prevLat, prevLon, lat, lon, prevOrientation); InstructionsOutgoingEdges outgoingEdges = new InstructionsOutgoingEdges(prevEdge, edge, weighting, maxSpeedEnc, - roadClassEnc, roadClassLinkEnc, allExplorer, nodeAccess, prevNode, baseNode, adjNode); + roadClassEnc, roadClassLinkEnc, lanesEnc, allExplorer, nodeAccess, prevNode, baseNode, adjNode); int nrOfPossibleTurns = outgoingEdges.getAllowedTurns(); // there is no other turn possible if (nrOfPossibleTurns <= 1) { - if (Math.abs(sign) > 1 && outgoingEdges.getVisibleTurns() > 1) { + if (Math.abs(sign) > 1 && outgoingEdges.getVisibleTurns() > 1 && !outgoingEdges.mergedOrSplitWay() + || InstructionsHelper.isFromFerry(roadEnv, prevRoadEnv)) { // This is an actual turn because |sign| > 1 // There could be some confusion, if we would not create a turn instruction, even though it is the only // possible turn, also see #1048 - // TODO if we see issue with this approach we could consider checking if the edge is a oneway + // TODO for motorways or trunks: merge left/right onto A4 return sign; } return Instruction.IGNORE; @@ -361,7 +386,9 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN if (Math.abs(sign) > 1) { // Don't show an instruction if the user is following a street, even though the street is // bending. We should only do this, if following the street is the obvious choice. - if (InstructionsHelper.isNameSimilar(name, prevName) && outgoingEdges.outgoingEdgesAreSlowerByFactor(2)) { + if (InstructionsHelper.isSameName(name, prevName) && outgoingEdges.outgoingEdgesAreSlowerByFactor(2) + || InstructionsHelper.isFromFerry(roadEnv, prevRoadEnv) + || outgoingEdges.mergedOrSplitWay()) { return Instruction.IGNORE; } @@ -389,14 +416,15 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN // Signs provide too less detail, so we use the delta for a precise comparison double delta = InstructionsHelper.calculateOrientationDelta(prevLat, prevLon, lat, lon, prevOrientation); + if (InstructionsHelper.isFromFerry(roadEnv, prevRoadEnv)) + return Instruction.CONTINUE_ON_STREET; + // This state is bad! Two streets are going more or less straight - // Happens a lot for trunk_links - // For _links, comparing flags works quite good, as links usually have different speeds => different flags if (otherContinue != null) { // We are at a fork - if (!InstructionsHelper.isNameSimilar(name, prevName) - || !InstructionsHelper.isNameSimilar(destinationAndRef, prevDestinationAndRef) - || InstructionsHelper.isNameSimilar(otherContinue.getName(), prevName) + if (!InstructionsHelper.isSameName(name, prevName) + || !InstructionsHelper.isSameName(destinationAndRef, prevDestinationAndRef) + || InstructionsHelper.isSameName(otherContinue.getName(), prevName) || !outgoingEdgesAreSlower) { final RoadClass roadClass = edge.get(roadClassEnc); @@ -417,7 +445,7 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN double otherDelta = InstructionsHelper.calculateOrientationDelta(prevLat, prevLon, tmpPoint.getLat(), tmpPoint.getLon(), prevOrientation); // This is required to avoid keep left/right on the motorway at off-ramps/motorway_links - if (Math.abs(delta) < .1 && Math.abs(otherDelta) > .15 && InstructionsHelper.isNameSimilar(name, prevName)) { + if (Math.abs(delta) < .1 /* ~5.7° */ && Math.abs(otherDelta) > .15 /* ~8.6° */ && InstructionsHelper.isSameName(name, prevName)) { return Instruction.CONTINUE_ON_STREET; } @@ -429,7 +457,9 @@ private int getTurn(EdgeIteratorState edge, int baseNode, int prevNode, int adjN } } - if (!outgoingEdgesAreSlower && (Math.abs(delta) > .6 || outgoingEdges.isLeavingCurrentStreet(prevName, name))) { + if (!outgoingEdgesAreSlower + && !outgoingEdges.mergedOrSplitWay() + && (Math.abs(delta) > .6 || outgoingEdges.isLeavingCurrentStreet(prevName, name))) { // Leave the current road -> create instruction return sign; } diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java b/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java index 68186ff175d..546fdbc10b5 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsHelper.java @@ -17,6 +17,7 @@ */ package com.graphhopper.routing; +import com.graphhopper.routing.ev.RoadEnvironment; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; @@ -60,7 +61,7 @@ static int calculateSign(double prevLatitude, double prevLongitude, double latit return Instruction.TURN_SHARP_RIGHT; } - static boolean isNameSimilar(String name1, String name2) { + static boolean isSameName(String name1, String name2) { // We don't want two empty names to be similar (they usually don't have names if they are random tracks) if (name1 == null || name2 == null || name1.isEmpty() || name2.isEmpty()) return false; @@ -80,4 +81,19 @@ static GHPoint getPointForOrientationCalculation(EdgeIteratorState edgeIteratorS } return new GHPoint(tmpLat, tmpLon); } -} \ No newline at end of file + + static boolean isToFerry(RoadEnvironment re, RoadEnvironment prev) { + return (re == RoadEnvironment.FERRY) && re != prev; + } + + static boolean isFromFerry(RoadEnvironment re, RoadEnvironment prev) { + return (prev == RoadEnvironment.FERRY) && re != prev; + } + + static String createFerryInfo(RoadEnvironment re, RoadEnvironment prev) { + if (re == prev) return null; + if (re == RoadEnvironment.FERRY) return "board_ferry"; + if (prev == RoadEnvironment.FERRY) return "leave_ferry"; + return null; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java b/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java index ac2be1e2ba8..d6f8318213c 100644 --- a/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java +++ b/core/src/main/java/com/graphhopper/routing/InstructionsOutgoingEdges.java @@ -17,10 +17,7 @@ */ package com.graphhopper.routing; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.RoadClass; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.EdgeExplorer; @@ -58,15 +55,18 @@ class InstructionsOutgoingEdges { private final EdgeIteratorState prevEdge; private final EdgeIteratorState currentEdge; - // Outgoing edges that we would be allowed to turn on + // edges that one can turn onto private final List allowedAlternativeTurns; - // All outgoing edges, including oneways in the wrong direction + // edges, including oneways in the wrong direction private final List visibleAlternativeTurns; private final DecimalEncodedValue maxSpeedEnc; private final EnumEncodedValue roadClassEnc; private final BooleanEncodedValue roadClassLinkEnc; + private final IntEncodedValue lanesEnc; private final NodeAccess nodeAccess; private final Weighting weighting; + private final int baseNode; + private final EdgeExplorer allExplorer; public InstructionsOutgoingEdges(EdgeIteratorState prevEdge, EdgeIteratorState currentEdge, @@ -74,6 +74,7 @@ public InstructionsOutgoingEdges(EdgeIteratorState prevEdge, DecimalEncodedValue maxSpeedEnc, EnumEncodedValue roadClassEnc, BooleanEncodedValue roadClassLinkEnc, + IntEncodedValue lanesEnc, EdgeExplorer allExplorer, NodeAccess nodeAccess, int prevNode, @@ -85,7 +86,10 @@ public InstructionsOutgoingEdges(EdgeIteratorState prevEdge, this.maxSpeedEnc = maxSpeedEnc; this.roadClassEnc = roadClassEnc; this.roadClassLinkEnc = roadClassLinkEnc; + this.lanesEnc = lanesEnc; this.nodeAccess = nodeAccess; + this.baseNode = baseNode; + this.allExplorer = allExplorer; visibleAlternativeTurns = new ArrayList<>(); allowedAlternativeTurns = new ArrayList<>(); @@ -179,7 +183,7 @@ public EdgeIteratorState getOtherContinue(double prevLat, double prevLon, double * If either of these properties is true, we can be quite certain that a turn instruction should be provided. */ public boolean isLeavingCurrentStreet(String prevName, String name) { - if (InstructionsHelper.isNameSimilar(name, prevName)) { + if (InstructionsHelper.isSameName(name, prevName)) { return false; } @@ -187,11 +191,11 @@ public boolean isLeavingCurrentStreet(String prevName, String name) { for (EdgeIteratorState edge : allowedAlternativeTurns) { String edgeName = edge.getName(); // leave the current street - if (InstructionsHelper.isNameSimilar(prevName, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(prevEdge, edge))) { + if (InstructionsHelper.isSameName(prevName, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(prevEdge, edge))) { return true; } // enter a different street - if (InstructionsHelper.isNameSimilar(name, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(currentEdge, edge))) { + if (InstructionsHelper.isSameName(name, edgeName) || (roadClassOrLinkChange && isTheSameRoadClassAndLink(currentEdge, edge))) { return true; } } @@ -202,4 +206,55 @@ private boolean isTheSameRoadClassAndLink(EdgeIteratorState edge1, EdgeIteratorS return edge1.get(roadClassEnc) == edge2.get(roadClassEnc) && edge1.get(roadClassLinkEnc) == edge2.get(roadClassLinkEnc); } + // for cases like in #2946 we should not create instructions as they are only "tagging artifacts" + public boolean mergedOrSplitWay() { + if (lanesEnc == null) return false; + + String name = currentEdge.getName(); + RoadClass roadClass = currentEdge.get(roadClassEnc); + if (!InstructionsHelper.isSameName(name, prevEdge.getName()) || roadClass != prevEdge.get(roadClassEnc)) + return false; + + // search another edge with the same name where at least one direction is accessible + EdgeIterator edgeIter = allExplorer.setBaseNode(baseNode); + EdgeIteratorState otherEdge = null; + while (edgeIter.next()) { + if (currentEdge.getEdge() != edgeIter.getEdge() + && prevEdge.getEdge() != edgeIter.getEdge() + && roadClass == edgeIter.get(roadClassEnc) + && InstructionsHelper.isSameName(name, edgeIter.getName()) + && (Double.isFinite(weighting.calcEdgeWeight(edgeIter, false)) + || Double.isFinite(weighting.calcEdgeWeight(edgeIter, true)))) { + if (otherEdge != null) return false; // too many possible other edges + otherEdge = edgeIter.detach(false); + } + } + if (otherEdge == null) return false; + + if (Double.isFinite(weighting.calcEdgeWeight(currentEdge, true))) { + // assume two ways are merged into one way + // -> prev -> + // <- edge -> + // -> other -> + if (Double.isFinite(weighting.calcEdgeWeight(prevEdge, true))) return false; + // otherEdge has direction from junction outwards + if (!Double.isFinite(weighting.calcEdgeWeight(otherEdge, false))) return false; + if (Double.isFinite(weighting.calcEdgeWeight(otherEdge, true))) return false; + + int delta = Math.abs(prevEdge.get(lanesEnc) + otherEdge.get(lanesEnc) - currentEdge.get(lanesEnc)); + return delta <= 1; + } + + // assume one way is split into two ways + // -> edge -> + // <- prev -> + // -> other -> + if (!Double.isFinite(weighting.calcEdgeWeight(prevEdge, true))) return false; + // otherEdge has direction from junction outwards + if (Double.isFinite(weighting.calcEdgeWeight(otherEdge, false))) return false; + if (!Double.isFinite(weighting.calcEdgeWeight(otherEdge, true))) return false; + + int delta = prevEdge.get(lanesEnc) - (currentEdge.get(lanesEnc) + otherEdge.get(lanesEnc)); + return delta <= 1; + } } diff --git a/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java index a1be72f6221..384e50d42ff 100644 --- a/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java +++ b/core/src/main/java/com/graphhopper/routing/OSMReaderConfig.java @@ -25,7 +25,7 @@ public class OSMReaderConfig { private List ignoredHighways = new ArrayList<>(); private boolean parseWayNames = true; private String preferredLanguage = ""; - private double maxWayPointDistance = 1; + private double maxWayPointDistance = 0.5; private double elevationMaxWayPointDistance = Double.MAX_VALUE; private String smoothElevation = ""; @@ -33,6 +33,7 @@ public class OSMReaderConfig { private int ramerElevationSmoothingMax = 5; private double longEdgeSamplingDistance = Double.MAX_VALUE; private int workerThreads = 2; + private double defaultElevation = 0; public List getIgnoredHighways() { return ignoredHighways; @@ -155,4 +156,16 @@ public OSMReaderConfig setWorkerThreads(int workerThreads) { this.workerThreads = workerThreads; return this; } + + public double getDefaultElevation() { + return defaultElevation; + } + + /** + * Sets the elevation in meters that shall be used if the elevation data source is missing a value + */ + public OSMReaderConfig setDefaultElevation(double defaultElevation) { + this.defaultElevation = defaultElevation; + return this; + } } diff --git a/core/src/main/java/com/graphhopper/routing/Path.java b/core/src/main/java/com/graphhopper/routing/Path.java index 6a3d5cba961..a208d300edc 100644 --- a/core/src/main/java/com/graphhopper/routing/Path.java +++ b/core/src/main/java/com/graphhopper/routing/Path.java @@ -43,7 +43,7 @@ public class Path { final Graph graph; private final NodeAccess nodeAccess; private double weight = Double.MAX_VALUE; - private double distance; + private long distance_mm; private long time; private IntArrayList edgeIds = new IntArrayList(); private int fromNode = -1; @@ -101,10 +101,7 @@ public Path setEndNode(int end) { return this; } - /** - * @return the first node of this Path. - */ - private int getFromNode() { + public int getFromNode() { if (fromNode < 0) throw new IllegalStateException("fromNode < 0 should not happen"); @@ -128,13 +125,8 @@ public Path setFound(boolean found) { return this; } - public Path setDistance(double distance) { - this.distance = distance; - return this; - } - - public Path addDistance(double distance) { - this.distance += distance; + public Path addDistance_mm(long distance_mm) { + this.distance_mm += distance_mm; return this; } @@ -142,7 +134,11 @@ public Path addDistance(double distance) { * @return distance in meter */ public double getDistance() { - return distance; + return distance_mm / 1000.0; + } + + public long getDistance_mm() { + return distance_mm; } /** @@ -303,7 +299,7 @@ public void finish() { @Override public String toString() { - return "found: " + found + ", weight: " + weight + ", time: " + time + ", distance: " + distance + ", edges: " + edgeIds.size(); + return "found: " + found + ", weight: " + weight + ", time: " + time + ", distance: " + distance_mm + ", edges: " + edgeIds.size(); } /** diff --git a/core/src/main/java/com/graphhopper/routing/PathExtractor.java b/core/src/main/java/com/graphhopper/routing/PathExtractor.java index 5f3d58773cf..197f53bc805 100644 --- a/core/src/main/java/com/graphhopper/routing/PathExtractor.java +++ b/core/src/main/java/com/graphhopper/routing/PathExtractor.java @@ -74,7 +74,7 @@ private void setExtractionTime(long nanos) { protected void onEdge(int edge, int adjNode, int prevEdge) { EdgeIteratorState edgeState = graph.getEdgeIteratorState(edge, adjNode); - path.addDistance(edgeState.getDistance()); + path.addDistance_mm(edgeState.getDistance_mm()); path.addTime(GHUtility.calcMillisWithTurnMillis(weighting, edgeState, false, prevEdge)); path.addEdge(edge); } diff --git a/core/src/main/java/com/graphhopper/routing/Router.java b/core/src/main/java/com/graphhopper/routing/Router.java index a58d8acead5..5b82be38492 100644 --- a/core/src/main/java/com/graphhopper/routing/Router.java +++ b/core/src/main/java/com/graphhopper/routing/Router.java @@ -24,9 +24,7 @@ import com.graphhopper.ResponsePath; import com.graphhopper.config.Profile; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.routing.ev.Subnetwork; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.lm.LMRoutingAlgorithmFactory; import com.graphhopper.routing.lm.LandmarkStorage; import com.graphhopper.routing.querygraph.QueryGraph; @@ -49,11 +47,11 @@ import java.util.*; -import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS; import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; import static com.graphhopper.util.Parameters.Algorithms.ALT_ROUTE; import static com.graphhopper.util.Parameters.Algorithms.ROUND_TRIP; import static com.graphhopper.util.Parameters.Routing.*; +import static com.graphhopper.util.TurnCostsConfig.INFINITE_U_TURN_COSTS; public class Router { protected final BaseGraph graph; @@ -66,8 +64,6 @@ public class Router { protected final WeightingFactory weightingFactory; protected final Map chGraphs; protected final Map landmarks; - protected final boolean chEnabled; - protected final boolean lmEnabled; public Router(BaseGraph graph, EncodingManager encodingManager, LocationIndex locationIndex, Map profilesByName, PathDetailsBuilderFactory pathDetailsBuilderFactory, @@ -83,10 +79,6 @@ public Router(BaseGraph graph, EncodingManager encodingManager, LocationIndex lo this.weightingFactory = weightingFactory; this.chGraphs = chGraphs; this.landmarks = landmarks; - // note that his is not the same as !ghStorage.getCHConfigs().isEmpty(), because the GHStorage might have some - // CHGraphs that were not built yet (and possibly no CH profiles were configured). - this.chEnabled = !chGraphs.isEmpty(); - this.lmEnabled = !landmarks.isEmpty(); for (String profile : profilesByName.keySet()) { if (!encodingManager.hasEncodedValue(Subnetwork.key(profile))) @@ -98,11 +90,12 @@ public GHResponse route(GHRequest request) { try { checkNoLegacyParameters(request); checkAtLeastOnePoint(request); - checkIfPointsAreInBounds(request.getPoints()); + checkIfPointsAreInBoundsAndNotNull(request.getPoints()); checkHeadings(request); checkPointHints(request); checkCurbsides(request); checkNoBlockArea(request); + checkCustomModel(request); Solver solver = createSolver(request); solver.checkRequest(); @@ -146,13 +139,14 @@ private void checkAtLeastOnePoint(GHRequest request) { throw new IllegalArgumentException("You have to pass at least one point"); } - private void checkIfPointsAreInBounds(List points) { + private void checkIfPointsAreInBoundsAndNotNull(List points) { BBox bounds = graph.getBounds(); for (int i = 0; i < points.size(); i++) { GHPoint point = points.get(i); - if (!bounds.contains(point.getLat(), point.getLon())) { + if (point == null) + throw new IllegalArgumentException("Point " + i + " is null"); + if (!bounds.contains(point.getLat(), point.getLon())) throw new PointOutOfBoundsException("Point " + i + " is out of bounds: " + point + ", the bounds are: " + bounds, i); - } } } @@ -180,12 +174,15 @@ private void checkNoBlockArea(GHRequest request) { throw new IllegalArgumentException("The `block_area` parameter is no longer supported. Use a custom model with `areas` instead."); } + private void checkCustomModel(GHRequest request) { + if (request.getCustomModel() != null && request.getCustomModel().isInternal()) + throw new IllegalArgumentException("CustomModel of query cannot be internal"); + } + protected Solver createSolver(GHRequest request) { - final boolean disableCH = getDisableCH(request.getHints()); - final boolean disableLM = getDisableLM(request.getHints()); - if (chEnabled && !disableCH) { + if (chGraphs.containsKey(request.getProfile()) && !getDisableCH(request.getHints())) { return createCHSolver(request, profilesByName, routerConfig, encodingManager, chGraphs); - } else if (lmEnabled && !disableLM) { + } else if (landmarks.containsKey(request.getProfile()) && !getDisableLM(request.getHints())) { return createLMSolver(request, profilesByName, routerConfig, encodingManager, weightingFactory, graph, locationIndex, landmarks); } else { return createFlexSolver(request, profilesByName, routerConfig, encodingManager, weightingFactory, graph, locationIndex); @@ -243,13 +240,14 @@ protected GHResponse routeAlt(GHRequest request, Solver solver) { QueryGraph queryGraph = QueryGraph.create(graph, snaps); PathCalculator pathCalculator = solver.createPathCalculator(queryGraph); boolean passThrough = getPassThrough(request.getHints()); - boolean forceCurbsides = getForceCurbsides(request.getHints()); + String curbsideStrictness = getCurbsideStrictness(request.getHints()); if (passThrough) throw new IllegalArgumentException("Alternative paths and " + PASS_THROUGH + " at the same time is currently not supported"); if (!request.getCurbsides().isEmpty()) throw new IllegalArgumentException("Alternative paths do not support the " + CURBSIDE + " parameter yet"); - ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, directedEdgeFilter, pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough); + ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, directedEdgeFilter, + pathCalculator, request.getCurbsides(), curbsideStrictness, request.getHeadings(), passThrough, encodingManager); if (result.paths.isEmpty()) throw new RuntimeException("Empty paths for alternative route calculation not expected"); @@ -277,9 +275,9 @@ protected GHResponse routeVia(GHRequest request, Solver solver) { QueryGraph queryGraph = QueryGraph.create(graph, snaps); PathCalculator pathCalculator = solver.createPathCalculator(queryGraph); boolean passThrough = getPassThrough(request.getHints()); - boolean forceCurbsides = getForceCurbsides(request.getHints()); + String curbsideStrictness = getCurbsideStrictness(request.getHints()); ViaRouting.Result result = ViaRouting.calcPaths(request.getPoints(), queryGraph, snaps, directedEdgeFilter, - pathCalculator, request.getCurbsides(), forceCurbsides, request.getHeadings(), passThrough); + pathCalculator, request.getCurbsides(), curbsideStrictness, request.getHeadings(), passThrough, encodingManager); if (request.getPoints().size() != result.paths.size() + 1) throw new RuntimeException("There should be exactly one more point than paths. points:" + request.getPoints().size() + ", paths:" + result.paths.size()); @@ -296,7 +294,7 @@ protected GHResponse routeVia(GHRequest request, Solver solver) { private PathMerger createPathMerger(GHRequest request, Weighting weighting, Graph graph) { boolean enableInstructions = request.getHints().getBool(Parameters.Routing.INSTRUCTIONS, routerConfig.isInstructionsEnabled()); boolean calcPoints = request.getHints().getBool(Parameters.Routing.CALC_POINTS, routerConfig.isCalcPoints()); - double wayPointMaxDistance = request.getHints().getDouble(Parameters.Routing.WAY_POINT_MAX_DISTANCE, 1d); + double wayPointMaxDistance = request.getHints().getDouble(Parameters.Routing.WAY_POINT_MAX_DISTANCE, 0.5); double elevationWayPointMaxDistance = request.getHints().getDouble(ELEVATION_WAY_POINT_MAX_DISTANCE, routerConfig.getElevationWayPointMaxDistance()); RamerDouglasPeucker peucker = new RamerDouglasPeucker(). @@ -339,8 +337,11 @@ private static boolean getPassThrough(PMap hints) { return hints.getBool(PASS_THROUGH, false); } - private static boolean getForceCurbsides(PMap hints) { - return hints.getBool(FORCE_CURBSIDE, true); + private static String getCurbsideStrictness(PMap hints) { + if (hints.has(CURBSIDE_STRICTNESS)) return hints.getString(CURBSIDE_STRICTNESS, "strict"); + + // legacy + return hints.getBool("force_curbside", true) ? "strict" : "soft"; } public static abstract class Solver { @@ -387,14 +388,14 @@ protected Profile getProfile() { } protected void checkProfileCompatibility() { - if (!profile.isTurnCosts() && !request.getCurbsides().isEmpty()) + if (!profile.hasTurnCosts() && !request.getCurbsides().isEmpty()) throw new IllegalArgumentException("To make use of the " + CURBSIDE + " parameter you need to use a profile that supports turn costs" + "\nThe following profiles do support turn costs: " + getTurnCostProfiles()); if (request.getCustomModel() != null && !CustomWeighting.NAME.equals(profile.getWeighting())) throw new IllegalArgumentException("The requested profile '" + request.getProfile() + "' cannot be used with `custom_model`, because it has weighting=" + profile.getWeighting()); final int uTurnCostsInt = request.getHints().getInt(Parameters.Routing.U_TURN_COSTS, INFINITE_U_TURN_COSTS); - if (uTurnCostsInt != INFINITE_U_TURN_COSTS && !profile.isTurnCosts()) { + if (uTurnCostsInt != INFINITE_U_TURN_COSTS && !profile.hasTurnCosts()) { throw new IllegalArgumentException("Finite u-turn costs can only be used for edge-based routing, you need to use a profile that" + " supports turn costs. Currently the following profiles that support turn costs are available: " + getTurnCostProfiles()); } @@ -416,7 +417,7 @@ protected DirectedEdgeFilter createDirectedEdgeFilter() { private List getTurnCostProfiles() { List turnCostProfiles = new ArrayList<>(); for (Profile p : profilesByName.values()) { - if (p.isTurnCosts()) { + if (p.hasTurnCosts()) { turnCostProfiles.add(p.getName()); } } @@ -525,7 +526,7 @@ protected FlexiblePathCalculator createPathCalculator(QueryGraph queryGraph) { protected AlgorithmOptions getAlgoOpts() { AlgorithmOptions algoOpts = new AlgorithmOptions(). setAlgorithm(request.getAlgorithm()). - setTraversalMode(profile.isTurnCosts() ? TraversalMode.EDGE_BASED : TraversalMode.NODE_BASED). + setTraversalMode(profile.hasTurnCosts() ? TraversalMode.EDGE_BASED : TraversalMode.NODE_BASED). setMaxVisitedNodes(getMaxVisitedNodes(request.getHints())). setTimeoutMillis(getTimeoutMillis(request.getHints())). setHints(request.getHints()); @@ -576,7 +577,7 @@ protected FlexiblePathCalculator createPathCalculator(QueryGraph queryGraph) { throw new IllegalArgumentException("Cannot find LM preparation for the requested profile: '" + profile.getName() + "'" + "\nYou can try disabling LM using " + Parameters.Landmark.DISABLE + "=true" + "\navailable LM profiles: " + landmarks.keySet()); - if (request.getCustomModel() != null && !request.getHints().getBool("lm.disable", false)) + if (request.getCustomModel() != null) FindMinMax.checkLMConstraints(profile.getCustomModel(), request.getCustomModel(), lookup); RoutingAlgorithmFactory routingAlgorithmFactory = new LMRoutingAlgorithmFactory(landmarkStorage).setDefaultActiveLandmarks(routerConfig.getActiveLandmarkCount()); return new FlexiblePathCalculator(queryGraph, routingAlgorithmFactory, weighting, getAlgoOpts()); diff --git a/core/src/main/java/com/graphhopper/routing/TestProfiles.java b/core/src/main/java/com/graphhopper/routing/TestProfiles.java new file mode 100644 index 00000000000..9a94ae31c7b --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/TestProfiles.java @@ -0,0 +1,69 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing; + +import com.graphhopper.config.Profile; +import com.graphhopper.json.Statement; +import com.graphhopper.util.CustomModel; + +import static com.graphhopper.json.Statement.Else; +import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.Op.LIMIT; +import static com.graphhopper.json.Statement.Op.MULTIPLY; + +public class TestProfiles { + public static Profile constantSpeed(String name) { + return constantSpeed(name, 60); + } + + public static Profile constantSpeed(String name, double speed) { + Profile profile = new Profile(name); + CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", Statement.Op.LIMIT, String.valueOf(speed))); + profile.setCustomModel(customModel); + return profile; + } + + public static Profile accessAndSpeed(String vehicle) { + return accessAndSpeed(vehicle, vehicle); + } + + public static Profile accessAndSpeed(String name, String vehicle) { + Profile profile = new Profile(name); + CustomModel customModel = new CustomModel(). + addToPriority(If("!" + vehicle + "_access", MULTIPLY, "0")). + addToSpeed(If("true", LIMIT, vehicle + "_average_speed")); + profile.setCustomModel(customModel); + return profile; + } + + public static Profile accessSpeedAndPriority(String vehicle) { + return accessSpeedAndPriority(vehicle, vehicle); + } + + public static Profile accessSpeedAndPriority(String name, String vehicle) { + Profile profile = new Profile(name); + CustomModel customModel = new CustomModel(). + addToPriority(If(vehicle + "_access", MULTIPLY, vehicle + "_priority")). + addToPriority(Else(MULTIPLY, "0")). + addToSpeed(If("true", LIMIT, vehicle + "_average_speed")); + profile.setCustomModel(customModel); + return profile; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ViaRouting.java b/core/src/main/java/com/graphhopper/routing/ViaRouting.java index 50e298bd121..a8f8ed2e31d 100644 --- a/core/src/main/java/com/graphhopper/routing/ViaRouting.java +++ b/core/src/main/java/com/graphhopper/routing/ViaRouting.java @@ -34,10 +34,12 @@ import java.util.ArrayList; import java.util.List; +import java.util.function.Function; import static com.graphhopper.util.EdgeIterator.ANY_EDGE; import static com.graphhopper.util.EdgeIterator.NO_EDGE; import static com.graphhopper.util.Parameters.Curbsides.CURBSIDE_ANY; +import static com.graphhopper.util.Parameters.Curbsides.CURBSIDE_AUTO; import static com.graphhopper.util.Parameters.Routing.CURBSIDE; /** @@ -94,12 +96,16 @@ public static List lookup(EncodedValueLookup lookup, List points, return snaps; } - public static Result calcPaths(List points, QueryGraph queryGraph, List snaps, DirectedEdgeFilter directedEdgeFilter, PathCalculator pathCalculator, List curbsides, boolean forceCurbsides, List headings, boolean passThrough) { + public static Result calcPaths(List points, QueryGraph queryGraph, List snaps, + DirectedEdgeFilter directedEdgeFilter, PathCalculator pathCalculator, + List curbsides, String curbsideStrictness, List headings, boolean passThrough, EncodingManager em) { if (!curbsides.isEmpty() && curbsides.size() != points.size()) throw new IllegalArgumentException("If you pass " + CURBSIDE + ", you need to pass exactly one curbside for every point, empty curbsides will be ignored"); if (!curbsides.isEmpty() && !headings.isEmpty()) throw new IllegalArgumentException("You cannot use curbsides and headings or pass_through at the same time"); + Function curbsideAutoFunction = CurbsideAutoHelper.createResolver(directedEdgeFilter, em); + final int legs = snaps.size() - 1; Result result = new Result(legs); for (int leg = 0; leg < legs; ++leg) { @@ -124,15 +130,20 @@ public static Result calcPaths(List points, QueryGraph queryGraph, List } // enforce curbsides - final String fromCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg); - final String toCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg + 1); + String fromCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg); + String toCurbside = curbsides.isEmpty() ? CURBSIDE_ANY : curbsides.get(leg + 1); + + if (CURBSIDE_AUTO.equals(fromCurbside)) + fromCurbside = curbsideAutoFunction.apply(fromSnap); + if (CURBSIDE_AUTO.equals(toCurbside)) + toCurbside = curbsideAutoFunction.apply(toSnap); EdgeRestrictions edgeRestrictions = buildEdgeRestrictions(queryGraph, fromSnap, toSnap, fromHeading, toHeading, incomingEdge, passThrough, fromCurbside, toCurbside, directedEdgeFilter); - edgeRestrictions.setSourceOutEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getSourceOutEdge(), leg, forceCurbsides)); - edgeRestrictions.setTargetInEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getTargetInEdge(), leg + 1, forceCurbsides)); + edgeRestrictions.setSourceOutEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getSourceOutEdge(), leg, curbsideStrictness)); + edgeRestrictions.setTargetInEdge(ignoreThrowOrAcceptImpossibleCurbsides(curbsides, edgeRestrictions.getTargetInEdge(), leg + 1, curbsideStrictness)); // calculate paths List paths = pathCalculator.calcPaths(fromSnap.getClosestNode(), toSnap.getClosestNode(), edgeRestrictions); @@ -193,6 +204,7 @@ private static EdgeRestrictions buildEdgeRestrictions( } else return edgeFilter.accept(edge, reverse); }; + DirectionResolver directionResolver = new DirectionResolver(queryGraph, directedEdgeFilter); DirectionResolverResult fromDirection = directionResolver.resolveDirections(fromSnap.getClosestNode(), fromSnap.getQueryPoint()); DirectionResolverResult toDirection = directionResolver.resolveDirections(toSnap.getClosestNode(), toSnap.getQueryPoint()); @@ -242,19 +254,20 @@ private static EdgeRestrictions buildEdgeRestrictions( return edgeRestrictions; } - private static int ignoreThrowOrAcceptImpossibleCurbsides(List curbsides, int edge, int placeIndex, boolean forceCurbsides) { + private static int ignoreThrowOrAcceptImpossibleCurbsides(List curbsides, int edge, int placeIndex, String curbsideStrictness) { if (edge != NO_EDGE) { return edge; } - if (forceCurbsides) { + if ("strict".equals(curbsideStrictness)) { return throwImpossibleCurbsideConstraint(curbsides, placeIndex); - } else { + } else if ("soft".equals(curbsideStrictness)) { return ANY_EDGE; + } else { + throw new IllegalArgumentException("Unknown curbside_strictness " + curbsideStrictness); } } private static int throwImpossibleCurbsideConstraint(List curbsides, int placeIndex) { throw new IllegalArgumentException("Impossible curbside constraint: 'curbside=" + curbsides.get(placeIndex) + "' at point " + placeIndex); } - } diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java index bd3868f8432..7d11df57096 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java +++ b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationGraph.java @@ -21,16 +21,9 @@ import com.carrotsearch.hppc.*; import com.carrotsearch.hppc.sorting.IndirectComparator; import com.carrotsearch.hppc.sorting.IndirectSort; -import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.util.AllEdgesIterator; -import com.graphhopper.routing.weighting.AbstractWeighting; -import com.graphhopper.routing.weighting.DefaultTurnCostProvider; -import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.Graph; -import com.graphhopper.storage.TurnCostStorage; -import com.graphhopper.util.BitUtil; -import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.GHUtility; import static com.graphhopper.util.ArrayUtil.zero; @@ -800,20 +793,20 @@ public String toString() { /** * This helper graph can be used to quickly obtain the edge-keys of the edges of a node. It is only used for - * edge-based CH. In principle we could use base graph for this, but it turned out it is faster to use this + * edge-based CH. In principle, we could use base graph for this, but it turned out it is faster to use this * graph (because it does not need to read all the edge flags to determine the access flags). */ static class OrigGraph { - // we store a list of 'edges' in the format: adjNode|edgeId|accessFlags, we use two ints for each edge - private final IntArrayList adjNodes; - private final IntArrayList keysAndFlags; + // we store a list of 'edges' in the format: adjNode|fwdAccess|edgeKey|bwdAccess, we use two ints for each edge + private final IntArrayList adjNodesAndFwdFlags; + private final IntArrayList keysAndBwdFlags; // for each node we store the index at which the edges for this node begin in the above edge list private final IntArrayList firstEdgesByNode; - private OrigGraph(IntArrayList firstEdgesByNode, IntArrayList adjNodes, IntArrayList keysAndFlags) { + private OrigGraph(IntArrayList firstEdgesByNode, IntArrayList adjNodesAndFwdFlags, IntArrayList keysAndBwdFlags) { this.firstEdgesByNode = firstEdgesByNode; - this.adjNodes = adjNodes; - this.keysAndFlags = keysAndFlags; + this.adjNodesAndFwdFlags = adjNodesAndFwdFlags; + this.keysAndBwdFlags = keysAndBwdFlags; } PrepareGraphOrigEdgeExplorer createOutOrigEdgeExplorer() { @@ -826,21 +819,21 @@ PrepareGraphOrigEdgeExplorer createInOrigEdgeExplorer() { static class Builder { private final IntArrayList fromNodes = new IntArrayList(); - private final IntArrayList toNodes = new IntArrayList(); - private final IntArrayList keysAndFlags = new IntArrayList(); + private final IntArrayList toNodesAndFwdFlags = new IntArrayList(); + private final IntArrayList keysAndBwdFlags = new IntArrayList(); private int maxFrom = -1; private int maxTo = -1; void addEdge(int from, int to, int edge, boolean fwd, boolean bwd) { fromNodes.add(from); - toNodes.add(to); - keysAndFlags.add(getKeyWithFlags(GHUtility.createEdgeKey(edge, false), fwd, bwd)); + toNodesAndFwdFlags.add(getIntWithFlag(to, fwd)); + keysAndBwdFlags.add(getIntWithFlag(GHUtility.createEdgeKey(edge, false), bwd)); maxFrom = Math.max(maxFrom, from); maxTo = Math.max(maxTo, to); fromNodes.add(to); - toNodes.add(from); - keysAndFlags.add(getKeyWithFlags(GHUtility.createEdgeKey(edge, true), bwd, fwd)); + toNodesAndFwdFlags.add(getIntWithFlag(from, bwd)); + keysAndBwdFlags.add(getIntWithFlag(GHUtility.createEdgeKey(edge, true), fwd)); maxFrom = Math.max(maxFrom, to); maxTo = Math.max(maxTo, from); } @@ -848,25 +841,23 @@ void addEdge(int from, int to, int edge, boolean fwd, boolean bwd) { OrigGraph build() { int[] sortOrder = IndirectSort.mergesort(0, fromNodes.elementsCount, new IndirectComparator.AscendingIntComparator(fromNodes.buffer)); sortAndTrim(fromNodes, sortOrder); - sortAndTrim(toNodes, sortOrder); - sortAndTrim(keysAndFlags, sortOrder); - return new OrigGraph(buildFirstEdgesByNode(), toNodes, keysAndFlags); + sortAndTrim(toNodesAndFwdFlags, sortOrder); + sortAndTrim(keysAndBwdFlags, sortOrder); + return new OrigGraph(buildFirstEdgesByNode(), toNodesAndFwdFlags, keysAndBwdFlags); } - private static int getKeyWithFlags(int key, boolean fwd, boolean bwd) { - // we use only 30 bits for the key and store two access flags along with the same int - // this allows for a maximum of 536mio edges in base graph which is still enough for planet-wide OSM, - // but if we exceed this limit we should probably move one of the fwd/bwd bits to the nodes field or - // store the edge instead of the key as we did before #2567 (only here) - if (key > Integer.MAX_VALUE >> 1) - throw new IllegalArgumentException("Maximum edge key exceeded: " + key + ", max: " + (Integer.MAX_VALUE >> 1)); - key <<= 1; - if (fwd) - key++; - key <<= 1; - if (bwd) - key++; - return key; + private static int getIntWithFlag(int val, boolean access) { + // we use only 31 bits for the val and store an access flag along with the same int + // this allows for a maximum of 1073mio edges (and 2147mio nodes) in base graph + // which is still enough for planet-wide OSM, but if we exceed this limit we need to + // move the access bits somewhere else or store the edge instead of the val as we + // did before #2567 (only here) + if (val < 0) + throw new IllegalArgumentException("Maximum node or edge key exceeded: " + val + ", max: " + Integer.MAX_VALUE); + val <<= 1; + if (access) + val++; + return val; } private IntArrayList buildFirstEdgesByNode() { @@ -931,12 +922,12 @@ public int getBaseNode() { @Override public int getAdjNode() { - return graph.adjNodes.get(index); + return graph.adjNodesAndFwdFlags.get(index) >>> 1; } @Override public int getOrigEdgeKeyFirst() { - return graph.keysAndFlags.get(index) >>> 2; + return graph.keysAndBwdFlags.get(index) >>> 1; } @Override @@ -945,11 +936,10 @@ public int getOrigEdgeKeyLast() { } private boolean hasAccess() { - int e = graph.keysAndFlags.get(index); - if (reverse) - return (e & 0b01) == 0b01; - else - return (e & 0b10) == 0b10; + int e = reverse + ? graph.keysAndBwdFlags.get(index) + : graph.adjNodesAndFwdFlags.get(index); + return (e & 0b01) == 0b01; } @Override diff --git a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java index 3f05d54732c..84087fb223f 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java +++ b/core/src/main/java/com/graphhopper/routing/ch/CHPreparationHandler.java @@ -27,8 +27,6 @@ import org.slf4j.LoggerFactory; import java.util.*; -import java.util.concurrent.Callable; -import java.util.stream.Collectors; import java.util.stream.Stream; import static com.graphhopper.util.Helper.createFormatter; @@ -101,7 +99,7 @@ public Map load(BaseGraph graph, List chConfig Map loaded = Collections.synchronizedMap(new LinkedHashMap<>()); Stream runnables = chConfigs.stream() .map(c -> () -> { - CHStorage chStorage = new CHStorage(graph.getDirectory(), c.getName(), graph.getSegmentSize(), c.isEdgeBased()); + CHStorage chStorage = new CHStorage(graph.getDirectory(), c.getName(), c.isEdgeBased()); if (chStorage.loadExisting()) loaded.put(c.getName(), RoutingCHGraphImpl.fromGraph(graph, chStorage, c)); else { @@ -120,19 +118,18 @@ public Map prepare(BaseGraph baseG return Collections.emptyMap(); } LOGGER.info("Creating CH preparations, {}", getMemInfo()); - List preparations = chConfigs.stream() - .map(c -> createCHPreparation(baseGraph, c)) - .collect(Collectors.toList()); Map results = Collections.synchronizedMap(new LinkedHashMap<>()); - List runnables = new ArrayList<>(preparations.size()); - for (int i = 0; i < preparations.size(); ++i) { - PrepareContractionHierarchies prepare = preparations.get(i); - LOGGER.info((i + 1) + "/" + preparations.size() + " calling " + - "CH prepare.doWork for profile '" + prepare.getCHConfig().getName() + "' " + prepare.getCHConfig().getTraversalMode() + " ... (" + getMemInfo() + ")"); + List runnables = new ArrayList<>(chConfigs.size()); + for (int i = 0; i < chConfigs.size(); ++i) { + CHConfig chConfig = chConfigs.get(i); + LOGGER.info((i + 1) + "/" + chConfigs.size() + " Setting up CH preparation for profile " + + "'" + chConfig.getName() + "' " + chConfig.getTraversalMode() + " ... (" + getMemInfo() + ")"); runnables.add(() -> { - final String name = prepare.getCHConfig().getName(); + final String name = chConfig.getName(); // toString is not taken into account so we need to cheat, see http://stackoverflow.com/q/6113746/194609 for other options Thread.currentThread().setName(name); + PrepareContractionHierarchies prepare = PrepareContractionHierarchies.fromGraph(baseGraph, chConfig); + prepare.setParams(pMap); PrepareContractionHierarchies.Result result = prepare.doWork(); results.put(name, result); prepare.flush(); diff --git a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java index caebfc06295..d4392adb034 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java +++ b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedCHBidirPathExtractor.java @@ -60,7 +60,7 @@ protected void onMeetingPoint(int inEdge, int viaNode, int outEdge) { private ShortcutUnpacker createShortcutUnpacker() { return new ShortcutUnpacker(routingGraph, (edge, reverse, prevOrNextEdgeId) -> { - path.addDistance(edge.getDistance()); + path.addDistance_mm(edge.getDistance_mm()); path.addTime(GHUtility.calcMillisWithTurnMillis(weighting, edge, reverse, prevOrNextEdgeId)); path.addEdge(edge.getEdge()); }, true); diff --git a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedNodeContractor.java b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedNodeContractor.java index d438abc9802..dff0db4a255 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedNodeContractor.java +++ b/core/src/main/java/com/graphhopper/routing/ch/EdgeBasedNodeContractor.java @@ -423,7 +423,7 @@ public static class Params { private float hierarchyDepthWeight = 20; // Increasing these parameters (heuristic especially) will lead to a longer preparation time but also to fewer // shortcuts and possibly (slightly) faster queries. - private double maxPollFactorHeuristic = 5; + private double maxPollFactorHeuristic = 4; private double maxPollFactorContraction = 200; } diff --git a/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java b/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java index 2bcb1912c3f..d8d5f31c071 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java +++ b/core/src/main/java/com/graphhopper/routing/ch/NodeBasedCHBidirPathExtractor.java @@ -19,16 +19,19 @@ package com.graphhopper.routing.ch; import com.graphhopper.routing.DefaultBidirPathExtractor; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.RoutingCHGraph; public class NodeBasedCHBidirPathExtractor extends DefaultBidirPathExtractor { private final ShortcutUnpacker shortcutUnpacker; private final RoutingCHGraph routingGraph; + private final Weighting weighting; public NodeBasedCHBidirPathExtractor(RoutingCHGraph routingGraph) { super(routingGraph.getBaseGraph(), routingGraph.getWeighting()); this.routingGraph = routingGraph; shortcutUnpacker = createShortcutUnpacker(); + weighting = routingGraph.getBaseGraph().wrapWeighting(routingGraph.getWeighting()); } @Override @@ -42,8 +45,8 @@ public void onEdge(int edge, int adjNode, boolean reverse, int prevOrNextEdge) { private ShortcutUnpacker createShortcutUnpacker() { return new ShortcutUnpacker(routingGraph, (edge, reverse, prevOrNextEdgeId) -> { - path.addDistance(edge.getDistance()); - path.addTime(routingGraph.getWeighting().calcEdgeMillis(edge, reverse)); + path.addDistance_mm(edge.getDistance_mm()); + path.addTime(weighting.calcEdgeMillis(edge, reverse)); path.addEdge(edge.getEdge()); }, false); } diff --git a/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java index 55d92e99d4f..5d747b78a84 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java +++ b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java @@ -109,7 +109,7 @@ public PrepareContractionHierarchies setParams(PMap pMap) { * This will speed up CH preparation, but might lead to slower queries. */ public PrepareContractionHierarchies useFixedNodeOrdering(NodeOrderingProvider nodeOrderingProvider) { - if (nodeOrderingProvider.getNumNodes() != nodes) { + if (nodeOrderingProvider != null && nodeOrderingProvider.getNumNodes() != nodes) { throw new IllegalArgumentException( "contraction order size (" + nodeOrderingProvider.getNumNodes() + ")" + " must be equal to number of nodes in graph (" + nodes + ")."); @@ -148,7 +148,9 @@ public boolean isPrepared() { } private void logFinalGraphStats() { - logger.info("shortcuts that exceed maximum weight: {}", chStore.getNumShortcutsExceedingWeight()); + logger.info("shortcut weights - under minimum: {}, over maximum: {}, minimum valid: {}, maximum valid: {}", + Helper.nf(chStore.getNumShortcutsUnderMinWeight()), Helper.nf(chStore.getNumShortcutsOverMaxWeight()), + chStore.getMinValidWeight(), chStore.getMaxValidWeight()); logger.info("took: {}s, graph now - num edges: {}, num nodes: {}, num shortcuts: {}", (int) allSW.getSeconds(), nf(graph.getEdges()), nf(nodes), nf(chStore.getShortcuts())); } @@ -169,22 +171,16 @@ private boolean isEdgeBased() { } private void initFromGraph() { - // todo: this whole chain of initFromGraph() methods is just needed because PrepareContractionHierarchies does - // not simply prepare contraction hierarchies, but instead it also serves as some kind of 'container' to give - // access to the preparations in the GraphHopper class. If this was not so we could make this a lot cleaner here, - // declare variables final and would not need all these close() methods... + logger.info("Creating CH prepare graph, {}", getMemInfo()); CHPreparationGraph prepareGraph; if (chConfig.getTraversalMode().isEdgeBased()) { TurnCostStorage turnCostStorage = graph.getTurnCostStorage(); - if (turnCostStorage == null) { + if (turnCostStorage == null) throw new IllegalArgumentException("For edge-based CH you need a turn cost storage"); - } - logger.info("Creating CH prepare graph, {}", getMemInfo()); CHPreparationGraph.TurnCostFunction turnCostFunction = CHPreparationGraph.buildTurnCostFunctionFromTurnCostStorage(graph, chConfig.getWeighting()); prepareGraph = CHPreparationGraph.edgeBased(graph.getNodes(), graph.getEdges(), turnCostFunction); nodeContractor = new EdgeBasedNodeContractor(prepareGraph, chBuilder, pMap); } else { - logger.info("Creating CH prepare graph, {}", getMemInfo()); prepareGraph = CHPreparationGraph.nodeBased(graph.getNodes(), graph.getEdges()); nodeContractor = new NodeBasedNodeContractor(prepareGraph, chBuilder, pMap); } diff --git a/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java b/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java index 6de724fd94b..f6a9c22fb78 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java +++ b/core/src/main/java/com/graphhopper/routing/ev/ArrayEdgeIntAccess.java @@ -28,6 +28,13 @@ public ArrayEdgeIntAccess(int intsPerEdge) { this.intsPerEdge = intsPerEdge; } + /** + * Ensures that the underlying storage has enough integers reserved for the specified bytes. + */ + public static ArrayEdgeIntAccess createFromBytes(int bytes) { + return new ArrayEdgeIntAccess((int) Math.ceil((double) bytes / 4)); + } + @Override public int getInt(int edgeId, int index) { int arrIndex = edgeId * intsPerEdge + index; @@ -42,4 +49,4 @@ public void setInt(int edgeId, int index, int value) { arr.set(arrIndex, value); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java new file mode 100644 index 00000000000..9fdec981fc0 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/BikeRoadAccess.java @@ -0,0 +1,47 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +public enum BikeRoadAccess { + MISSING, YES, DESIGNATED, DISMOUNT, DESTINATION, PRIVATE, MILITARY, USE_SIDEPATH, NO; + + public static final String KEY = "bike_road_access"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(BikeRoadAccess.KEY, BikeRoadAccess.class); + } + + @Override + public String toString() { + return Helper.toLowerCase(super.toString()); + } + + public static BikeRoadAccess find(String name) { + if (name == null || name.isEmpty()) + return MISSING; + if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("customers")) + return PRIVATE; + try { + return BikeRoadAccess.valueOf(Helper.toUpperCase(name)); + } catch (IllegalArgumentException ex) { + return YES; + } + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelgiumCountryRule.java b/core/src/main/java/com/graphhopper/routing/ev/BikeTemporalAccess.java similarity index 50% rename from core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelgiumCountryRule.java rename to core/src/main/java/com/graphhopper/routing/ev/BikeTemporalAccess.java index 6616d71b82a..948abbb9985 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelgiumCountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/ev/BikeTemporalAccess.java @@ -15,29 +15,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.graphhopper.routing.util.countryrules.europe; -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; /** - * Defines the default rules for Belgian roads - * - * @author Thomas Butz + * Stores temporary so-called conditional restrictions from access:conditional and other conditional + * tags affecting bikes. See OSMRoadAccessConditionalParser. */ -public class BelgiumCountryRule implements CountryRule { +public enum BikeTemporalAccess { + + MISSING, YES, NO; + + public static final Collection CONDITIONALS = new HashSet<>(Arrays.asList("access:conditional", + "vehicle:conditional", "bicycle:conditional")); + public static final String KEY = "bike_temporal_access"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, BikeTemporalAccess.class); + } @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; + public String toString() { + return Helper.toLowerCase(super.toString()); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/CarTemporalAccess.java b/core/src/main/java/com/graphhopper/routing/ev/CarTemporalAccess.java new file mode 100644 index 00000000000..05e813a79a9 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/CarTemporalAccess.java @@ -0,0 +1,47 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; + +/** + * Stores temporary so-called conditional restrictions from access:conditional and other conditional + * tags affecting cars. See OSMRoadAccessConditionalParser. + */ +public enum CarTemporalAccess { + + MISSING, YES, NO; + + public static final Collection CONDITIONALS = new HashSet<>(Arrays.asList("access:conditional", + "vehicle:conditional", "motor_vehicle:conditional", "motorcar:conditional")); + public static final String KEY = "car_temporal_access"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, CarTemporalAccess.class); + } + + @Override + public String toString() { + return Helper.toLowerCase(super.toString()); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/Country.java b/core/src/main/java/com/graphhopper/routing/ev/Country.java index 60670937425..6f63d24c92d 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/Country.java +++ b/core/src/main/java/com/graphhopper/routing/ev/Country.java @@ -29,229 +29,231 @@ */ public enum Country { - MISSING("missing", "---", "--"), - AFG("Afghanistan", "AFG", "AF"), - AGO("Angola", "AGO", "AO"), - AIA("Anguilla", "AIA", "AI"), - ALB("Albania", "ALB", "AL"), - AND("Andorra", "AND", "AD"), - ARE("United Arab Emirates", "ARE", "AE"), - ARG("Argentina", "ARG", "AR"), - ARM("Armenia", "ARM", "AM"), - ATG("Antigua and Barbuda", "ATG", "AG"), - AUS("Australia", "AUS", "AU", AU_ACT, AU_NSW, AU_NT, AU_QLD, AU_SA, AU_TAS, AU_VIC, AU_WA), - AUT("Austria", "AUT", "AT"), - AZE("Azerbaijan", "AZE", "AZ"), - BDI("Burundi", "BDI", "BI"), - BEL("Belgium", "BEL", "BE"), - BEN("Benin", "BEN", "BJ"), - BFA("Burkina Faso", "BFA", "BF"), - BGD("Bangladesh", "BGD", "BD"), - BGR("Bulgaria", "BGR", "BG"), - BHR("Bahrain", "BHR", "BH"), - BHS("The Bahamas", "BHS", "BS"), - BIH("Bosnia and Herzegovina", "BIH", "BA"), - BLR("Belarus", "BLR", "BY"), - BLZ("Belize", "BLZ", "BZ"), - BMU("Bermuda", "BMU", "BM"), - BOL("Bolivia", "BOL", "BO"), - BRA("Brazil", "BRA", "BR"), - BRB("Barbados", "BRB", "BB"), - BRN("Brunei", "BRN", "BN"), - BTN("Bhutan", "BTN", "BT"), - BWA("Botswana", "BWA", "BW"), - CAF("Central African Republic", "CAF", "CF"), - CAN("Canada", "CAN", "CA", CA_AB, CA_BC, CA_MB, CA_NB, CA_NL, CA_NS, CA_NT, CA_NU, CA_ON, CA_PE, CA_QC, CA_SK, CA_YT), - CHE("Switzerland", "CHE", "CH"), - CHL("Chile", "CHL", "CL"), - CHN("China", "CHN", "CN"), - CIV("Côte d'Ivoire", "CIV", "CI"), - CMR("Cameroon", "CMR", "CM"), - COD("Democratic Republic of the Congo", "COD", "CD"), - COG("Congo-Brazzaville", "COG", "CG"), - COK("Cook Islands", "COK", "CK"), - COL("Colombia", "COL", "CO"), - COM("Comoros", "COM", "KM"), - CPV("Cape Verde", "CPV", "CV"), - CRI("Costa Rica", "CRI", "CR"), - CUB("Cuba", "CUB", "CU"), - CYM("Cayman Islands", "CYM", "KY"), - CYP("Cyprus", "CYP", "CY"), - CZE("Czechia", "CZE", "CZ"), - DEU("Germany", "DEU", "DE"), - DJI("Djibouti", "DJI", "DJ"), - DMA("Dominica", "DMA", "DM"), - DNK("Denmark", "DNK", "DK"), - DOM("Dominican Republic", "DOM", "DO"), - DZA("Algeria", "DZA", "DZ"), - ECU("Ecuador", "ECU", "EC"), - EGY("Egypt", "EGY", "EG"), - ERI("Eritrea", "ERI", "ER"), - ESP("Spain", "ESP", "ES"), - EST("Estonia", "EST", "EE"), - ETH("Ethiopia", "ETH", "ET"), - FIN("Finland", "FIN", "FI"), - FJI("Fiji", "FJI", "FJ"), - FLK("Falkland Islands", "FLK", "FK"), - FRA("France", "FRA", "FR"), - FRO("Faroe Islands", "FRO", "FO"), - FSM("Federated States of Micronesia", "FSM", "FM", FM_KSA, FM_PNI, FM_TRK, FM_YAP), - GAB("Gabon", "GAB", "GA"), - GBR("United Kingdom", "GBR", "GB"), - GEO("Georgia", "GEO", "GE"), - GGY("Guernsey", "GGY", "GG"), - GHA("Ghana", "GHA", "GH"), - GIB("Gibraltar", "GIB", "GI"), - GIN("Guinea", "GIN", "GN"), - GMB("The Gambia", "GMB", "GM"), - GNB("Guinea-Bissau", "GNB", "GW"), - GNQ("Equatorial Guinea", "GNQ", "GQ"), - GRC("Greece", "GRC", "GR"), - GRD("Grenada", "GRD", "GD"), - GRL("Greenland", "GRL", "GL"), - GTM("Guatemala", "GTM", "GT"), - GUY("Guyana", "GUY", "GY"), - HND("Honduras", "HND", "HN"), - HRV("Croatia", "HRV", "HR"), - HTI("Haiti", "HTI", "HT"), - HUN("Hungary", "HUN", "HU"), - IDN("Indonesia", "IDN", "ID"), - IMN("Isle of Man", "IMN", "IM"), - IND("India", "IND", "IN"), - IOT("British Indian Ocean Territory", "IOT", "IO"), - IRL("Ireland", "IRL", "IE"), - IRN("Iran", "IRN", "IR"), - IRQ("Iraq", "IRQ", "IQ"), - ISL("Iceland", "ISL", "IS"), - ISR("Israel", "ISR", "IL"), - ITA("Italy", "ITA", "IT"), - JAM("Jamaica", "JAM", "JM"), - JEY("Jersey", "JEY", "JE"), - JOR("Jordan", "JOR", "JO"), - JPN("Japan", "JPN", "JP"), - KAZ("Kazakhstan", "KAZ", "KZ"), - KEN("Kenya", "KEN", "KE"), - KGZ("Kyrgyzstan", "KGZ", "KG"), - KHM("Cambodia", "KHM", "KH"), - KIR("Kiribati", "KIR", "KI"), - KNA("Saint Kitts and Nevis", "KNA", "KN"), - KOR("South Korea", "KOR", "KR"), - KWT("Kuwait", "KWT", "KW"), - LAO("Laos", "LAO", "LA"), - LBN("Lebanon", "LBN", "LB"), - LBR("Liberia", "LBR", "LR"), - LBY("Libya", "LBY", "LY"), - LCA("Saint Lucia", "LCA", "LC"), - LIE("Liechtenstein", "LIE", "LI"), - LKA("Sri Lanka", "LKA", "LK"), - LSO("Lesotho", "LSO", "LS"), - LTU("Lithuania", "LTU", "LT"), - LUX("Luxembourg", "LUX", "LU"), - LVA("Latvia", "LVA", "LV"), - MAR("Morocco", "MAR", "MA"), - MCO("Monaco", "MCO", "MC"), - MDA("Moldova", "MDA", "MD"), - MDG("Madagascar", "MDG", "MG"), - MDV("Maldives", "MDV", "MV"), - MEX("Mexico", "MEX", "MX"), - MHL("Marshall Islands", "MHL", "MH"), - MKD("North Macedonia", "MKD", "MK"), - MLI("Mali", "MLI", "ML"), - MLT("Malta", "MLT", "MT"), - MMR("Myanmar", "MMR", "MM"), - MNE("Montenegro", "MNE", "ME"), - MNG("Mongolia", "MNG", "MN"), - MOZ("Mozambique", "MOZ", "MZ"), - MRT("Mauritania", "MRT", "MR"), - MSR("Montserrat", "MSR", "MS"), - MUS("Mauritius", "MUS", "MU"), - MWI("Malawi", "MWI", "MW"), - MYS("Malaysia", "MYS", "MY"), - NAM("Namibia", "NAM", "NA"), - NER("Niger", "NER", "NE"), - NGA("Nigeria", "NGA", "NG"), - NIC("Nicaragua", "NIC", "NI"), - NIU("Niue", "NIU", "NU"), - NLD("Netherlands", "NLD", "NL"), - NOR("Norway", "NOR", "NO"), - NPL("Nepal", "NPL", "NP"), - NRU("Nauru", "NRU", "NR"), - NZL("New Zealand", "NZL", "NZ"), - OMN("Oman", "OMN", "OM"), - PAK("Pakistan", "PAK", "PK"), - PAN("Panama", "PAN", "PA"), - PCN("Pitcairn Islands", "PCN", "PN"), - PER("Peru", "PER", "PE"), - PHL("Philippines", "PHL", "PH"), - PLW("Palau", "PLW", "PW"), - PNG("Papua New Guinea", "PNG", "PG"), - POL("Poland", "POL", "PL"), - PRK("North Korea", "PRK", "KP"), - PRT("Portugal", "PRT", "PT"), - PRY("Paraguay", "PRY", "PY"), - PSE("Palestinian Territories", "PSE", "PS"), - QAT("Qatar", "QAT", "QA"), - ROU("Romania", "ROU", "RO"), - RUS("Russia", "RUS", "RU"), - RWA("Rwanda", "RWA", "RW"), - SAU("Saudi Arabia", "SAU", "SA"), - SDN("Sudan", "SDN", "SD"), - SEN("Senegal", "SEN", "SN"), - SGP("Singapore", "SGP", "SG"), - SGS("South Georgia and the South Sandwich Islands", "SGS", "GS"), - SHN("Saint Helena, Ascension and Tristan da Cunha", "SHN", "SH"), - SLB("Solomon Islands", "SLB", "SB"), - SLE("Sierra Leone", "SLE", "SL"), - SLV("El Salvador", "SLV", "SV"), - SMR("San Marino", "SMR", "SM"), - SOM("Somalia", "SOM", "SO"), - SRB("Serbia", "SRB", "RS"), - SSD("South Sudan", "SSD", "SS"), - STP("São Tomé and Príncipe", "STP", "ST"), - SUR("Suriname", "SUR", "SR"), - SVK("Slovakia", "SVK", "SK"), - SVN("Slovenia", "SVN", "SI"), - SWE("Sweden", "SWE", "SE"), - SWZ("Eswatini", "SWZ", "SZ"), - SYC("Seychelles", "SYC", "SC"), - SYR("Syria", "SYR", "SY"), - TCA("Turks and Caicos Islands", "TCA", "TC"), - TCD("Chad", "TCD", "TD"), - TGO("Togo", "TGO", "TG"), - THA("Thailand", "THA", "TH"), - TJK("Tajikistan", "TJK", "TJ"), - TKL("Tokelau", "TKL", "TK"), - TKM("Turkmenistan", "TKM", "TM"), - TLS("East Timor", "TLS", "TL"), - TON("Tonga", "TON", "TO"), - TTO("Trinidad and Tobago", "TTO", "TT"), - TUN("Tunisia", "TUN", "TN"), - TUR("Turkey", "TUR", "TR"), - TUV("Tuvalu", "TUV", "TV"), - TWN("Taiwan", "TWN", "TW"), - TZA("Tanzania", "TZA", "TZ"), - UGA("Uganda", "UGA", "UG"), - UKR("Ukraine", "UKR", "UA"), - URY("Uruguay", "URY", "UY"), + MISSING("missing", "---", "--", true), + AFG("Afghanistan", "AFG", "AF", true), + AGO("Angola", "AGO", "AO", true), + AIA("Anguilla", "AIA", "AI", false), + ALB("Albania", "ALB", "AL", true), + AND("Andorra", "AND", "AD", true), + ARE("United Arab Emirates", "ARE", "AE", true), + ARG("Argentina", "ARG", "AR", true), + ARM("Armenia", "ARM", "AM", true), + ATG("Antigua and Barbuda", "ATG", "AG", false), + AUS("Australia", "AUS", "AU", false, AU_ACT, AU_NSW, AU_NT, AU_QLD, AU_SA, AU_TAS, AU_VIC, AU_WA), + AUT("Austria", "AUT", "AT", true), + AZE("Azerbaijan", "AZE", "AZ", true), + BDI("Burundi", "BDI", "BI", true), + BEL("Belgium", "BEL", "BE", true), + BEN("Benin", "BEN", "BJ", true), + BFA("Burkina Faso", "BFA", "BF", true), + BGD("Bangladesh", "BGD", "BD", true), + BGR("Bulgaria", "BGR", "BG", true), + BHR("Bahrain", "BHR", "BH", true), + BHS("The Bahamas", "BHS", "BS", false), + BIH("Bosnia and Herzegovina", "BIH", "BA", true), + BLR("Belarus", "BLR", "BY", true), + BLZ("Belize", "BLZ", "BZ", true), + BMU("Bermuda", "BMU", "BM", true), + BOL("Bolivia", "BOL", "BO", true), + BRA("Brazil", "BRA", "BR", true), + BRB("Barbados", "BRB", "BB", false), + BRN("Brunei", "BRN", "BN", true), + BTN("Bhutan", "BTN", "BT", false), + BWA("Botswana", "BWA", "BW", false), + CAF("Central African Republic", "CAF", "CF", true), + CAN("Canada", "CAN", "CA", true, CA_AB, CA_BC, CA_MB, CA_NB, CA_NL, CA_NS, CA_NT, CA_NU, CA_ON, CA_PE, CA_QC, CA_SK, CA_YT), + CHE("Switzerland", "CHE", "CH", true), + CHL("Chile", "CHL", "CL", true), + CHN("China", "CHN", "CN", true), + CIV("Côte d'Ivoire", "CIV", "CI", true), + CMR("Cameroon", "CMR", "CM", true), + COD("Democratic Republic of the Congo", "COD", "CD", true), + COG("Congo-Brazzaville", "COG", "CG", true), + COK("Cook Islands", "COK", "CK", true), + COL("Colombia", "COL", "CO", true), + COM("Comoros", "COM", "KM", true), + CPV("Cape Verde", "CPV", "CV", true), + CRI("Costa Rica", "CRI", "CR", true), + CUB("Cuba", "CUB", "CU", true), + CYM("Cayman Islands", "CYM", "KY", false), + CYP("Cyprus", "CYP", "CY", false), + CZE("Czechia", "CZE", "CZ", true), + DEU("Germany", "DEU", "DE", true), + DJI("Djibouti", "DJI", "DJ", true), + DMA("Dominica", "DMA", "DM", false), + DNK("Denmark", "DNK", "DK", true), + DOM("Dominican Republic", "DOM", "DO", true), + DZA("Algeria", "DZA", "DZ", true), + ECU("Ecuador", "ECU", "EC", true), + EGY("Egypt", "EGY", "EG", true), + ERI("Eritrea", "ERI", "ER", true), + ESP("Spain", "ESP", "ES", true), + EST("Estonia", "EST", "EE", true), + ETH("Ethiopia", "ETH", "ET", true), + FIN("Finland", "FIN", "FI", true), + FJI("Fiji", "FJI", "FJ", false), + FLK("Falkland Islands", "FLK", "FK", false), + FRA("France", "FRA", "FR", true), + FRO("Faroe Islands", "FRO", "FO", true), + FSM("Federated States of Micronesia", "FSM", "FM", true, FM_KSA, FM_PNI, FM_TRK, FM_YAP), + GAB("Gabon", "GAB", "GA", true), + GBR("United Kingdom", "GBR", "GB", false), + GEO("Georgia", "GEO", "GE", true), + GGY("Guernsey", "GGY", "GG", false), + GHA("Ghana", "GHA", "GH", true), + GIB("Gibraltar", "GIB", "GI", true), + GIN("Guinea", "GIN", "GN", true), + GMB("The Gambia", "GMB", "GM", true), + GNB("Guinea-Bissau", "GNB", "GW", true), + GNQ("Equatorial Guinea", "GNQ", "GQ", true), + GRC("Greece", "GRC", "GR", true), + GRD("Grenada", "GRD", "GD", false), + GRL("Greenland", "GRL", "GL", true), + GTM("Guatemala", "GTM", "GT", true), + GUY("Guyana", "GUY", "GY", false), + HKG("Hong Kong", "HKG", "HK", false), + HND("Honduras", "HND", "HN", true), + HRV("Croatia", "HRV", "HR", true), + HTI("Haiti", "HTI", "HT", true), + HUN("Hungary", "HUN", "HU", true), + IDN("Indonesia", "IDN", "ID", false), + IMN("Isle of Man", "IMN", "IM", false), + IND("India", "IND", "IN", false), + IOT("British Indian Ocean Territory", "IOT", "IO", true), + IRL("Ireland", "IRL", "IE", false), + IRN("Iran", "IRN", "IR", true), + IRQ("Iraq", "IRQ", "IQ", true), + ISL("Iceland", "ISL", "IS", true), + ISR("Israel", "ISR", "IL", true), + ITA("Italy", "ITA", "IT", true), + JAM("Jamaica", "JAM", "JM", false), + JEY("Jersey", "JEY", "JE", false), + JOR("Jordan", "JOR", "JO", true), + JPN("Japan", "JPN", "JP", false), + KAZ("Kazakhstan", "KAZ", "KZ", true), + KEN("Kenya", "KEN", "KE", false), + KGZ("Kyrgyzstan", "KGZ", "KG", true), + KHM("Cambodia", "KHM", "KH", true), + KIR("Kiribati", "KIR", "KI", false), + KNA("Saint Kitts and Nevis", "KNA", "KN", false), + KOR("South Korea", "KOR", "KR", true), + KWT("Kuwait", "KWT", "KW", true), + LAO("Laos", "LAO", "LA", true), + LBN("Lebanon", "LBN", "LB", true), + LBR("Liberia", "LBR", "LR", true), + LBY("Libya", "LBY", "LY", true), + LCA("Saint Lucia", "LCA", "LC", false), + LIE("Liechtenstein", "LIE", "LI", true), + LKA("Sri Lanka", "LKA", "LK", false), + LSO("Lesotho", "LSO", "LS", false), + LTU("Lithuania", "LTU", "LT", true), + LUX("Luxembourg", "LUX", "LU", true), + LVA("Latvia", "LVA", "LV", true), + MAC("Macao", "MAC", "MO", false), + MAR("Morocco", "MAR", "MA", true), + MCO("Monaco", "MCO", "MC", true), + MDA("Moldova", "MDA", "MD", true), + MDG("Madagascar", "MDG", "MG", true), + MDV("Maldives", "MDV", "MV", false), + MEX("Mexico", "MEX", "MX", true), + MHL("Marshall Islands", "MHL", "MH", true), + MKD("North Macedonia", "MKD", "MK", true), + MLI("Mali", "MLI", "ML", true), + MLT("Malta", "MLT", "MT", false), + MMR("Myanmar", "MMR", "MM", true), + MNE("Montenegro", "MNE", "ME", true), + MNG("Mongolia", "MNG", "MN", true), + MOZ("Mozambique", "MOZ", "MZ", false), + MRT("Mauritania", "MRT", "MR", true), + MSR("Montserrat", "MSR", "MS", true), + MUS("Mauritius", "MUS", "MU", false), + MWI("Malawi", "MWI", "MW", false), + MYS("Malaysia", "MYS", "MY", false), + NAM("Namibia", "NAM", "NA", false), + NER("Niger", "NER", "NE", true), + NGA("Nigeria", "NGA", "NG", true), + NIC("Nicaragua", "NIC", "NI", true), + NIU("Niue", "NIU", "NU", true), + NLD("Netherlands", "NLD", "NL", true), + NOR("Norway", "NOR", "NO", true), + NPL("Nepal", "NPL", "NP", false), + NRU("Nauru", "NRU", "NR", false), + NZL("New Zealand", "NZL", "NZ", false), + OMN("Oman", "OMN", "OM", true), + PAK("Pakistan", "PAK", "PK", false), + PAN("Panama", "PAN", "PA", true), + PCN("Pitcairn Islands", "PCN", "PN", false), + PER("Peru", "PER", "PE", true), + PHL("Philippines", "PHL", "PH", true), + PLW("Palau", "PLW", "PW", true), + PNG("Papua New Guinea", "PNG", "PG", false), + POL("Poland", "POL", "PL", true), + PRK("North Korea", "PRK", "KP", true), + PRT("Portugal", "PRT", "PT", true), + PRY("Paraguay", "PRY", "PY", true), + PSE("Palestinian Territories", "PSE", "PS", true), + QAT("Qatar", "QAT", "QA", true), + ROU("Romania", "ROU", "RO", true), + RUS("Russia", "RUS", "RU", true), + RWA("Rwanda", "RWA", "RW", true), + SAU("Saudi Arabia", "SAU", "SA", true), + SDN("Sudan", "SDN", "SD", true), + SEN("Senegal", "SEN", "SN", true), + SGP("Singapore", "SGP", "SG", false), + SGS("South Georgia and the South Sandwich Islands", "SGS", "GS", true), + SHN("Saint Helena, Ascension and Tristan da Cunha", "SHN", "SH", true), + SLB("Solomon Islands", "SLB", "SB", false), + SLE("Sierra Leone", "SLE", "SL", true), + SLV("El Salvador", "SLV", "SV", true), + SMR("San Marino", "SMR", "SM", true), + SOM("Somalia", "SOM", "SO", true), + SRB("Serbia", "SRB", "RS", true), + SSD("South Sudan", "SSD", "SS", true), + STP("São Tomé and Príncipe", "STP", "ST", true), + SUR("Suriname", "SUR", "SR", false), + SVK("Slovakia", "SVK", "SK", true), + SVN("Slovenia", "SVN", "SI", true), + SWE("Sweden", "SWE", "SE", true), + SWZ("Eswatini", "SWZ", "SZ", false), + SYC("Seychelles", "SYC", "SC", false), + SYR("Syria", "SYR", "SY", true), + TCA("Turks and Caicos Islands", "TCA", "TC", false), + TCD("Chad", "TCD", "TD", true), + TGO("Togo", "TGO", "TG", true), + THA("Thailand", "THA", "TH", false), + TJK("Tajikistan", "TJK", "TJ", true), + TKL("Tokelau", "TKL", "TK", true), + TKM("Turkmenistan", "TKM", "TM", true), + TLS("Timor-Leste", "TLS", "TL", false), // East Timor + TON("Tonga", "TON", "TO", false), + TTO("Trinidad and Tobago", "TTO", "TT", false), + TUN("Tunisia", "TUN", "TN", true), + TUR("Turkey", "TUR", "TR", true), + TUV("Tuvalu", "TUV", "TV", false), + TWN("Taiwan", "TWN", "TW", true), + TZA("Tanzania", "TZA", "TZ", false), + UGA("Uganda", "UGA", "UG", false), + UKR("Ukraine", "UKR", "UA", true), + URY("Uruguay", "URY", "UY", true), USA("United States", "USA", "US", - US_AL, US_AK, US_AZ, US_AR, US_CA, US_CO, US_CT, US_DE, US_DC, US_FL, + true, US_AL, US_AK, US_AZ, US_AR, US_CA, US_CO, US_CT, US_DE, US_DC, US_FL, US_GA, US_HI, US_ID, US_IL, US_IN, US_IA, US_KS, US_KY, US_LA, US_ME, US_MD, US_MA, US_MI, US_MN, US_MS, US_MO, US_MT, US_NE, US_NV, US_NH, US_NJ, US_NM, US_NY, US_NC, US_ND, US_OH, US_OK, US_OR, US_PA, US_RI, US_SC, US_SD, US_TN, US_TX, US_UT, US_VT, US_VA, US_WA, US_WV, US_WI, US_WY), - UZB("Uzbekistan", "UZB", "UZ"), - VAT("Vatican City", "VAT", "VA"), - VCT("Saint Vincent and the Grenadines", "VCT", "VC"), - VEN("Venezuela", "VEN", "VE"), - VGB("British Virgin Islands", "VGB", "VG"), - VNM("Vietnam", "VNM", "VN"), - VUT("Vanuatu", "VUT", "VU"), - WSM("Samoa", "WSM", "WS"), - XKX("Kosovo", "XKX", "XK"), - YEM("Yemen", "YEM", "YE"), - ZAF("South Africa", "ZAF", "ZA"), - ZMB("Zambia", "ZMB", "ZM"), - ZWE("Zimbabwe", "ZWE", "ZW"); + UZB("Uzbekistan", "UZB", "UZ", true), + VAT("Vatican City", "VAT", "VA", true), + VCT("Saint Vincent and the Grenadines", "VCT", "VC", false), + VEN("Venezuela", "VEN", "VE", true), + VGB("British Virgin Islands", "VGB", "VG", false), + VNM("Vietnam", "VNM", "VN", true), + VUT("Vanuatu", "VUT", "VU", true), + WSM("Samoa", "WSM", "WS", false), + XKX("Kosovo", "XKX", "XK", true), + YEM("Yemen", "YEM", "YE", true), + ZAF("South Africa", "ZAF", "ZA", false), + ZMB("Zambia", "ZMB", "ZM", false), + ZWE("Zimbabwe", "ZWE", "ZW", false); public static final String KEY = "country"; @@ -271,11 +273,13 @@ public enum Country { // ISO 3166-1 alpha3 private final String alpha3; private final List states; + private final boolean isRightHandTraffic; - Country(String countryName, String alpha3, String alpha2, State... states) { + Country(String countryName, String alpha3, String alpha2, boolean isRightHandTraffic, State... states) { this.countryName = countryName; this.alpha2 = alpha2; this.alpha3 = alpha3; + this.isRightHandTraffic = isRightHandTraffic; this.states = Arrays.asList(states); } @@ -297,6 +301,10 @@ public String getAlpha3() { return alpha3; } + public boolean isRightHandTraffic() { + return isRightHandTraffic; + } + public List getStates() { return states; } diff --git a/core/src/main/java/com/graphhopper/routing/ev/Cycleway.java b/core/src/main/java/com/graphhopper/routing/ev/Cycleway.java new file mode 100644 index 00000000000..124417e63de --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/Cycleway.java @@ -0,0 +1,63 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +/** + * This enum defines the cycleway encoded value, parsed from cycleway, cycleway:left, cycleway:right and + * cycleway:both OSM tags. If not tagged or unknown the value will be MISSING. + *

+ * This encoded value stores two directions. The OSM tags cycleway:right and cycleway:left map to forward and reverse + * respectively. The deprecated opposite_lane and opposite_track tags are stored as LANE and TRACK in the reverse + * direction. + * + * @see Key:cycleway + */ +public enum Cycleway { + MISSING, TRACK, LANE, SHARED_LANE, SHOULDER, SEPARATE, NO; + + public static final String KEY = "cycleway"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, Cycleway.class, true); + } + + @Override + public String toString() { + return Helper.toLowerCase(super.toString()); + } + + public static Cycleway find(String name) { + if (Helper.isEmpty(name)) + return MISSING; + switch (name) { + case "sidepath": + return SEPARATE; + case "crossing": + return TRACK; + case "share_busway", "shared": + return SHARED_LANE; + } + try { + return Cycleway.valueOf(Helper.toUpperCase(name)); + } catch (IllegalArgumentException ex) { + return MISSING; + } + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java deleted file mode 100644 index 6fffefd5823..00000000000 --- a/core/src/main/java/com/graphhopper/routing/ev/DefaultEncodedValueFactory.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.ev; - -import com.graphhopper.util.PMap; - -public class DefaultEncodedValueFactory implements EncodedValueFactory { - - @Override - public EncodedValue create(String name, PMap properties) { - if (Roundabout.KEY.equals(name)) { - return Roundabout.create(); - } else if (GetOffBike.KEY.equals(name)) { - return GetOffBike.create(); - } else if (RoadClass.KEY.equals(name)) { - return RoadClass.create(); - } else if (RoadClassLink.KEY.equals(name)) { - return RoadClassLink.create(); - } else if (RoadEnvironment.KEY.equals(name)) { - return RoadEnvironment.create(); - } else if (RoadAccess.KEY.equals(name)) { - return RoadAccess.create(); - } else if (MaxSpeed.KEY.equals(name)) { - return MaxSpeed.create(); - } else if (MaxSpeedEstimated.KEY.equals(name)) { - return MaxSpeedEstimated.create(); - } else if (MaxWeight.KEY.equals(name)) { - return MaxWeight.create(); - } else if (MaxWeightExcept.KEY.equals(name)) { - return MaxWeightExcept.create(); - } else if (MaxHeight.KEY.equals(name)) { - return MaxHeight.create(); - } else if (MaxWidth.KEY.equals(name)) { - return MaxWidth.create(); - } else if (MaxAxleLoad.KEY.equals(name)) { - return MaxAxleLoad.create(); - } else if (MaxLength.KEY.equals(name)) { - return MaxLength.create(); - } else if (Hgv.KEY.equals(name)) { - return Hgv.create(); - } else if (Surface.KEY.equals(name)) { - return Surface.create(); - } else if (Smoothness.KEY.equals(name)) { - return Smoothness.create(); - } else if (Toll.KEY.equals(name)) { - return Toll.create(); - } else if (TrackType.KEY.equals(name)) { - return TrackType.create(); - } else if (BikeNetwork.KEY.equals(name) || MtbNetwork.KEY.equals(name) || FootNetwork.KEY.equals(name)) { - return RouteNetwork.create(name); - } else if (Hazmat.KEY.equals(name)) { - return Hazmat.create(); - } else if (HazmatTunnel.KEY.equals(name)) { - return HazmatTunnel.create(); - } else if (HazmatWater.KEY.equals(name)) { - return HazmatWater.create(); - } else if (Lanes.KEY.equals(name)) { - return Lanes.create(); - } else if (Footway.KEY.equals(name)) { - return Footway.create(); - } else if (OSMWayID.KEY.equals(name)) { - return OSMWayID.create(); - } else if (MtbRating.KEY.equals(name)) { - return MtbRating.create(); - } else if (HikeRating.KEY.equals(name)) { - return HikeRating.create(); - } else if (HorseRating.KEY.equals(name)) { - return HorseRating.create(); - } else if (Country.KEY.equals(name)) { - return Country.create(); - } else if (State.KEY.equals(name)) { - return State.create(); - } else if (name.endsWith(Subnetwork.key(""))) { - return Subnetwork.create(name); - } else if (MaxSlope.KEY.equals(name)) { - return MaxSlope.create(); - } else if (AverageSlope.KEY.equals(name)) { - return AverageSlope.create(); - } else if (Curvature.KEY.equals(name)) { - return Curvature.create(); - } else if (Crossing.KEY.equals(name)) { - return Crossing.create(); - } else if (FerrySpeed.KEY.equals(name)) { - return FerrySpeed.create(); - } else if (BusAccess.KEY.equals(name)) { - return BusAccess.create(); - } else if (Hov.KEY.equals(name)) { - return Hov.create(); - } else { - throw new IllegalArgumentException("DefaultEncodedValueFactory cannot find EncodedValue " + name); - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java new file mode 100644 index 00000000000..c9511037b25 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java @@ -0,0 +1,348 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +import com.graphhopper.routing.util.*; +import com.graphhopper.routing.util.parsers.*; +import com.graphhopper.util.PMap; + +public class DefaultImportRegistry implements ImportRegistry { + @Override + public ImportUnit createImportUnit(String name) { + if (Roundabout.KEY.equals(name)) + return ImportUnit.create(name, props -> Roundabout.create(), + (lookup, props) -> new OSMRoundaboutParser( + lookup.getBooleanEncodedValue(Roundabout.KEY)) + ); + else if (GetOffBike.KEY.equals(name)) + return ImportUnit.create(name, props -> GetOffBike.create(), + (lookup, pros) -> new OSMGetOffBikeParser( + lookup.getBooleanEncodedValue(GetOffBike.KEY), + lookup.getBooleanEncodedValue("bike_access") + ), "bike_access"); + else if (RoadClass.KEY.equals(name)) + return ImportUnit.create(name, props -> RoadClass.create(), + (lookup, props) -> new OSMRoadClassParser( + lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class)) + ); + else if (RoadClassLink.KEY.equals(name)) + return ImportUnit.create(name, props -> RoadClassLink.create(), + (lookup, props) -> new OSMRoadClassLinkParser( + lookup.getBooleanEncodedValue(RoadClassLink.KEY)) + ); + else if (RoadEnvironment.KEY.equals(name)) + return ImportUnit.create(name, props -> RoadEnvironment.create(), + (lookup, props) -> new OSMRoadEnvironmentParser( + lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)) + ); + else if (FootRoadAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> FootRoadAccess.create(), + (lookup, props) -> OSMRoadAccessParser.forFoot( + lookup.getEnumEncodedValue(FootRoadAccess.KEY, FootRoadAccess.class)) + ); + else if (BikeRoadAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> BikeRoadAccess.create(), + (lookup, props) -> OSMRoadAccessParser.forBike( + lookup.getEnumEncodedValue(BikeRoadAccess.KEY, BikeRoadAccess.class)) + ); + else if (RoadAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> RoadAccess.create(), + (lookup, props) -> OSMRoadAccessParser.forCar( + lookup.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class)) + ); + else if (MaxSpeed.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxSpeed.create(), + (lookup, props) -> new OSMMaxSpeedParser( + lookup.getDecimalEncodedValue(MaxSpeed.KEY)) + ); + else if (MaxSpeedEstimated.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxSpeedEstimated.create(), + null, Country.KEY, UrbanDensity.KEY); + else if (UrbanDensity.KEY.equals(name)) + return ImportUnit.create(name, props -> UrbanDensity.create(), + null); + else if (MaxWeight.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxWeight.create(), + (lookup, props) -> new OSMMaxWeightParser( + lookup.getDecimalEncodedValue(MaxWeight.KEY)) + ); + else if (MaxWeightExcept.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxWeightExcept.create(), + (lookup, props) -> new MaxWeightExceptParser( + lookup.getEnumEncodedValue(MaxWeightExcept.KEY, MaxWeightExcept.class)) + ); + else if (MaxHeight.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxHeight.create(), + (lookup, props) -> new OSMMaxHeightParser( + lookup.getDecimalEncodedValue(MaxHeight.KEY)) + ); + else if (MaxWidth.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxWidth.create(), + (lookup, props) -> new OSMMaxWidthParser( + lookup.getDecimalEncodedValue(MaxWidth.KEY)) + ); + else if (MaxAxleLoad.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxAxleLoad.create(), + (lookup, props) -> new OSMMaxAxleLoadParser( + lookup.getDecimalEncodedValue(MaxAxleLoad.KEY)) + ); + else if (MaxLength.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxLength.create(), + (lookup, props) -> new OSMMaxLengthParser( + lookup.getDecimalEncodedValue(MaxLength.KEY)) + ); + else if (Orientation.KEY.equals(name)) + return ImportUnit.create(name, props -> Orientation.create(), + (lookup, props) -> new OrientationCalculator( + lookup.getDecimalEncodedValue(Orientation.KEY)) + ); + else if (Surface.KEY.equals(name)) + return ImportUnit.create(name, props -> Surface.create(), + (lookup, props) -> new OSMSurfaceParser( + lookup.getEnumEncodedValue(Surface.KEY, Surface.class)) + ); + else if (Smoothness.KEY.equals(name)) + return ImportUnit.create(name, props -> Smoothness.create(), + (lookup, props) -> new OSMSmoothnessParser( + lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class)) + ); + else if (Hgv.KEY.equals(name)) + return ImportUnit.create(name, props -> Hgv.create(), + (lookup, props) -> new OSMHgvParser( + lookup.getEnumEncodedValue(Hgv.KEY, Hgv.class) + )); + else if (Toll.KEY.equals(name)) + return ImportUnit.create(name, props -> Toll.create(), + (lookup, props) -> new OSMTollParser( + lookup.getEnumEncodedValue(Toll.KEY, Toll.class)) + ); + else if (TrackType.KEY.equals(name)) + return ImportUnit.create(name, props -> TrackType.create(), + (lookup, props) -> new OSMTrackTypeParser( + lookup.getEnumEncodedValue(TrackType.KEY, TrackType.class)) + ); + else if (Hazmat.KEY.equals(name)) + return ImportUnit.create(name, props -> Hazmat.create(), + (lookup, props) -> new OSMHazmatParser( + lookup.getEnumEncodedValue(Hazmat.KEY, Hazmat.class)) + ); + else if (HazmatTunnel.KEY.equals(name)) + return ImportUnit.create(name, props -> HazmatTunnel.create(), + (lookup, props) -> new OSMHazmatTunnelParser( + lookup.getEnumEncodedValue(HazmatTunnel.KEY, HazmatTunnel.class)) + ); + else if (HazmatWater.KEY.equals(name)) + return ImportUnit.create(name, props -> HazmatWater.create(), + (lookup, props) -> new OSMHazmatWaterParser( + lookup.getEnumEncodedValue(HazmatWater.KEY, HazmatWater.class)) + ); + else if (Lanes.KEY.equals(name)) + return ImportUnit.create(name, props -> Lanes.create(), + (lookup, props) -> new OSMLanesParser( + lookup.getIntEncodedValue(Lanes.KEY)) + ); + else if (Footway.KEY.equals(name)) + return ImportUnit.create(name, props -> Footway.create(), + (lookup, props) -> new OSMFootwayParser( + lookup.getEnumEncodedValue(Footway.KEY, Footway.class)) + ); + else if (Sidewalk.KEY.equals(name)) + return ImportUnit.create(name, props -> Sidewalk.create(), + (lookup, props) -> new OSMSidewalkParser( + lookup.getEnumEncodedValue(Sidewalk.KEY, Sidewalk.class)) + ); + else if (Cycleway.KEY.equals(name)) + return ImportUnit.create(name, props -> Cycleway.create(), + (lookup, props) -> new OSMCyclewayParser( + lookup.getEnumEncodedValue(Cycleway.KEY, Cycleway.class)) + ); + else if (OSMWayID.KEY.equals(name)) + return ImportUnit.create(name, props -> OSMWayID.create(), + (lookup, props) -> new OSMWayIDParser( + lookup.getIntEncodedValue(OSMWayID.KEY)) + ); + else if (MtbRating.KEY.equals(name)) + return ImportUnit.create(name, props -> MtbRating.create(), + (lookup, props) -> new OSMMtbRatingParser( + lookup.getIntEncodedValue(MtbRating.KEY)) + ); + else if (HikeRating.KEY.equals(name)) + return ImportUnit.create(name, props -> HikeRating.create(), + (lookup, props) -> new OSMHikeRatingParser( + lookup.getIntEncodedValue(HikeRating.KEY)) + ); + else if (HorseRating.KEY.equals(name)) + return ImportUnit.create(name, props -> HorseRating.create(), + (lookup, props) -> new OSMHorseRatingParser( + lookup.getIntEncodedValue(HorseRating.KEY)) + ); + else if (Country.KEY.equals(name)) + return ImportUnit.create(name, props -> Country.create(), + (lookup, props) -> new CountryParser( + lookup.getEnumEncodedValue(Country.KEY, Country.class)) + ); + else if (State.KEY.equals(name)) + return ImportUnit.create(name, props -> State.create(), + (lookup, props) -> new StateParser( + lookup.getEnumEncodedValue(State.KEY, State.class)) + ); + else if (Crossing.KEY.equals(name)) + return ImportUnit.create(name, props -> Crossing.create(), + (lookup, props) -> new OSMCrossingParser( + lookup.getEnumEncodedValue(Crossing.KEY, Crossing.class)) + ); + else if (FerrySpeed.KEY.equals(name)) + return ImportUnit.create(name, props -> FerrySpeed.create(), + (lookup, props) -> new FerrySpeedCalculator( + lookup.getDecimalEncodedValue(FerrySpeed.KEY))); + else if (Curvature.KEY.equals(name)) + return ImportUnit.create(name, props -> Curvature.create(), null); + else if (AverageSlope.KEY.equals(name)) + return ImportUnit.create(name, props -> AverageSlope.create(), null); + else if (MaxSlope.KEY.equals(name)) + return ImportUnit.create(name, props -> MaxSlope.create(), null); + else if (BikeNetwork.KEY.equals(name) || MtbNetwork.KEY.equals(name) || FootNetwork.KEY.equals(name)) + return ImportUnit.create(name, props -> RouteNetwork.create(name), null); + + else if (BusAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> BusAccess.create(), + (lookup, props) -> new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BUS), + lookup.getBooleanEncodedValue(name), true, lookup.getBooleanEncodedValue(Roundabout.KEY), + PMap.toSet(props.getString("restrictions", "")), PMap.toSet(props.getString("barriers", ""))), + "roundabout" + ); + + else if (HovAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> HovAccess.create(), + (lookup, props) -> new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.HOV), + lookup.getBooleanEncodedValue(name), true, lookup.getBooleanEncodedValue(Roundabout.KEY), + PMap.toSet(props.getString("restrictions", "")), PMap.toSet(props.getString("barriers", ""))), + "roundabout" + ); + else if (FootTemporalAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> FootTemporalAccess.create(), + (lookup, props) -> { + EnumEncodedValue enc = lookup.getEnumEncodedValue(FootTemporalAccess.KEY, FootTemporalAccess.class); + OSMTemporalAccessParser.Setter fct = (edgeId, edgeIntAccess, b) -> enc.setEnum(false, edgeId, edgeIntAccess, b ? FootTemporalAccess.YES : FootTemporalAccess.NO); + return new OSMTemporalAccessParser(FootTemporalAccess.CONDITIONALS, fct, props.getString("date_range_parser_day", "")); + } + ); + + else if (BikeTemporalAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> BikeTemporalAccess.create(), + (lookup, props) -> { + EnumEncodedValue enc = lookup.getEnumEncodedValue(BikeTemporalAccess.KEY, BikeTemporalAccess.class); + OSMTemporalAccessParser.Setter fct = (edgeId, edgeIntAccess, b) -> enc.setEnum(false, edgeId, edgeIntAccess, b ? BikeTemporalAccess.YES : BikeTemporalAccess.NO); + return new OSMTemporalAccessParser(BikeTemporalAccess.CONDITIONALS, fct, props.getString("date_range_parser_day", "")); + } + ); + + else if (CarTemporalAccess.KEY.equals(name)) + return ImportUnit.create(name, props -> CarTemporalAccess.create(), + (lookup, props) -> { + EnumEncodedValue enc = lookup.getEnumEncodedValue(CarTemporalAccess.KEY, CarTemporalAccess.class); + OSMTemporalAccessParser.Setter fct = (edgeId, edgeIntAccess, b) -> enc.setEnum(false, edgeId, edgeIntAccess, b ? CarTemporalAccess.YES : CarTemporalAccess.NO); + return new OSMTemporalAccessParser(CarTemporalAccess.CONDITIONALS, fct, props.getString("date_range_parser_day", "")); + } + ); + + else if (VehicleAccess.key("car").equals(name)) + return ImportUnit.create(name, props -> VehicleAccess.create("car"), + CarAccessParser::new, + "roundabout" + ); + else if (VehicleAccess.key("roads").equals(name)) + throw new IllegalArgumentException("roads_access parser no longer necessary, see docs/migration/config-migration-08-09.md"); + else if (VehicleAccess.key("bike").equals(name)) + return ImportUnit.create(name, props -> VehicleAccess.create("bike"), + BikeAccessParser::new, + "roundabout" + ); + else if (VehicleAccess.key("racingbike").equals(name)) + return ImportUnit.create(name, props -> VehicleAccess.create("racingbike"), + RacingBikeAccessParser::new, + "roundabout" + ); + else if (VehicleAccess.key("mtb").equals(name)) + return ImportUnit.create(name, props -> VehicleAccess.create("mtb"), + MountainBikeAccessParser::new, + "roundabout" + ); + else if (VehicleAccess.key("foot").equals(name)) + return ImportUnit.create(name, props -> VehicleAccess.create("foot"), + FootAccessParser::new); + + else if (VehicleSpeed.key("car").equals(name)) + return ImportUnit.create(name, props -> new DecimalEncodedValueImpl( + name, props.getInt("speed_bits", 7), props.getDouble("speed_factor", 2), true), + (lookup, props) -> new CarAverageSpeedParser(lookup) + ); + else if (VehicleSpeed.key("roads").equals(name)) + throw new IllegalArgumentException("roads_average_speed parser no longer necessary, see docs/migration/config-migration-08-09.md"); + else if (VehicleSpeed.key("bike").equals(name)) + return ImportUnit.create(name, props -> new DecimalEncodedValueImpl( + name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false), + (lookup, props) -> new BikeAverageSpeedParser(lookup), + Smoothness.KEY + ); + else if (VehicleSpeed.key("racingbike").equals(name)) + return ImportUnit.create(name, props -> new DecimalEncodedValueImpl( + name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false), + (lookup, props) -> new RacingBikeAverageSpeedParser(lookup), + Smoothness.KEY + ); + else if (VehicleSpeed.key("mtb").equals(name)) + return ImportUnit.create(name, props -> new DecimalEncodedValueImpl( + name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 2), false), + (lookup, props) -> new MountainBikeAverageSpeedParser(lookup), + Smoothness.KEY + ); + else if (VehicleSpeed.key("foot").equals(name)) + return ImportUnit.create(name, props -> new DecimalEncodedValueImpl( + name, props.getInt("speed_bits", 4), props.getDouble("speed_factor", 1), false), + (lookup, props) -> new FootAverageSpeedParser(lookup) + ); + else if (VehiclePriority.key("foot").equals(name)) + return ImportUnit.create(name, props -> VehiclePriority.create("foot", 4, PriorityCode.getFactor(1), false), + (lookup, props) -> new FootPriorityParser(lookup), + RouteNetwork.key("foot") + ); + else if (VehiclePriority.key("bike").equals(name)) + return ImportUnit.create(name, props -> VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false), + (lookup, props) -> new BikePriorityParser(lookup), + VehicleSpeed.key("bike"), BikeNetwork.KEY + ); + else if (VehiclePriority.key("racingbike").equals(name)) + return ImportUnit.create(name, props -> VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false), + (lookup, props) -> new RacingBikePriorityParser(lookup), + VehicleSpeed.key("racingbike"), BikeNetwork.KEY + ); + else if (VehiclePriority.key("mtb").equals(name)) + return ImportUnit.create(name, props -> VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false), + (lookup, props) -> new MountainBikePriorityParser(lookup), + VehicleSpeed.key("mtb"), BikeNetwork.KEY, MtbNetwork.KEY + ); + else if (Lit.KEY.equals(name)) + return ImportUnit.create(name, props -> Lit.create(), + (lookup, props) -> new OSMLitParser( + lookup.getBooleanEncodedValue(Lit.KEY)) + ); + return null; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java b/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java index 6f8040b00af..7fc8358e894 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java +++ b/core/src/main/java/com/graphhopper/routing/ev/EncodedValue.java @@ -68,12 +68,16 @@ void next(int usedBits) { nextShift = shift + usedBits; } - public int getRequiredBits() { + private int getRequiredBits() { return (dataIndex) * 32 + nextShift; } public int getRequiredInts() { return (int) Math.ceil((double) getRequiredBits() / 32.0); } + + public int getRequiredBytes() { + return (int) Math.ceil((double) getRequiredBits() / 8.0); + } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java b/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java index 82d5bd6185e..fd30efe56eb 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java +++ b/core/src/main/java/com/graphhopper/routing/ev/EncodedValueSerializer.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; public class EncodedValueSerializer { private final static ObjectMapper MAPPER = new ObjectMapper(); @@ -31,7 +31,7 @@ public class EncodedValueSerializer { static { MAPPER.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); MAPPER.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY); - MAPPER.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + MAPPER.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); } public static String serializeEncodedValue(EncodedValue encodedValue) { diff --git a/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java new file mode 100644 index 00000000000..85b19e77964 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/FootRoadAccess.java @@ -0,0 +1,47 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +public enum FootRoadAccess { + MISSING, YES, DESIGNATED, DESTINATION, PRIVATE, MILITARY, USE_SIDEPATH, NO; + + public static final String KEY = "foot_road_access"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(FootRoadAccess.KEY, FootRoadAccess.class); + } + + @Override + public String toString() { + return Helper.toLowerCase(super.toString()); + } + + public static FootRoadAccess find(String name) { + if (name == null || name.isEmpty()) + return MISSING; + if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("customers")) + return PRIVATE; + try { + return FootRoadAccess.valueOf(Helper.toUpperCase(name)); + } catch (IllegalArgumentException ex) { + return YES; + } + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CroatiaCountryRule.java b/core/src/main/java/com/graphhopper/routing/ev/FootTemporalAccess.java similarity index 51% rename from core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CroatiaCountryRule.java rename to core/src/main/java/com/graphhopper/routing/ev/FootTemporalAccess.java index 9d5a9df449a..eac70d4da2e 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CroatiaCountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/ev/FootTemporalAccess.java @@ -15,29 +15,33 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.graphhopper.routing.util.countryrules.europe; -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; /** - * Defines the default rules for Croatian roads - * - * @author Thomas Butz + * Stores temporary so-called conditional restrictions from access:conditional and other conditional + * tags affecting foot. See OSMRoadAccessConditionalParser. */ -public class CroatiaCountryRule implements CountryRule { +public enum FootTemporalAccess { + + MISSING, YES, NO; + + public static final Collection CONDITIONALS = new HashSet<>(Arrays.asList("access:conditional", + "foot:conditional")); + public static final String KEY = "foot_temporal_access"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, FootTemporalAccess.class); + } @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; + public String toString() { + return Helper.toLowerCase(super.toString()); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/Hov.java b/core/src/main/java/com/graphhopper/routing/ev/Hov.java deleted file mode 100644 index 2cf1e9b00cc..00000000000 --- a/core/src/main/java/com/graphhopper/routing/ev/Hov.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.graphhopper.routing.ev; - -import com.graphhopper.util.Helper; - -public enum Hov { - MISSING, YES, DESIGNATED, NO; - - public static final String KEY = "hov"; - - public static EnumEncodedValue create() { - return new EnumEncodedValue<>(Hov.KEY, Hov.class); - } - - @Override - public String toString() { - return Helper.toLowerCase(super.toString()); - } - - public static Hov find(String name) { - if (name == null) - return MISSING; - try { - return Hov.valueOf(Helper.toUpperCase(name)); - } catch (IllegalArgumentException ex) { - return MISSING; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java b/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java new file mode 100644 index 00000000000..71d77cfd792 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/HovAccess.java @@ -0,0 +1,13 @@ +package com.graphhopper.routing.ev; + +/** + * High-occupancy vehicle (carpool, diamond, transit, T2, or T3). + * See also here. + */ +public class HovAccess { + public final static String KEY = "hov_access"; + + public static BooleanEncodedValue create() { + return new SimpleBooleanEncodedValue(KEY, true); + } +} diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundleConfiguration.java b/core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java similarity index 86% rename from web-bundle/src/main/java/com/graphhopper/http/RealtimeBundleConfiguration.java rename to core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java index d75d763be98..283321e3574 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundleConfiguration.java +++ b/core/src/main/java/com/graphhopper/routing/ev/ImportRegistry.java @@ -16,10 +16,8 @@ * limitations under the License. */ -package com.graphhopper.http; - -public interface RealtimeBundleConfiguration { - - RealtimeConfiguration gtfsrealtime(); +package com.graphhopper.routing.ev; +public interface ImportRegistry { + ImportUnit createImportUnit(String name); } diff --git a/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java b/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java new file mode 100644 index 00000000000..04fd08a389c --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/ImportUnit.java @@ -0,0 +1,61 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +import com.graphhopper.routing.util.parsers.TagParser; +import com.graphhopper.util.PMap; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Function; + +public class ImportUnit { + private final String name; + private final Function createEncodedValue; + private final BiFunction createTagParser; + private final List requiredImportUnits; + + public static ImportUnit create(String name, Function createEncodedValue, BiFunction createTagParser, String... requiredImportUnits) { + return new ImportUnit(name, createEncodedValue, createTagParser, List.of(requiredImportUnits)); + } + + private ImportUnit(String name, Function createEncodedValue, BiFunction createTagParser, List requiredImportUnits) { + this.name = name; + this.createEncodedValue = createEncodedValue; + this.createTagParser = createTagParser; + this.requiredImportUnits = requiredImportUnits; + } + + public Function getCreateEncodedValue() { + return createEncodedValue; + } + + public BiFunction getCreateTagParser() { + return createTagParser; + } + + public List getRequiredImportUnits() { + return requiredImportUnits; + } + + @Override + public String toString() { + return "ImportUnit: " + name + " (requires: " + requiredImportUnits + ")"; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java b/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java new file mode 100644 index 00000000000..301a4c49207 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/ImportUnitSorter.java @@ -0,0 +1,57 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +import java.util.*; + +// topological sort with a depth first search +public class ImportUnitSorter { + Set permanentMarked = new HashSet<>(); + Set temporaryMarked = new HashSet<>(); + List result = new ArrayList<>(); + final Map map; + + public ImportUnitSorter(Map map) { + this.map = map; + } + + public List sort() { + for (String strN : map.keySet()) { + visit(strN); + } + return result; + } + + private void visit(String strN) { + if (permanentMarked.contains(strN)) return; + ImportUnit importUnit = map.get(strN); + if (importUnit == null) + throw new IllegalArgumentException("cannot find import unit " + strN); + if (temporaryMarked.contains(strN)) + throw new IllegalArgumentException("import units with cyclic dependencies are not allowed: " + importUnit + " " + importUnit.getRequiredImportUnits()); + + temporaryMarked.add(strN); + for (String strM : importUnit.getRequiredImportUnits()) { + visit(strM); + } + temporaryMarked.remove(strN); + permanentMarked.add(strN); + result.add(strN); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java b/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java index 71c14628985..334a1794b75 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java +++ b/core/src/main/java/com/graphhopper/routing/ev/IntEncodedValueImpl.java @@ -189,6 +189,9 @@ final void uncheckedSet(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess @Override public final int getInt(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) { + assert fwdShift >= 0 : "incorrect shift " + fwdShift + " for " + getName(); + assert bits > 0 : "incorrect bits " + bits + " for " + getName(); + int flags; // if we do not store both directions ignore reverse == true for convenient reading if (storeTwoDirections && reverse) { diff --git a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java b/core/src/main/java/com/graphhopper/routing/ev/Lit.java similarity index 82% rename from core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java rename to core/src/main/java/com/graphhopper/routing/ev/Lit.java index c8fcbcc993e..6069b2d803b 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/EncodedValueFactory.java +++ b/core/src/main/java/com/graphhopper/routing/ev/Lit.java @@ -17,8 +17,11 @@ */ package com.graphhopper.routing.ev; -import com.graphhopper.util.PMap; +public class Lit { + public final static String KEY = "lit"; + + public static BooleanEncodedValue create() { + return new SimpleBooleanEncodedValue(KEY, false); + } -public interface EncodedValueFactory { - EncodedValue create(String name, PMap properties); } diff --git a/core/src/main/java/com/graphhopper/routing/ev/MaxSlope.java b/core/src/main/java/com/graphhopper/routing/ev/MaxSlope.java index 26282b45704..a3add461bdc 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/MaxSlope.java +++ b/core/src/main/java/com/graphhopper/routing/ev/MaxSlope.java @@ -7,6 +7,6 @@ public class MaxSlope { public static final String KEY = "max_slope"; public static DecimalEncodedValue create() { - return new DecimalEncodedValueImpl(KEY, 5, 1, false); + return new DecimalEncodedValueImpl(KEY, 5, 0, 1, true, false, false); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/MaxSpeed.java b/core/src/main/java/com/graphhopper/routing/ev/MaxSpeed.java index e8239955662..2943d4b3c84 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/MaxSpeed.java +++ b/core/src/main/java/com/graphhopper/routing/ev/MaxSpeed.java @@ -18,7 +18,7 @@ package com.graphhopper.routing.ev; /** - * This EncodedValue stores maximum speed values for car in km/h. If not initialized it returns UNSET_SPEED. + * This EncodedValue stores maximum speed values for car in km/h. */ public class MaxSpeed { public static final String KEY = "max_speed"; @@ -28,11 +28,11 @@ public class MaxSpeed { * not explicitly used in OSM and can be precisely returned for a factor of 5, 3, 2 and 1. It is fixed and * not DecimalEncodedValue.getMaxInt to allow special case handling. */ - public static final double UNLIMITED_SIGN_SPEED = 150; + public static final double MAXSPEED_150 = 150; /** * The speed value used for road sections without known speed limit. */ - public static final double UNSET_SPEED = Double.POSITIVE_INFINITY; + public static final double MAXSPEED_MISSING = Double.POSITIVE_INFINITY; public static DecimalEncodedValue create() { // if we would store only km/h we could live with a factor of 5 and only 5 bits diff --git a/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java b/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java index 5f0cca0a205..cf5794662f6 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java +++ b/core/src/main/java/com/graphhopper/routing/ev/MaxWeight.java @@ -28,11 +28,11 @@ public class MaxWeight { public static final String KEY = "max_weight"; /** - * Currently enables to store 0.1 to max=0.1*2⁸ tons and infinity. If a value is between the maximum and infinity + * Currently enables to store 0.1 to max=0.1*2⁹ tons and infinity. If a value is between the maximum and infinity * it is assumed to use the maximum value. To save bits it might make more sense to store only a few values like * it was done with the MappedDecimalEncodedValue still handling (or rounding) of unknown values is unclear. */ public static DecimalEncodedValue create() { - return new DecimalEncodedValueImpl(KEY, 8, 0, 0.1, false, false, true); + return new DecimalEncodedValueImpl(KEY, 9, 0, 0.1, false, false, true); } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/MaxWeightExcept.java b/core/src/main/java/com/graphhopper/routing/ev/MaxWeightExcept.java index d25cc79e476..bcc2cf0d72a 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/MaxWeightExcept.java +++ b/core/src/main/java/com/graphhopper/routing/ev/MaxWeightExcept.java @@ -8,7 +8,7 @@ */ public enum MaxWeightExcept { - NONE, DELIVERY, DESTINATION, FORESTRY; + MISSING, DELIVERY, DESTINATION, FORESTRY; public static final String KEY = "max_weight_except"; @@ -23,12 +23,16 @@ public String toString() { public static MaxWeightExcept find(String name) { if (name == null || name.isEmpty()) - return NONE; + return MISSING; + + // "maxweight:conditional=none @ private" is rare and seems to be known from a few mappers only + if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("private")) + return DELIVERY; try { return MaxWeightExcept.valueOf(Helper.toUpperCase(name)); } catch (IllegalArgumentException ex) { - return NONE; + return MISSING; } } } diff --git a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalTagInspector.java b/core/src/main/java/com/graphhopper/routing/ev/Orientation.java similarity index 65% rename from core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalTagInspector.java rename to core/src/main/java/com/graphhopper/routing/ev/Orientation.java index 7b5e54eaeb8..85863dd7df8 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/conditional/ConditionalTagInspector.java +++ b/core/src/main/java/com/graphhopper/routing/ev/Orientation.java @@ -15,15 +15,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.graphhopper.reader.osm.conditional; +package com.graphhopper.routing.ev; -import com.graphhopper.reader.ReaderWay; +public class Orientation { + public static final String KEY = "orientation"; -/** - * @author Peter Karich - */ -public interface ConditionalTagInspector { - boolean isRestrictedWayConditionallyPermitted(ReaderWay way); - - boolean isPermittedWayConditionallyRestricted(ReaderWay way); + // Due to pillar nodes we need 2 values: the orientation at the adjacent node and the reverse + // value for orientation at the base node. Store in degrees. + public static DecimalEncodedValue create() { + return new DecimalEncodedValueImpl(KEY, 5, 0, 360 / 30.0, false, true, false); + } } diff --git a/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java b/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java index 768beb6cdbd..4220fe94823 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java +++ b/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java @@ -20,12 +20,12 @@ import com.graphhopper.util.Helper; /** - * This enum defines the road access of an edge. Most edges are accessible from everyone and so the default value is - * YES. But some have restrictions like "accessible only for customers" or when delivering. Unknown tags will get the - * value OTHER. The NO value does not permit any access. + * This enum defines the road access of an edge. Most edges are accessible from everyone and so the + * default value is YES. But some have restrictions like "accessible only for customers" or when + * delivering. The NO value does not permit any access. */ public enum RoadAccess { - YES, DESTINATION, CUSTOMERS, DELIVERY, FORESTRY, AGRICULTURAL, PRIVATE, OTHER, NO; + YES, DESTINATION, CUSTOMERS, DELIVERY, PRIVATE, MILITARY, AGRICULTURAL, FORESTRY, NO; public static final String KEY = "road_access"; @@ -41,6 +41,8 @@ public String toString() { public static RoadAccess find(String name) { if (name == null) return YES; + if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("service")) + return PRIVATE; try { // public and permissive will be converted into "yes" return RoadAccess.valueOf(Helper.toUpperCase(name)); diff --git a/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java b/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java index 806b6dbccf0..b6e8f0e9a95 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java +++ b/core/src/main/java/com/graphhopper/routing/ev/RoadClass.java @@ -25,7 +25,8 @@ */ public enum RoadClass { OTHER, MOTORWAY, TRUNK, PRIMARY, SECONDARY, TERTIARY, RESIDENTIAL, UNCLASSIFIED, - SERVICE, ROAD, TRACK, BRIDLEWAY, STEPS, CYCLEWAY, PATH, LIVING_STREET, FOOTWAY, PEDESTRIAN, PLATFORM, CORRIDOR; + SERVICE, ROAD, TRACK, BRIDLEWAY, STEPS, CYCLEWAY, PATH, LIVING_STREET, FOOTWAY, + PEDESTRIAN, PLATFORM, CORRIDOR, CONSTRUCTION, BUSWAY; public static final String KEY = "road_class"; diff --git a/core/src/main/java/com/graphhopper/routing/ev/Sidewalk.java b/core/src/main/java/com/graphhopper/routing/ev/Sidewalk.java new file mode 100644 index 00000000000..a039ca4bfa1 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/Sidewalk.java @@ -0,0 +1,57 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.ev; + +import com.graphhopper.util.Helper; + +/** + * This enum defines the sidewalk encoded value, parsed from sidewalk, sidewalk:left, sidewalk:right and + * sidewalk:both OSM tags. If not tagged or unknown the value will be MISSING. + *

+ * This encoded value stores two directions. The main sidewalk tag encodes direction as its value + * (left, right, both), while the directional keys (sidewalk:left, sidewalk:right) encode presence + * as their value (yes, no, separate). + * + * @see Key:sidewalk + */ +public enum Sidewalk { + MISSING, YES, SEPARATE, NO; + + public static final String KEY = "sidewalk"; + + public static EnumEncodedValue create() { + return new EnumEncodedValue<>(KEY, Sidewalk.class, true); + } + + @Override + public String toString() { + return Helper.toLowerCase(super.toString()); + } + + public static Sidewalk find(String name) { + if (Helper.isEmpty(name)) + return MISSING; + if ("none".equals(name)) + return NO; + try { + return Sidewalk.valueOf(Helper.toUpperCase(name)); + } catch (IllegalArgumentException ex) { + return MISSING; + } + } +} diff --git a/core/src/main/java/com/graphhopper/routing/ev/State.java b/core/src/main/java/com/graphhopper/routing/ev/State.java index ea91fa17d15..924b72db6c1 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/State.java +++ b/core/src/main/java/com/graphhopper/routing/ev/State.java @@ -1,6 +1,7 @@ package com.graphhopper.routing.ev; -import static com.graphhopper.routing.ev.Country.*; +import java.util.HashMap; +import java.util.Map; /** * The country subdivision is stored in this EncodedValue. E.g. US-CA is the enum US_CA. @@ -110,6 +111,15 @@ public enum State { public static final String KEY = "state", ISO_3166_2 = "ISO3166-2"; + private static final Map STATE_BY_CODE; + static { + var map = new HashMap(); + for (State state : State.values()) { + map.put(state.stateCode, state); + } + STATE_BY_CODE = Map.copyOf(map); // unmodifiable + } + private final String stateCode; /** @@ -123,11 +133,7 @@ public enum State { * @param iso should be ISO 3166-2 but with hyphen like US-CA */ public static State find(String iso) { - try { - return State.valueOf(iso.replace('-', '_')); - } catch (IllegalArgumentException ex) { - return State.MISSING; - } + return STATE_BY_CODE.getOrDefault(iso, State.MISSING); } /** diff --git a/core/src/main/java/com/graphhopper/routing/lm/LMApproximator.java b/core/src/main/java/com/graphhopper/routing/lm/LMApproximator.java index ef3d780d4cd..e1836a8fd52 100644 --- a/core/src/main/java/com/graphhopper/routing/lm/LMApproximator.java +++ b/core/src/main/java/com/graphhopper/routing/lm/LMApproximator.java @@ -185,7 +185,7 @@ public void setTo(int t) { } private void findClosestRealNode(int t) { - Dijkstra dijkstra = new Dijkstra(graph, lmWeighting, TraversalMode.NODE_BASED) { + Dijkstra dijkstra = new Dijkstra(graph, graph.wrapWeighting(lmWeighting), TraversalMode.NODE_BASED) { @Override protected boolean finished() { towerNodeNextToT = currEdge.adjNode; diff --git a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java index 20ae626741e..64e57d255c8 100644 --- a/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java +++ b/core/src/main/java/com/graphhopper/routing/lm/LMPreparationHandler.java @@ -40,7 +40,6 @@ import java.io.Reader; import java.net.URL; import java.util.*; -import java.util.concurrent.Callable; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -173,6 +172,10 @@ public List load(List lmConfigs, BaseGraph baseGraph, * Prepares the landmark data for all given configs */ public List prepare(List lmConfigs, BaseGraph baseGraph, EncodingManager encodingManager, StorableProperties properties, LocationIndex locationIndex, final boolean closeEarly) { + if (lmConfigs.isEmpty()) { + LOGGER.info("There are no LMs to prepare"); + return Collections.emptyList(); + } List preparations = createPreparations(lmConfigs, baseGraph, encodingManager, locationIndex); List prepareRunnables = new ArrayList<>(); for (int i = 0; i < preparations.size(); i++) { diff --git a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java index 6927a8b394f..b206fafb78f 100644 --- a/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java +++ b/core/src/main/java/com/graphhopper/routing/lm/LandmarkStorage.java @@ -322,7 +322,7 @@ public void createLandmarks() { int subnetworkCount = landmarkIDs.size(); // store all landmark node IDs and one int for the factor itself. - this.landmarkWeightDA.ensureCapacity(maxBytes /* landmark weights */ + (long) subnetworkCount * landmarks /* landmark mapping per subnetwork */); + this.landmarkWeightDA.ensureCapacity(maxBytes /* landmark weights */ + (long) subnetworkCount * landmarks /* landmark mapping per subnetwork */ + 4); // calculate offset to point into landmark mapping long bytePos = maxBytes; diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/EdgeChangeBuilder.java b/core/src/main/java/com/graphhopper/routing/querygraph/EdgeChangeBuilder.java index d534afd386b..effe90bcc7d 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/EdgeChangeBuilder.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/EdgeChangeBuilder.java @@ -66,7 +66,7 @@ private void build() { // these adjacent real nodes so we can use them in the next step for (int i = 0; i < getNumVirtualNodes(); i++) { // base node - EdgeIteratorState baseRevEdge = getVirtualEdge(i * 4 + SNAP_BASE); + VirtualEdgeIteratorState baseRevEdge = getVirtualEdge(i * 4 + SNAP_BASE); int towerNode = baseRevEdge.getAdjNode(); if (!isVirtualNode(towerNode)) { towerNodesToChange.add(towerNode); @@ -74,7 +74,7 @@ private void build() { } // adj node - EdgeIteratorState adjEdge = getVirtualEdge(i * 4 + SNAP_ADJ); + VirtualEdgeIteratorState adjEdge = getVirtualEdge(i * 4 + SNAP_ADJ); towerNode = adjEdge.getAdjNode(); if (!isVirtualNode(towerNode)) { towerNodesToChange.add(towerNode); @@ -100,7 +100,7 @@ private void addVirtualEdges(boolean base, int node, int virtNode) { edgeChanges = new QueryOverlay.EdgeChanges(2, 2); edgeChangesAtRealNodes.put(node, edgeChanges); } - EdgeIteratorState edge = base + VirtualEdgeIteratorState edge = base ? getVirtualEdge(virtNode * 4 + BASE_SNAP) : getVirtualEdge(virtNode * 4 + ADJ_SNAP); edgeChanges.getAdditionalEdges().add(edge); @@ -116,9 +116,9 @@ private void addRemovedEdges(int towerNode) { throw new IllegalStateException("Node should not be virtual:" + towerNode + ", " + edgeChangesAtRealNodes); QueryOverlay.EdgeChanges edgeChanges = edgeChangesAtRealNodes.get(towerNode); - List existingEdges = edgeChanges.getAdditionalEdges(); + List existingEdges = edgeChanges.getAdditionalEdges(); IntArrayList removedEdges = edgeChanges.getRemovedEdges(); - for (EdgeIteratorState existingEdge : existingEdges) { + for (VirtualEdgeIteratorState existingEdge : existingEdges) { removedEdges.add(getClosestEdge(existingEdge.getAdjNode())); } } diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java index dc768586d18..d2cb4af7f86 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryGraph.java @@ -108,31 +108,27 @@ public boolean isVirtualNode(int nodeId) { return nodeId >= baseNodes; } + /** + * Assigns the 'unfavored' flag to the given virtual edges (for both directions) + */ public void unfavorVirtualEdges(IntArrayList edgeIds) { for (IntCursor c : edgeIds) { - unfavorVirtualEdge(c.value); + int virtualEdgeId = c.value; + if (!isVirtualEdge(virtualEdgeId)) + return; + VirtualEdgeIteratorState edge = getVirtualEdge(getInternalVirtualEdgeId(virtualEdgeId)); + edge.setUnfavored(true); + unfavoredEdges.add(edge); + // we have to set the unfavored flag also for the virtual edge state that is used when we discover the same edge + // from the adjacent node. note that the unfavored flag will be set for both 'directions' of the same edge state. + VirtualEdgeIteratorState reverseEdge = getVirtualEdge(getPosOfReverseEdge(getInternalVirtualEdgeId(virtualEdgeId))); + reverseEdge.setUnfavored(true); + unfavoredEdges.add(reverseEdge); } } /** - * Assigns the 'unfavored' flag to a virtual edge (for both directions) - */ - public void unfavorVirtualEdge(int virtualEdgeId) { - if (!isVirtualEdge(virtualEdgeId)) - return; - VirtualEdgeIteratorState edge = getVirtualEdge(getInternalVirtualEdgeId(virtualEdgeId)); - edge.setUnfavored(true); - unfavoredEdges.add(edge); - // we have to set the unfavored flag also for the virtual edge state that is used when we discover the same edge - // from the adjacent node. note that the unfavored flag will be set for both 'directions' of the same edge state. - VirtualEdgeIteratorState reverseEdge = getVirtualEdge(getPosOfReverseEdge(getInternalVirtualEdgeId(virtualEdgeId))); - reverseEdge.setUnfavored(true); - unfavoredEdges.add(reverseEdge); - } - - /** - * Returns all virtual edges that have been unfavored via - * {@link #unfavorVirtualEdge(int)} or {@link #unfavorVirtualEdges(IntArrayList)} + * Returns all virtual edges that have been unfavored via {@link #unfavorVirtualEdges(IntArrayList)} */ public Set getUnfavoredVirtualEdges() { // Need to create a new set to convert Set to @@ -280,7 +276,10 @@ public TurnCostStorage getTurnCostStorage() { @Override public Weighting wrapWeighting(Weighting weighting) { - return new QueryGraphWeighting(weighting, baseGraph.getNodes(), baseGraph.getEdges(), queryOverlay.getClosestEdges()); + if (weighting instanceof QueryGraphWeighting) + return weighting; + QueryOverlay.WeightsAndTimes result = QueryOverlay.calcAdjustedVirtualWeightsAndTimes(queryOverlay, baseGraph, weighting); + return new QueryGraphWeighting(baseGraph, weighting, queryOverlay.getClosestEdges(), result.weights(), result.times()); } @Override diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlay.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlay.java index 3b5193f48d2..d818c348ca1 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlay.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlay.java @@ -18,9 +18,12 @@ package com.graphhopper.routing.querygraph; -import com.carrotsearch.hppc.IntArrayList; -import com.carrotsearch.hppc.IntObjectMap; +import com.carrotsearch.hppc.*; +import com.carrotsearch.hppc.cursors.DoubleCursor; +import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.graphhopper.coll.GHIntObjectHashMap; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.PointList; @@ -76,8 +79,116 @@ IntArrayList getClosestEdges() { return closestEdges; } + record WeightsAndTimes(IntDoubleMap weights, IntLongMap times) { + } + + public static WeightsAndTimes calcAdjustedVirtualWeightsAndTimes(QueryOverlay queryOverlay, BaseGraph baseGraph, Weighting weighting) { + return calcAdjustedVirtualWeightsAndTimes(queryOverlay.getVirtualEdges(), baseGraph, weighting); + } + + static WeightsAndTimes calcAdjustedVirtualWeightsAndTimes(List virtualEdges, BaseGraph baseGraph, Weighting weighting) { + IntDoubleScatterMap weights = new IntDoubleScatterMap(virtualEdges.size()); + IntLongScatterMap times = new IntLongScatterMap(virtualEdges.size()); + + IntObjectMap> virtualEdgesByOriginalKey = new IntObjectScatterMap<>(); + IntSet edgesSet = new IntScatterSet(); + for (VirtualEdgeIteratorState v : virtualEdges) { + List edges = virtualEdgesByOriginalKey.get(v.getOriginalEdgeKey()); + if (edges == null) { + edges = new ArrayList<>(); + virtualEdgesByOriginalKey.put(v.getOriginalEdgeKey(), edges); + } + // remove duplicates + if (edges.isEmpty() || edgesSet.add(v.getEdgeKey())) + edges.add(v); + } + + for (IntObjectCursor> c : virtualEdgesByOriginalKey) { + DoubleArrayList virtualWeights = new DoubleArrayList(c.value.size()); + LongArrayList virtualTimes = new LongArrayList(c.value.size()); + boolean hasInfiniteVirtualEdge = false; + for (VirtualEdgeIteratorState v : c.value) { + double w = weighting.calcEdgeWeight(v, false); + if (Double.isInfinite(w)) + hasInfiniteVirtualEdge = true; + else if (w < 0 || w % 1 != 0) + throw new IllegalArgumentException("weight must be non-negative whole number, got: " + w); + virtualWeights.add(w); + + long t = weighting.calcEdgeMillis(v, false); + virtualTimes.add(t); + } + EdgeIteratorState originalEdge = baseGraph.getEdgeIteratorStateForKey(c.key); + double originalWeight = weighting.calcEdgeWeight(originalEdge, false); + long originalTime = weighting.calcEdgeMillis(originalEdge, false); + + if (Double.isInfinite(originalWeight) || hasInfiniteVirtualEdge) { + // we don't adjust anything + for (int i = 0; i < c.value.size(); i++) { + weights.put(c.value.get(i).getEdgeKey(), virtualWeights.get(i)); + times.put(c.value.get(i).getEdgeKey(), virtualTimes.get(i)); + } + continue; + } else if (originalWeight < 0 || originalWeight % 1 != 0) + throw new IllegalArgumentException("weight must be non-negative whole number, got: " + originalWeight); + + // casting to long is safe since we checked weights are whole numbers + LongArrayList virtualWeightsLong = new LongArrayList(virtualWeights.size()); + for (DoubleCursor vw : virtualWeights) virtualWeightsLong.add((long) vw.value); + + // We do not adjust the weights if the difference is more than rounding errors. + // For example, when we snap onto an edge only partially covered by an avoided area, + // only one of the virtual edges might intersect the area. In this case we do not want to + // penalize the virtual edges that are outside the area. This means that the sum of the + // virtual edges' weights does not equal the weight of the original edge. + adjustValues(virtualWeightsLong, (long) originalWeight, 1); + adjustValues(virtualTimes, originalTime, 20); + for (int i = 0; i < c.value.size(); i++) { + weights.put(c.value.get(i).getEdgeKey(), virtualWeightsLong.get(i)); + times.put(c.value.get(i).getEdgeKey(), virtualTimes.get(i)); + } + } + return new WeightsAndTimes(weights, times); + } + + /** + * Adjusts values so they sum to target, changing each by at most maxPerElement. + * The first element is kept >= 1 to avoid zero-weight virtual edges at tower nodes. + * Zero-weight virtual edges at tower node introduce unique path ambiguity. + * If the target is unreachable within these constraints, values are left untouched. + */ + static void adjustValues(LongArrayList values, long target, long maxPerElement) { + if (values.isEmpty()) return; + if (target < 0) throw new IllegalArgumentException("target cannot be negative: " + target); + if (maxPerElement < 0) + throw new IllegalArgumentException("maxPerElement cannot be negative: " + maxPerElement); + // If the target is zero we do nothing, because we would have to set all values zero, but we want to keep the zeroth >= 1 -> not our problem + if (target == 0) return; + long minTarget = 0, maxTarget = 0, diff = target; + for (int i = 0; i < values.size(); i++) { + diff -= values.get(i); + long floor = (i == 0) ? 1 : 0; + minTarget += Math.max(floor, values.get(i) - maxPerElement); + maxTarget += values.get(i) + maxPerElement; + } + if (diff == 0) return; + // Check if the target is reachable given maxPerElement, no element must be negative, and the first must be at least one. + // If not, we leave the array untouched since we only want to account for small numerical errors. + if (target < minTarget || target > maxTarget) return; + int sign = diff > 0 ? 1 : -1; + for (int i = 0; i < values.size(); i++) { + long adjustment = sign * Math.min(Math.abs(diff), maxPerElement); + // The first element must stay > 0: a zero-weight first virtual edge (leaving the + // tower node) introduces unique path ambiguity. + long floor = (i == 0) ? 1 : 0; + if (values.get(i) + adjustment < floor) adjustment = floor - values.get(i); + values.set(i, values.get(i) + adjustment); + diff -= adjustment; + } + } + static class EdgeChanges { - private final List additionalEdges; + private final List additionalEdges; private final IntArrayList removedEdges; EdgeChanges(int expectedNumAdditionalEdges, int expectedNumRemovedEdges) { @@ -85,7 +196,7 @@ static class EdgeChanges { removedEdges = new IntArrayList(expectedNumRemovedEdges); } - List getAdditionalEdges() { + List getAdditionalEdges() { return additionalEdges; } diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java index 79d9a53cb61..e08c758b3da 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryOverlayBuilder.java @@ -18,6 +18,7 @@ package com.graphhopper.routing.querygraph; +import com.carrotsearch.hppc.LongArrayList; import com.carrotsearch.hppc.predicates.IntObjectPredicate; import com.graphhopper.coll.GHIntObjectHashMap; import com.graphhopper.search.KVStorage; @@ -28,10 +29,7 @@ import com.graphhopper.util.shapes.GHPoint; import com.graphhopper.util.shapes.GHPoint3D; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; +import java.util.*; import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; @@ -41,6 +39,9 @@ class QueryOverlayBuilder { private final boolean is3D; private QueryOverlay queryOverlay; + private final List virtualEdgesFwdForSnap = new ArrayList<>(); + private final List virtualEdgesBwdForSnap = new ArrayList<>(); + public static QueryOverlay build(Graph graph, List snaps) { return build(graph.getNodes(), graph.getEdges(), graph.getNodeAccess().is3D(), snaps); } @@ -124,11 +125,13 @@ private void buildVirtualEdges(List snaps) { edge2res.forEach(new IntObjectPredicate>() { @Override public boolean apply(int edgeId, List results) { + virtualEdgesFwdForSnap.clear(); + virtualEdgesBwdForSnap.clear(); // we can expect at least one entry in the results EdgeIteratorState closestEdge = results.get(0).getClosestEdge(); final PointList fullPL = closestEdge.fetchWayGeometry(FetchMode.ALL); - int baseNode = closestEdge.getBaseNode(); - Collections.sort(results, new Comparator() { + final int baseNode = closestEdge.getBaseNode(); + results.sort(new Comparator<>() { @Override public int compare(Snap o1, Snap o2) { int diff = Integer.compare(o1.getWayIndex(), o2.getWayIndex()); @@ -148,9 +151,9 @@ private double distanceOfSnappedPointToPillarNode(Snap o) { }); GHPoint3D prevPoint = fullPL.get(0); - int adjNode = closestEdge.getAdjNode(); - int origEdgeKey = closestEdge.getEdgeKey(); - int origRevEdgeKey = closestEdge.getReverseEdgeKey(); + final int adjNode = closestEdge.getAdjNode(); + final int origEdgeKey = closestEdge.getEdgeKey(); + final int origRevEdgeKey = closestEdge.getReverseEdgeKey(); int prevWayIndex = 1; int prevNodeId = baseNode; int virtNodeId = queryOverlay.getVirtualNodes().size() + firstVirtualNodeId; @@ -206,11 +209,32 @@ private double distanceOfSnappedPointToPillarNode(Snap o) { fullPL.get(fullPL.size() - 1), fullPL.size() - 2, fullPL, closestEdge, virtNodeId - 1, adjNode); + adjustDistances(virtualEdgesFwdForSnap, closestEdge.getDistance_mm()); + adjustDistances(virtualEdgesBwdForSnap, closestEdge.getDistance_mm()); + return true; } }); } + private void adjustDistances(List virtualEdges, long originalDistance) { + // the sum of virtual edge distances can differ from the distance of the original edge: + // - we use dist_plane instead of dist_earth in OSMReader (not entirely sure why) + // - virtual edge distances include numeric errors (regardless of the dist calc) + if (virtualEdges.isEmpty()) + // early exit & prevent division by zero below + return; + LongArrayList virtualDistances = new LongArrayList(virtualEdges.size()); + for (VirtualEdgeIteratorState v : virtualEdges) + virtualDistances.add(v.getDistance_mm()); + + // we allow adjustments up to 1mm. this should be enough to account for dist_plane vs. dist_earth for most segments including the ones we create in random graph tests + final long maxPerElement = 1; + QueryOverlay.adjustValues(virtualDistances, originalDistance, maxPerElement); + for (int i = 0; i < virtualEdges.size(); i++) + virtualEdges.get(i).setDistance_mm(virtualDistances.get(i)); + } + private void createEdges(int origEdgeKey, int origRevEdgeKey, GHPoint3D prevSnapped, int prevWayIndex, boolean isPillar, GHPoint3D currSnapped, int wayIndex, PointList fullPL, EdgeIteratorState closestEdge, @@ -233,7 +257,7 @@ private void createEdges(int origEdgeKey, int origRevEdgeKey, boolean reverse = closestEdge.get(EdgeIteratorState.REVERSE_STATE); // edges between base and snapped point - List keyValues = closestEdge.getKeyValues(); + Map keyValues = closestEdge.getKeyValues(); VirtualEdgeIteratorState baseEdge = new VirtualEdgeIteratorState(origEdgeKey, GHUtility.createEdgeKey(virtEdgeId, false), prevNodeId, nodeId, baseDistance, closestEdge.getFlags(), keyValues, basePoints, reverse); VirtualEdgeIteratorState baseReverseEdge = new VirtualEdgeIteratorState(origRevEdgeKey, GHUtility.createEdgeKey(virtEdgeId, true), @@ -243,6 +267,9 @@ private void createEdges(int origEdgeKey, int origRevEdgeKey, baseReverseEdge.setReverseEdge(baseEdge); queryOverlay.addVirtualEdge(baseEdge); queryOverlay.addVirtualEdge(baseReverseEdge); + // we collect the unique virtual edges separately here, so it is easier to adjust their distances afterwards + virtualEdgesFwdForSnap.add(baseEdge); + virtualEdgesBwdForSnap.add(baseReverseEdge); } private void buildEdgeChangesAtRealNodes() { diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/QueryRoutingCHGraph.java b/core/src/main/java/com/graphhopper/routing/querygraph/QueryRoutingCHGraph.java index 2bd81c14075..319411a50a1 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/QueryRoutingCHGraph.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/QueryRoutingCHGraph.java @@ -184,7 +184,7 @@ private IntObjectMap> buildVirtualEdgesAtRealNo @Override public void apply(int node, QueryOverlay.EdgeChanges edgeChanges) { List virtualEdges = new ArrayList<>(); - for (EdgeIteratorState v : edgeChanges.getAdditionalEdges()) { + for (VirtualEdgeIteratorState v : edgeChanges.getAdditionalEdges()) { assert v.getBaseNode() == node; int edge = v.getEdge(); if (queryGraph.isVirtualEdge(edge)) { @@ -215,7 +215,7 @@ private List> buildVirtualEdgesAtVirtualNodes() final int virtualNodes = queryOverlay.getVirtualNodes().size(); final List> virtualEdgesAtVirtualNodes = new ArrayList<>(virtualNodes); for (int i = 0; i < virtualNodes; i++) { - List virtualEdges = Arrays.asList( + List virtualEdges = List.of( buildVirtualCHEdgeState(queryOverlay.getVirtualEdges().get(i * 4 + SNAP_BASE)), buildVirtualCHEdgeState(queryOverlay.getVirtualEdges().get(i * 4 + SNAP_ADJ)) ); @@ -229,9 +229,9 @@ private VirtualCHEdgeIteratorState buildVirtualCHEdgeState(VirtualEdgeIteratorSt return buildVirtualCHEdgeState(virtualEdgeState, virtualCHEdge); } - private VirtualCHEdgeIteratorState buildVirtualCHEdgeState(EdgeIteratorState edgeState, int edgeID) { - double fwdWeight = weighting.calcEdgeWeight(edgeState, false); - double bwdWeight = weighting.calcEdgeWeight(edgeState, true); + private VirtualCHEdgeIteratorState buildVirtualCHEdgeState(VirtualEdgeIteratorState edgeState, int edgeID) { + double fwdWeight = queryGraphWeighting.calcEdgeWeight(edgeState, false); + double bwdWeight = queryGraphWeighting.calcEdgeWeight(edgeState, true); return new VirtualCHEdgeIteratorState(edgeID, edgeState.getEdge(), edgeState.getBaseNode(), edgeState.getAdjNode(), edgeState.getEdgeKey(), edgeState.getEdgeKey(), NO_EDGE, NO_EDGE, fwdWeight, bwdWeight); } @@ -329,7 +329,7 @@ public double getWeight(boolean reverse) { @Override public String toString() { - return "virtual: " + edge + ": " + baseNode + "->" + adjNode + ", orig: " + origEdge + ", weightFwd: " + Helper.round2(weightFwd) + ", weightBwd: " + Helper.round2(weightBwd); + return "virtual: " + edge + ": " + baseNode + "->" + adjNode + ", orig: " + origEdge + ", weightFwd: " + weightFwd + ", weightBwd: " + weightBwd; } } diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java index 9541130fdeb..347f9a1272c 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIterator.java @@ -27,11 +27,12 @@ import com.graphhopper.util.PointList; import java.util.List; +import java.util.Map; /** * @author Peter Karich */ -class VirtualEdgeIterator implements EdgeIterator { +public class VirtualEdgeIterator implements EdgeIterator { private final EdgeFilter edgeFilter; private List edges; private int current; @@ -109,6 +110,16 @@ public EdgeIteratorState setDistance(double dist) { return getCurrentEdge().setDistance(dist); } + @Override + public long getDistance_mm() { + return getCurrentEdge().getDistance_mm(); + } + + @Override + public EdgeIteratorState setDistance_mm(long distance_mm) { + return getCurrentEdge().setDistance_mm(distance_mm); + } + @Override public IntsRef getFlags() { return getCurrentEdge().getFlags(); @@ -262,12 +273,12 @@ public String getName() { } @Override - public List getKeyValues() { + public Map getKeyValues() { return getCurrentEdge().getKeyValues(); } @Override - public EdgeIteratorState setKeyValues(List list) { + public EdgeIteratorState setKeyValues(Map list) { return getCurrentEdge().setKeyValues(list); } @@ -276,6 +287,11 @@ public Object getValue(String key) { return getCurrentEdge().getValue(key); } + @Override + public boolean isVirtual() { + return getCurrentEdge().isVirtual(); + } + @Override public String toString() { if (current >= 0 && current < edges.size()) { diff --git a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java index bcc8b2cc0f6..b068c06483d 100644 --- a/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java +++ b/core/src/main/java/com/graphhopper/routing/querygraph/VirtualEdgeIteratorState.java @@ -25,7 +25,10 @@ import com.graphhopper.util.GHUtility; import com.graphhopper.util.PointList; -import java.util.List; +import java.util.Map; + +import static com.graphhopper.storage.BaseGraph.MAX_DIST_METERS; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; /** * Creates an edge state decoupled from a graph where nodes, pointList, etc are kept in memory. @@ -39,22 +42,22 @@ public class VirtualEdgeIteratorState implements EdgeIteratorState { private final int baseNode; private final int adjNode; private final int originalEdgeKey; - private double distance; + private long distance_mm; private IntsRef edgeFlags; private EdgeIntAccess edgeIntAccess; - private List keyValues; + private Map keyValues; // true if edge should be avoided as start/stop private boolean unfavored; private EdgeIteratorState reverseEdge; private final boolean reverse; public VirtualEdgeIteratorState(int originalEdgeKey, int edgeKey, int baseNode, int adjNode, double distance, - IntsRef edgeFlags, List keyValues, PointList pointList, boolean reverse) { + IntsRef edgeFlags, Map keyValues, PointList pointList, boolean reverse) { this.originalEdgeKey = originalEdgeKey; this.edgeKey = edgeKey; this.baseNode = baseNode; this.adjNode = adjNode; - this.distance = distance; + setDistance(distance); this.edgeFlags = edgeFlags; this.edgeIntAccess = new IntsRefEdgeIntAccess(edgeFlags); this.keyValues = keyValues; @@ -130,12 +133,32 @@ public EdgeIteratorState setWayGeometry(PointList list) { @Override public double getDistance() { - return distance; + return distance_mm / 1000.0; } @Override - public EdgeIteratorState setDistance(double dist) { - this.distance = dist; + public EdgeIteratorState setDistance(double distance) { + if (distance < 0) + throw new IllegalArgumentException("distances must be non-negative, got: " + distance); + if (distance > MAX_DIST_METERS) + distance = MAX_DIST_METERS; + long distance_mm = Math.round(distance * 1000); + setDistance_mm(distance_mm); + return this; + } + + @Override + public long getDistance_mm() { + return distance_mm; + } + + @Override + public EdgeIteratorState setDistance_mm(long distance_mm) { + if (distance_mm < 0) + throw new IllegalArgumentException("distances must be non-negative, got: " + distance_mm); + if (distance_mm > Integer.MAX_VALUE) + distance_mm = Integer.MAX_VALUE; + this.distance_mm = distance_mm; return this; } @@ -313,26 +336,28 @@ public EdgeIteratorState set(StringEncodedValue property, String fwd, String bwd @Override public String getName() { - String name = (String) getValue(KVStorage.KeyValue.STREET_NAME); + String name = (String) getValue(STREET_NAME); // preserve backward compatibility (returns empty string if name tag missing) return name == null ? "" : name; } @Override - public EdgeIteratorState setKeyValues(List list) { + public EdgeIteratorState setKeyValues(Map list) { this.keyValues = list; return this; } @Override - public List getKeyValues() { + public Map getKeyValues() { return keyValues; } @Override public Object getValue(String key) { - for (KVStorage.KeyValue keyValue : keyValues) { - if (keyValue.key.equals(key)) return keyValue.value; + KVStorage.KValue value = keyValues.get(key); + if (value != null) { + if (!reverse && value.getFwd() != null) return value.getFwd(); + if (reverse && value.getBwd() != null) return value.getBwd(); } return null; } @@ -356,7 +381,7 @@ public EdgeIteratorState detach(boolean reverse) { // TODO copy pointList (geometry) too reverseEdge.setFlags(getFlags()); reverseEdge.setKeyValues(getKeyValues()); - reverseEdge.setDistance(getDistance()); + reverseEdge.setDistance_mm(getDistance_mm()); return reverseEdge; } else { return this; @@ -368,6 +393,11 @@ public EdgeIteratorState copyPropertiesFrom(EdgeIteratorState fromEdge) { throw new RuntimeException("Not supported."); } + @Override + public boolean isVirtual() { + return true; + } + public void setReverseEdge(EdgeIteratorState reverseEdge) { this.reverseEdge = reverseEdge; } diff --git a/core/src/main/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCC.java b/core/src/main/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCC.java index bc5d22b55b0..cc6ee929fcb 100644 --- a/core/src/main/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCC.java +++ b/core/src/main/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCC.java @@ -219,8 +219,6 @@ private ConnectedComponents findComponentsForStartEdges(IntContainer startEdges) for (IntCursor edge : startEdges) { // todo: using getEdgeIteratorState here is not efficient EdgeIteratorState edgeState = graph.getEdgeIteratorState(edge.value, Integer.MIN_VALUE); - if (!edgeTransitionFilter.accept(NO_EDGE, edgeState)) - continue; findComponentsForEdgeState(edgeState); } return components; diff --git a/core/src/main/java/com/graphhopper/routing/util/AreaIndex.java b/core/src/main/java/com/graphhopper/routing/util/AreaIndex.java index 5fb5f6bcb85..4e42c2dae4b 100644 --- a/core/src/main/java/com/graphhopper/routing/util/AreaIndex.java +++ b/core/src/main/java/com/graphhopper/routing/util/AreaIndex.java @@ -21,7 +21,6 @@ import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedGeometryFactory; -import org.locationtech.jts.geom.prep.PreparedPolygon; import org.locationtech.jts.index.strtree.STRtree; import java.util.List; diff --git a/core/src/main/java/com/graphhopper/routing/util/CurvatureCalculator.java b/core/src/main/java/com/graphhopper/routing/util/CurvatureCalculator.java index fdc864641d8..4dc4e9311ef 100644 --- a/core/src/main/java/com/graphhopper/routing/util/CurvatureCalculator.java +++ b/core/src/main/java/com/graphhopper/routing/util/CurvatureCalculator.java @@ -1,14 +1,12 @@ package com.graphhopper.routing.util; -import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.util.parsers.TagParser; -import com.graphhopper.storage.IntsRef; +import com.graphhopper.storage.Graph; import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.FetchMode; import com.graphhopper.util.PointList; -public class CurvatureCalculator implements TagParser { +public class CurvatureCalculator { private final DecimalEncodedValue curvatureEnc; @@ -16,19 +14,23 @@ public CurvatureCalculator(DecimalEncodedValue curvatureEnc) { this.curvatureEnc = curvatureEnc; } - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - PointList pointList = way.getTag("point_list", null); - Double edgeDistance = way.getTag("edge_distance", null); - if (pointList != null && edgeDistance != null && !pointList.isEmpty()) { - double beeline = DistanceCalcEarth.DIST_EARTH.calcDist(pointList.getLat(0), pointList.getLon(0), - pointList.getLat(pointList.size() - 1), pointList.getLon(pointList.size() - 1)); - // For now keep the formula simple. Maybe later use quadratic value as it might improve the "resolution" - double curvature = beeline / edgeDistance; - curvatureEnc.setDecimal(false, edgeId, edgeIntAccess, Math.max(curvatureEnc.getMinStorableDecimal(), Math.min(curvatureEnc.getMaxStorableDecimal(), - curvature))); - } else { - curvatureEnc.setDecimal(false, edgeId, edgeIntAccess, 1.0); + public void execute(Graph graph) { + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + PointList pointList = iter.fetchWayGeometry(FetchMode.ALL); + double edgeDistance = iter.getDistance(); + if (!pointList.isEmpty() && edgeDistance > 0.1) { + double lat0 = pointList.getLat(0), lon0 = pointList.getLon(0); + double latEnd = pointList.getLat(pointList.size() - 1), lonEnd = pointList.getLon(pointList.size() - 1); + double beeline = pointList.is3D() ? DistanceCalcEarth.DIST_EARTH.calcDist3D(lat0, lon0, pointList.getEle(0), latEnd, lonEnd, pointList.getEle(pointList.size() - 1)) + : DistanceCalcEarth.DIST_EARTH.calcDist(lat0, lon0, latEnd, lonEnd); + // For now keep the formula simple. Maybe later use quadratic value as it might improve the "resolution" + double curvature = beeline / edgeDistance; + iter.set(curvatureEnc, Math.max(curvatureEnc.getMinStorableDecimal(), Math.min(curvatureEnc.getMaxStorableDecimal(), + curvature))); + } else { + iter.set(curvatureEnc, 1.0); + } } } } diff --git a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java b/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java deleted file mode 100644 index f0773a321a9..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleEncodedValuesFactory.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util; - -import com.graphhopper.util.PMap; - -/** - * This class creates vehicle encoded values that are already included in the GraphHopper distribution. - * - * @author Peter Karich - */ -public class DefaultVehicleEncodedValuesFactory implements VehicleEncodedValuesFactory { - @Override - public VehicleEncodedValues createVehicleEncodedValues(String name, PMap configuration) { - if (name.equals(ROADS)) - return VehicleEncodedValues.roads(configuration); - - if (name.equals(CAR)) - return VehicleEncodedValues.car(configuration); - - if (name.equals("car4wd")) - throw new IllegalArgumentException("Instead of car4wd use custom_models/car4wd.json"); - - if (name.equals(BIKE)) - return VehicleEncodedValues.bike(configuration); - - if (name.equals("bike2")) - throw new IllegalArgumentException("Instead of bike2 use custom_models/bike.json, see #2668"); - - if (name.equals(RACINGBIKE)) - return VehicleEncodedValues.racingbike(configuration); - - if (name.equals(MOUNTAINBIKE)) - return VehicleEncodedValues.mountainbike(configuration); - - if (name.equals(FOOT)) - return VehicleEncodedValues.foot(configuration); - - if (name.equals("hike")) - throw new IllegalArgumentException("Instead of hike use custom_models/hike.json, see #2759"); - - if (name.equals("motorcycle")) - throw new IllegalArgumentException("Instead of motorcycle use custom_models/motorcycle.json, see #2781"); - - if (name.equals("wheelchair")) - throw new IllegalArgumentException("wheelchair is no longer supported, see #"); - - throw new IllegalArgumentException("entry in vehicle list not supported: " + name); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java deleted file mode 100644 index aba279bcd43..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/DefaultVehicleTagParserFactory.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util; - -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.util.PMap; - -import static com.graphhopper.routing.util.VehicleEncodedValuesFactory.*; - -public class DefaultVehicleTagParserFactory implements VehicleTagParserFactory { - public VehicleTagParsers createParsers(EncodedValueLookup lookup, String name, PMap configuration) { - if (name.equals(ROADS)) - return VehicleTagParsers.roads(lookup); - if (name.equals(CAR)) - return VehicleTagParsers.car(lookup, configuration); - if (name.equals(BIKE)) - return VehicleTagParsers.bike(lookup, configuration); - if (name.equals(RACINGBIKE)) - return VehicleTagParsers.racingbike(lookup, configuration); - if (name.equals(MOUNTAINBIKE)) - return VehicleTagParsers.mtb(lookup, configuration); - if (name.equals(FOOT)) - return VehicleTagParsers.foot(lookup, configuration); - - throw new IllegalArgumentException("Unknown name for vehicle tag parsers: " + name); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java index a810aeb06a3..61f0cfb021c 100644 --- a/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java +++ b/core/src/main/java/com/graphhopper/routing/util/EncodingManager.java @@ -24,10 +24,12 @@ import com.graphhopper.storage.IntsRef; import com.graphhopper.storage.StorableProperties; import com.graphhopper.util.Constants; -import com.graphhopper.util.PMap; import java.io.UncheckedIOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; import java.util.stream.Collectors; /** @@ -42,12 +44,12 @@ public class EncodingManager implements EncodedValueLookup { private final LinkedHashMap encodedValueMap; private final LinkedHashMap turnEncodedValueMap; - private int intsForFlags; + private int bytesForFlags; private int intsForTurnCostFlags; public static void putEncodingManagerIntoProperties(EncodingManager encodingManager, StorableProperties properties) { properties.put("graph.em.version", Constants.VERSION_EM); - properties.put("graph.em.ints_for_flags", encodingManager.intsForFlags); + properties.put("graph.em.bytes_for_flags", encodingManager.bytesForFlags); properties.put("graph.em.ints_for_turn_cost_flags", encodingManager.intsForTurnCostFlags); properties.put("graph.encoded_values", encodingManager.toEncodedValuesAsString()); properties.put("graph.turn_encoded_values", encodingManager.toTurnEncodedValuesAsString()); @@ -60,7 +62,8 @@ public static EncodingManager fromProperties(StorableProperties properties) { String versionStr = properties.get("graph.em.version"); if (versionStr.isEmpty() || !String.valueOf(Constants.VERSION_EM).equals(versionStr)) - throw new IllegalStateException("Incompatible encoding version. You need to use the same GraphHopper version you used to import the graph, or run a new import. " + throw new IllegalStateException("Incompatible encoding version. You need to use the same GraphHopper version you used to import the graph" + + " in '" + properties.getDirectory().getLocation() + "', delete the folder, or run a new import with another location. " + " Stored encoding version: " + (versionStr.isEmpty() ? "missing" : versionStr) + ", used encoding version: " + Constants.VERSION_EM); String encodedValueStr = properties.get("graph.encoded_values"); ArrayNode evList = deserializeEncodedValueList(encodedValueStr); @@ -80,9 +83,8 @@ public static EncodingManager fromProperties(StorableProperties properties) { throw new IllegalStateException("Duplicate turn encoded value name: " + encodedValue.getName() + " in: graph.turn_encoded_values=" + turnEncodedValueStr); }); - return new EncodingManager(encodedValues, turnEncodedValues, - getIntegerProperty(properties, "graph.em.ints_for_flags"), - getIntegerProperty(properties, "graph.em.ints_for_turn_cost_flags") + return new EncodingManager(getIntegerProperty(properties, "graph.em.bytes_for_flags"), getIntegerProperty(properties, "graph.em.ints_for_turn_cost_flags"), encodedValues, + turnEncodedValues ); } @@ -108,17 +110,17 @@ public static Builder start() { return new Builder(); } - public EncodingManager(LinkedHashMap encodedValueMap, - LinkedHashMap turnEncodedValueMap, - int intsForFlags, int intsForTurnCostFlags) { + public EncodingManager(int bytesForFlags, int intsForTurnCostFlags, + LinkedHashMap encodedValueMap, + LinkedHashMap turnEncodedValueMap) { this.encodedValueMap = encodedValueMap; this.turnEncodedValueMap = turnEncodedValueMap; - this.intsForFlags = intsForFlags; + this.bytesForFlags = bytesForFlags; this.intsForTurnCostFlags = intsForTurnCostFlags; } private EncodingManager() { - this(new LinkedHashMap<>(), new LinkedHashMap<>(), 0, 0); + this(0, 0, new LinkedHashMap<>(), new LinkedHashMap<>()); } public static class Builder { @@ -126,18 +128,6 @@ public static class Builder { private final EncodedValue.InitializerConfig turnCostConfig = new EncodedValue.InitializerConfig(); private EncodingManager em = new EncodingManager(); - public Builder add(VehicleEncodedValues v) { - checkNotBuiltAlready(); - List list = new ArrayList<>(); - v.createEncodedValues(list); - list.forEach(this::add); - - list = new ArrayList<>(); - v.createTurnCostEncodedValues(list); - list.forEach(this::addTurnCostEncodedValue); - return this; - } - public Builder add(EncodedValue encodedValue) { checkNotBuiltAlready(); if (em.hasEncodedValue(encodedValue.getName())) @@ -167,45 +157,16 @@ private void checkNotBuiltAlready() { public EncodingManager build() { checkNotBuiltAlready(); - addDefaultEncodedValues(); - if (em.encodedValueMap.isEmpty()) - throw new IllegalStateException("No EncodedValues were added to the EncodingManager"); - em.intsForFlags = edgeConfig.getRequiredInts(); + em.bytesForFlags = edgeConfig.getRequiredBytes(); em.intsForTurnCostFlags = turnCostConfig.getRequiredInts(); EncodingManager result = em; em = null; return result; } - - private void addDefaultEncodedValues() { - // todo: I think ultimately these should all be removed and must be added explicitly - List keys = new ArrayList<>(Arrays.asList( - Roundabout.KEY, - RoadClass.KEY, - RoadClassLink.KEY, - RoadEnvironment.KEY, - MaxSpeed.KEY, - RoadAccess.KEY, - FerrySpeed.KEY - )); - if (em.getVehicles().stream().anyMatch(vehicle -> vehicle.contains("bike") || vehicle.contains("mtb") || vehicle.contains("racingbike"))) { - keys.add(BikeNetwork.KEY); - keys.add(MtbNetwork.KEY); - keys.add(GetOffBike.KEY); - keys.add(Smoothness.KEY); - } - if (em.getVehicles().stream().anyMatch(vehicle -> vehicle.contains("foot") || vehicle.contains("hike"))) - keys.add(FootNetwork.KEY); - - DefaultEncodedValueFactory evFactory = new DefaultEncodedValueFactory(); - for (String key : keys) - if (!em.hasEncodedValue(key)) - add(evFactory.create(key, new PMap())); - } } - public int getIntsForFlags() { - return intsForFlags; + public int getBytesForFlags() { + return bytesForFlags; } public boolean hasEncodedValue(String key) { @@ -216,9 +177,10 @@ public boolean hasTurnEncodedValue(String key) { return turnEncodedValueMap.get(key) != null; } + /** + * @return list of all prefixes of xy_access and xy_average_speed encoded values. + */ public List getVehicles() { - // we define the 'vehicles' as all the prefixes for which there is an access and speed EV - // any EVs that contain prefix_average_speed are accepted return getEncodedValues().stream() .filter(ev -> ev.getName().endsWith("_access")) .map(ev -> ev.getName().replaceAll("_access", "")) @@ -242,7 +204,7 @@ public String toString() { // TODO hide IntsRef even more in a later version: https://gist.github.com/karussell/f4c2b2b1191be978d7ee9ec8dd2cd48f public IntsRef createEdgeFlags() { - return new IntsRef(getIntsForFlags()); + return new IntsRef((int) Math.ceil((double) getBytesForFlags() / 4)); } public IntsRef createRelationFlags() { @@ -290,7 +252,7 @@ public T getEncodedValue(String key, Class encodedVa EncodedValue ev = encodedValueMap.get(key); // todo: why do we not just return null when EV is missing? just like java.util.Map? -> https://github.com/graphhopper/graphhopper/pull/2561#discussion_r859770067 if (ev == null) - throw new IllegalArgumentException("Cannot find EncodedValue " + key + " in collection: " + encodedValueMap.keySet()); + throw new IllegalArgumentException("Cannot find EncodedValue '" + key + "' in collection: " + encodedValueMap.keySet()); return (T) ev; } @@ -310,7 +272,7 @@ public T getTurnEncodedValue(String key, Class encod EncodedValue ev = turnEncodedValueMap.get(key); // todo: why do we not just return null when EV is missing? just like java.util.Map? -> https://github.com/graphhopper/graphhopper/pull/2561#discussion_r859770067 if (ev == null) - throw new IllegalArgumentException("Cannot find Turn-EncodedValue " + key + " in collection: " + encodedValueMap.keySet()); + throw new IllegalArgumentException("Cannot find Turn-EncodedValue " + key + " in collection: " + turnEncodedValueMap.keySet()); return (T) ev; } diff --git a/core/src/main/java/com/graphhopper/routing/util/FerrySpeedCalculator.java b/core/src/main/java/com/graphhopper/routing/util/FerrySpeedCalculator.java index 24542990599..209b1576199 100644 --- a/core/src/main/java/com/graphhopper/routing/util/FerrySpeedCalculator.java +++ b/core/src/main/java/com/graphhopper/routing/util/FerrySpeedCalculator.java @@ -20,37 +20,37 @@ public static boolean isFerry(ReaderWay way) { } static double getSpeed(ReaderWay way) { - // todo: We currently face two problems related to ferry speeds: - // 1) We cannot account for waiting times for short ferries (when we do the ferry speed is slower than the slowest we can store) - // 2) When the ferry speed is larger than the maximum speed of the encoder (like 15km/h for foot) the - // ferry speed will be faster than what we can store. + // todo: We cannot account for waiting times for short ferries as speed is slower than the slowest we can store - // OSMReader adds the artificial 'speed_from_duration' and 'way_distance' tags that we can + // OSMReader adds the artificial 'duration_in_seconds' and 'way_distance_2d' tags that we can // use to set the ferry speed. Otherwise we need to use fallback values. - double speedInKmPerHour = way.getTag("speed_from_duration", Double.NaN); - if (!Double.isNaN(speedInKmPerHour)) { - // we reduce the speed to account for waiting time (we increase the duration by 40%) - return Math.round(speedInKmPerHour / 1.4); + long durationInSeconds = way.getTag("duration_in_seconds", 0L); + if (durationInSeconds > 0) { + // a way can consist of multiple edges like https://www.openstreetmap.org/way/61215714 => use way_distance_2d + double waitTime = 30 * 60; + double wayDistance = way.getTag("way_distance_2d", Double.NaN); + return Math.round(wayDistance / 1000 / ((durationInSeconds + waitTime) / 60.0 / 60.0)); } else { - // we have no speed value to work with because there was no valid duration tag. - // we have to take a guess based on the distance. - double wayDistance = way.getTag("edge_distance", Double.NaN); - if (Double.isNaN(wayDistance)) + double edgeDistance = way.getTag("edge_distance", Double.NaN); + int shuttleFactor = way.hasTag("route", "shuttle_train") ? 2 : 1; + if (Double.isNaN(edgeDistance)) throw new IllegalStateException("No 'edge_distance' set for edge created for way: " + way.getId()); - else if (wayDistance < 500) + // When we have no speed value to work with we have to take a guess based on the distance. + if (edgeDistance < 1000) { // Use the slowest possible speed for very short ferries. Note that sometimes these aren't really ferries // that take you from one harbour to another, but rather ways that only represent the beginning of a // longer ferry connection and that are used by multiple different connections, like here: https://www.openstreetmap.org/way/107913687 // It should not matter much which speed we use in this case, so we have no special handling for these. - return 1; - else { - // todo: distinguish speed based on the distance of the ferry, see #2532 - return 6; + return 5 * shuttleFactor; + } else if (edgeDistance < 30_000) { + return 15 * shuttleFactor; + } else { + return 30 * shuttleFactor; } } } - public static double minmax(double speed, DecimalEncodedValue avgSpeedEnc) { + static double minmax(double speed, DecimalEncodedValue avgSpeedEnc) { return Math.max(avgSpeedEnc.getSmallestNonZeroValue(), Math.min(speed, avgSpeedEnc.getMaxStorableDecimal())); } diff --git a/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java b/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java index 91fca9bf9f9..46afd3efcda 100644 --- a/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java +++ b/core/src/main/java/com/graphhopper/routing/util/MaxSpeedCalculator.java @@ -4,8 +4,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.parsers.DefaultMaxSpeedParser; +import com.graphhopper.routing.util.parsers.OSMMaxSpeedParser; import com.graphhopper.routing.util.parsers.TagParser; -import com.graphhopper.routing.util.parsers.helpers.OSMValueExtractor; import com.graphhopper.storage.DataAccess; import com.graphhopper.storage.Directory; import com.graphhopper.storage.Graph; @@ -77,8 +77,8 @@ private static void convertMaxspeed(Set>> e if ("maxspeed".equals(tags.getKey()) || "maxspeed:advisory".equals(tags.getKey())) { - double tmp = OSMValueExtractor.stringToKmh(tags.getValue()); - if (Double.isNaN(tmp)) + double tmp = OSMMaxSpeedParser.parseMaxspeedString(tags.getValue()); + if (tmp == MaxSpeed.MAXSPEED_MISSING || tmp == OSMMaxSpeedParser.MAXSPEED_NONE) throw new IllegalStateException("illegal maxspeed " + tags.getValue()); newTags.put(tags.getKey(), "" + Math.round(tmp)); } @@ -120,15 +120,15 @@ public void createDataAccessForParser(Directory directory) { EncodedValue.InitializerConfig config = new EncodedValue.InitializerConfig(); ruralMaxSpeedEnc.init(config); urbanMaxSpeedEnc.init(config); - if (config.getRequiredBits() > 16) - throw new IllegalStateException("bits are not sufficient " + config.getRequiredBits()); + if (config.getRequiredBytes() > 2) + throw new IllegalStateException("bytes are not sufficient " + config.getRequiredBytes()); parser.init(ruralMaxSpeedEnc, urbanMaxSpeedEnc, internalMaxSpeedStorage); } /** - * This method sets max_speed values where the value is UNSET_SPEED to a value determined by - * the default speed library which is country-dependent. + * This method sets max_speed values where the value is {@link MaxSpeed.MAXSPEED_MISSING} to a + * value determined by the default speed library which is country-dependent. */ public void fillMaxSpeed(Graph graph, EncodingManager em) { // In DefaultMaxSpeedParser and in OSMMaxSpeedParser we don't have the rural/urban info, @@ -148,21 +148,21 @@ public void fillMaxSpeed(Graph graph, EncodingManager em, Function set it explicitly to 0 + if (averageSlopeEnc != null) + iter.set(averageSlopeEnc, 0); + if (maxSlopeEnc != null) + iter.set(maxSlopeEnc, 0); + continue; } double towerNodeSlope = calcSlope(pointList.getEle(pointList.size() - 1) - pointList.getEle(0), distance2D); if (Double.isNaN(towerNodeSlope)) - throw new IllegalArgumentException("average_slope was NaN for OSM way ID " + way.getId()); + throw new IllegalArgumentException("average_slope was NaN for edge " + iter.getEdge() + " " + pointList); - if (towerNodeSlope >= 0) - averageSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(towerNodeSlope, averageSlopeEnc.getMaxStorableDecimal())); - else - averageSlopeEnc.setDecimal(true, edgeId, edgeIntAccess, Math.min(Math.abs(towerNodeSlope), averageSlopeEnc.getMaxStorableDecimal())); + if (averageSlopeEnc != null) { + if (towerNodeSlope >= 0) + iter.set(averageSlopeEnc, Math.min(towerNodeSlope, averageSlopeEnc.getMaxStorableDecimal())); + else + iter.setReverse(averageSlopeEnc, Math.min(Math.abs(towerNodeSlope), averageSlopeEnc.getMaxStorableDecimal())); + } - // max_slope is more error-prone as the shorter distances increase the fluctuation - // so apply some more filtering (here we use the average elevation delta of the previous two points) - double maxSlope = 0, prevDist = 0, prevLat = pointList.getLat(0), prevLon = pointList.getLon(0); - for (int i = 1; i < pointList.size(); i++) { - double pillarDistance2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, pointList.getLat(i), pointList.getLon(i)); - if (i > 1 && prevDist > MIN_LENGTH) { - double averagedPrevEle = (pointList.getEle(i - 1) + pointList.getEle(i - 2)) / 2; - double tmpSlope = calcSlope(pointList.getEle(i) - averagedPrevEle, pillarDistance2D + prevDist / 2); - maxSlope = Math.max(maxSlope, Math.abs(tmpSlope)); + if (maxSlopeEnc != null) { + // max_slope is more error-prone as the shorter distances increase the fluctuation + // so apply some more filtering (here we use the average elevation delta of the previous two points) + double maxSlope = 0, prevDist = 0, prevLat = pointList.getLat(0), prevLon = pointList.getLon(0); + for (int i = 1; i < pointList.size(); i++) { + double pillarDistance2D = DistanceCalcEarth.DIST_EARTH.calcDist(prevLat, prevLon, pointList.getLat(i), pointList.getLon(i)); + if (i > 1 && prevDist > MIN_LENGTH) { + double averagedPrevEle = (pointList.getEle(i - 1) + pointList.getEle(i - 2)) / 2; + double tmpSlope = calcSlope(pointList.getEle(i) - averagedPrevEle, pillarDistance2D + prevDist / 2); + maxSlope = Math.abs(tmpSlope) > Math.abs(maxSlope) ? tmpSlope : maxSlope; + } + prevDist = pillarDistance2D; + prevLat = pointList.getLat(i); + prevLon = pointList.getLon(i); } - prevDist = pillarDistance2D; - prevLat = pointList.getLat(i); - prevLon = pointList.getLon(i); - } - // For tunnels and bridges we cannot trust the pillar node elevation and ignore all changes. - // Probably we should somehow recalculate even the average_slope after elevation interpolation? See EdgeElevationInterpolator - if (way.hasTag("tunnel", "yes") || way.hasTag("bridge", "yes") || way.hasTag("highway", "steps")) - maxSlope = Math.abs(towerNodeSlope); - else - maxSlope = Math.max(Math.abs(towerNodeSlope), maxSlope); + maxSlope = Math.abs(towerNodeSlope) > Math.abs(maxSlope) ? towerNodeSlope : maxSlope; - if (Double.isNaN(maxSlope)) - throw new IllegalArgumentException("max_slope was NaN for OSM way ID " + way.getId()); + if (Double.isNaN(maxSlope)) + throw new IllegalArgumentException("max_slope was NaN for edge " + iter.getEdge() + " " + pointList); - // TODO Use two independent values for both directions to store if it is a gain or loss and not just the absolute change. - // TODO To save space then it would be nice to have an encoded value that can store two different values which are swapped when the reverse direction is used - maxSlopeEnc.setDecimal(false, edgeId, edgeIntAccess, Math.min(maxSlope, maxSlopeEnc.getMaxStorableDecimal())); + double val = Math.max(maxSlope, maxSlopeEnc.getMinStorableDecimal()); + iter.set(maxSlopeEnc, Math.min(maxSlopeEnc.getMaxStorableDecimal(), val)); + } } } - static double calcSlope(double eleDelta, double distance2D) { + private static double calcSlope(double eleDelta, double distance2D) { return eleDelta * 100 / distance2D; } } diff --git a/core/src/main/java/com/graphhopper/routing/util/TransportationMode.java b/core/src/main/java/com/graphhopper/routing/util/TransportationMode.java index 30f0287bfc2..c025218d55e 100644 --- a/core/src/main/java/com/graphhopper/routing/util/TransportationMode.java +++ b/core/src/main/java/com/graphhopper/routing/util/TransportationMode.java @@ -26,7 +26,7 @@ */ public enum TransportationMode { OTHER(false), FOOT(false), VEHICLE(false), BIKE(false), - CAR(true), MOTORCYCLE(true), HGV(true), PSV(true), BUS(true); + CAR(true), MOTORCYCLE(true), HGV(true), PSV(true), BUS(true), HOV(true); private final boolean motorVehicle; diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java b/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java deleted file mode 100644 index 37f922190d3..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValues.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util; - -import com.graphhopper.routing.ev.*; -import com.graphhopper.util.PMap; - -import java.util.Arrays; -import java.util.List; - -import static com.graphhopper.routing.util.VehicleEncodedValuesFactory.*; - -public class VehicleEncodedValues { - public static final List OUTDOOR_VEHICLES = Arrays.asList(BIKE, RACINGBIKE, MOUNTAINBIKE, FOOT); - - private final String name; - private final BooleanEncodedValue accessEnc; - private final DecimalEncodedValue avgSpeedEnc; - private final DecimalEncodedValue priorityEnc; - private final BooleanEncodedValue turnRestrictionEnc; - - public static VehicleEncodedValues foot(PMap properties) { - String name = "foot"; - int speedBits = properties.getInt("speed_bits", 4); - double speedFactor = properties.getDouble("speed_factor", 1); - boolean speedTwoDirections = properties.getBool("speed_two_directions", false); - boolean turnCosts = properties.getBool("turn_costs", false); - BooleanEncodedValue accessEnc = VehicleAccess.create(name); - DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections); - DecimalEncodedValue priorityEnc = VehiclePriority.create(name, 4, PriorityCode.getFactor(1), false); - BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null; - return new VehicleEncodedValues(name, accessEnc, speedEnc, priorityEnc, turnRestrictionEnc); - } - - public static VehicleEncodedValues bike(PMap properties) { - return createBike(properties, "bike"); - } - - public static VehicleEncodedValues racingbike(PMap properties) { - return createBike(properties, "racingbike"); - } - - public static VehicleEncodedValues mountainbike(PMap properties) { - return createBike(properties, "mtb"); - } - - private static VehicleEncodedValues createBike(PMap properties, String name) { - int speedBits = properties.getInt("speed_bits", 4); - double speedFactor = properties.getDouble("speed_factor", 2); - boolean speedTwoDirections = properties.getBool("speed_two_directions", false); - boolean turnCosts = properties.getBool("turn_costs", false); - BooleanEncodedValue accessEnc = VehicleAccess.create(name); - DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections); - DecimalEncodedValue priorityEnc = VehiclePriority.create(name, 4, PriorityCode.getFactor(1), false); - BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null; - return new VehicleEncodedValues(name, accessEnc, speedEnc, priorityEnc, turnRestrictionEnc); - } - - public static VehicleEncodedValues car(PMap properties) { - String name = "car"; - int speedBits = properties.getInt("speed_bits", 7); - double speedFactor = properties.getDouble("speed_factor", 2); - boolean turnCosts = properties.getBool("turn_costs", false); - BooleanEncodedValue accessEnc = VehicleAccess.create(name); - DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, true); - BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null; - return new VehicleEncodedValues(name, accessEnc, speedEnc, null, turnRestrictionEnc); - } - - public static VehicleEncodedValues roads(PMap properties) { - String name = "roads"; - int speedBits = properties.getInt("speed_bits", 7); - double speedFactor = properties.getDouble("speed_factor", 2); - boolean speedTwoDirections = properties.getBool("speed_two_directions", true); - boolean turnCosts = properties.getBool("turn_costs", false); - BooleanEncodedValue accessEnc = VehicleAccess.create(name); - DecimalEncodedValue speedEnc = VehicleSpeed.create(name, speedBits, speedFactor, speedTwoDirections); - BooleanEncodedValue turnRestrictionEnc = turnCosts ? TurnRestriction.create(name) : null; - return new VehicleEncodedValues(name, accessEnc, speedEnc, null, turnRestrictionEnc); - } - - public VehicleEncodedValues(String name, BooleanEncodedValue accessEnc, DecimalEncodedValue avgSpeedEnc, - DecimalEncodedValue priorityEnc, BooleanEncodedValue turnRestrictionEnc) { - this.name = name; - this.accessEnc = accessEnc; - this.avgSpeedEnc = avgSpeedEnc; - this.priorityEnc = priorityEnc; - this.turnRestrictionEnc = turnRestrictionEnc; - } - - public void createEncodedValues(List registerNewEncodedValue) { - if (accessEnc != null) - registerNewEncodedValue.add(accessEnc); - if (avgSpeedEnc != null) - registerNewEncodedValue.add(avgSpeedEnc); - if (priorityEnc != null) - registerNewEncodedValue.add(priorityEnc); - } - - public void createTurnCostEncodedValues(List registerNewTurnCostEncodedValues) { - if (turnRestrictionEnc != null) - registerNewTurnCostEncodedValues.add(turnRestrictionEnc); - } - - public BooleanEncodedValue getAccessEnc() { - return accessEnc; - } - - public DecimalEncodedValue getAverageSpeedEnc() { - return avgSpeedEnc; - } - - public DecimalEncodedValue getPriorityEnc() { - return priorityEnc; - } - - public BooleanEncodedValue getTurnRestrictionEnc() { - return turnRestrictionEnc; - } - - public String getName() { - return name; - } - - @Override - public String toString() { - return getName(); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java b/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java deleted file mode 100644 index dee644adecc..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/VehicleEncodedValuesFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util; - -import com.graphhopper.util.PMap; - -/** - * @author Peter Karich - */ -public interface VehicleEncodedValuesFactory { - String ROADS = "roads"; - String CAR = "car"; - String BIKE = "bike"; - String RACINGBIKE = "racingbike"; - String MOUNTAINBIKE = "mtb"; - String FOOT = "foot"; - - VehicleEncodedValues createVehicleEncodedValues(String name, PMap configuration); - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java deleted file mode 100644 index 4998a9609ba..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParserFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util; - -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.util.PMap; - -public interface VehicleTagParserFactory { - VehicleTagParsers createParsers(EncodedValueLookup lookup, String name, PMap configuration); -} diff --git a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java b/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java deleted file mode 100644 index f0106f90450..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/VehicleTagParsers.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util; - -import com.graphhopper.reader.osm.conditional.DateRangeParser; -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.routing.util.parsers.*; -import com.graphhopper.util.PMap; - -import java.util.Arrays; -import java.util.List; - -public class VehicleTagParsers { - private final TagParser accessParser; - private final TagParser speedParser; - private final TagParser priorityParser; - - public static VehicleTagParsers roads(EncodedValueLookup lookup) { - return new VehicleTagParsers( - new RoadsAccessParser(lookup), - new RoadsAverageSpeedParser(lookup), - null - ); - } - - public static VehicleTagParsers car(EncodedValueLookup lookup, PMap properties) { - return new VehicleTagParsers( - new CarAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())), - new CarAverageSpeedParser(lookup), - null - ); - } - - public static VehicleTagParsers bike(EncodedValueLookup lookup, PMap properties) { - return new VehicleTagParsers( - new BikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())), - new BikeAverageSpeedParser(lookup), - new BikePriorityParser(lookup) - ); - } - - public static VehicleTagParsers racingbike(EncodedValueLookup lookup, PMap properties) { - return new VehicleTagParsers( - new RacingBikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())), - new RacingBikeAverageSpeedParser(lookup), - new RacingBikePriorityParser(lookup) - ); - } - - public static VehicleTagParsers mtb(EncodedValueLookup lookup, PMap properties) { - return new VehicleTagParsers( - new MountainBikeAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())), - new MountainBikeAverageSpeedParser(lookup), - new MountainBikePriorityParser(lookup) - ); - } - - public static VehicleTagParsers foot(EncodedValueLookup lookup, PMap properties) { - return new VehicleTagParsers( - new FootAccessParser(lookup, properties).init(properties.getObject("date_range_parser", new DateRangeParser())), - new FootAverageSpeedParser(lookup), - new FootPriorityParser(lookup) - ); - } - - public VehicleTagParsers(TagParser accessParser, TagParser speedParser, TagParser priorityParser) { - this.accessParser = accessParser; - this.speedParser = speedParser; - this.priorityParser = priorityParser; - } - - public TagParser getAccessParser() { - return accessParser; - } - - public TagParser getSpeedParser() { - return speedParser; - } - - public TagParser getPriorityParser() { - return priorityParser; - } - - public List getTagParsers() { - return priorityParser == null ? Arrays.asList(accessParser, speedParser) : Arrays.asList(accessParser, speedParser, priorityParser); - } - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java deleted file mode 100644 index 870138f52f0..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRule.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.TransportationMode; - -/** - * GraphHopper uses country rules to adjust the routing behavior based on the country an edge is located in - */ -public interface CountryRule { - default RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportationMode, RoadAccess currentRoadAccess) { - return currentRoadAccess; - } - - default Toll getToll(ReaderWay readerWay, Toll currentToll) { - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java deleted file mode 100644 index 4262b82deef..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/CountryRuleFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules; - - -import com.graphhopper.routing.ev.Country; -import com.graphhopper.routing.util.countryrules.europe.*; - -import java.util.EnumMap; -import java.util.Map; - -import static com.graphhopper.routing.ev.Country.*; - -public class CountryRuleFactory { - - private final Map rules = new EnumMap<>(Country.class); - - public CountryRuleFactory() { - - // Europe - rules.put(ALB, new AlbaniaCountryRule()); - rules.put(AND, new AndorraCountryRule()); - rules.put(AUT, new AustriaCountryRule()); - rules.put(BEL, new BelgiumCountryRule()); - rules.put(BGR, new BulgariaCountryRule()); - rules.put(BIH, new BosniaHerzegovinaCountryRule()); - rules.put(BLR, new BelarusCountryRule()); - rules.put(CHE, new SwitzerlandCountryRule()); - rules.put(CZE, new CzechiaCountryRule()); - rules.put(DEU, new GermanyCountryRule()); - rules.put(DNK, new DenmarkCountryRule()); - rules.put(ESP, new SpainCountryRule()); - rules.put(EST, new EstoniaCountryRule()); - rules.put(FIN, new FinlandCountryRule()); - rules.put(FRA, new FranceCountryRule()); - rules.put(FRO, new FaroeIslandsCountryRule()); - rules.put(GGY, new GuernseyCountryRule()); - rules.put(GIB, new GibraltarCountryRule()); - rules.put(GBR, new UnitedKingdomCountryRule()); - rules.put(GRC, new GreeceCountryRule()); - rules.put(HRV, new CroatiaCountryRule()); - rules.put(HUN, new HungaryCountryRule()); - rules.put(IMN, new IsleOfManCountryRule()); - rules.put(IRL, new IrelandCountryRule()); - rules.put(ISL, new IcelandCountryRule()); - rules.put(ITA, new ItalyCountryRule()); - rules.put(JEY, new JerseyCountryRule()); - rules.put(LIE, new LiechtensteinCountryRule()); - rules.put(LTU, new LithuaniaCountryRule()); - rules.put(LUX, new LuxembourgCountryRule()); - rules.put(LVA, new LatviaCountryRule()); - rules.put(MCO, new MonacoCountryRule()); - rules.put(MDA, new MoldovaCountryRule()); - rules.put(MKD, new NorthMacedoniaCountryRule()); - rules.put(MLT, new MaltaCountryRule()); - rules.put(MNE, new MontenegroCountryRule()); - rules.put(NLD, new NetherlandsCountryRule()); - rules.put(NOR, new NorwayCountryRule()); - rules.put(POL, new PolandCountryRule()); - rules.put(PRT, new PortugalCountryRule()); - rules.put(ROU, new RomaniaCountryRule()); - rules.put(RUS, new RussiaCountryRule()); - rules.put(SMR, new SanMarinoCountryRule()); - rules.put(SRB, new SerbiaCountryRule()); - rules.put(SVK, new SlovakiaCountryRule()); - rules.put(SVN, new SloveniaCountryRule()); - rules.put(SWE, new SwedenCountryRule()); - rules.put(UKR, new UkraineCountryRule()); - rules.put(VAT, new VaticanCityCountryRule()); - } - - public CountryRule getCountryRule(Country country) { - return rules.get(country); - } - - public Map getCountryToRuleMap() { - return rules; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AlbaniaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AlbaniaCountryRule.java deleted file mode 100644 index 484cce045e5..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AlbaniaCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class AlbaniaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AndorraCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AndorraCountryRule.java deleted file mode 100644 index 6aaaf1df852..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AndorraCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class AndorraCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AustriaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AustriaCountryRule.java deleted file mode 100644 index aeb87c77fcd..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/AustriaCountryRule.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class AustriaCountryRule implements CountryRule { - - @Override - public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportationMode, RoadAccess currentRoadAccess) { - if (currentRoadAccess != RoadAccess.YES) - return currentRoadAccess; - if (!transportationMode.isMotorVehicle()) - return RoadAccess.YES; - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case LIVING_STREET: - return RoadAccess.DESTINATION; - case TRACK: - return RoadAccess.FORESTRY; - case PATH: - case BRIDLEWAY: - case CYCLEWAY: - case FOOTWAY: - case PEDESTRIAN: - return RoadAccess.NO; - default: - return RoadAccess.YES; - } - } - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) { - return Toll.ALL; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelarusCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelarusCountryRule.java deleted file mode 100644 index 96bc500b09e..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BelarusCountryRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class BelarusCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY) { - return Toll.HGV; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BosniaHerzegovinaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BosniaHerzegovinaCountryRule.java deleted file mode 100644 index 4b3fbe23bcb..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BosniaHerzegovinaCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class BosniaHerzegovinaCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CzechiaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CzechiaCountryRule.java deleted file mode 100644 index c3ca0fb777b..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/CzechiaCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for the roads of the Czech Republic. - * - * @author Thomas Butz - */ -public class CzechiaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/DenmarkCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/DenmarkCountryRule.java deleted file mode 100644 index ddec29d0adc..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/DenmarkCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Polish roads - * - * @author Thomas Butz - */ -public class DenmarkCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FaroeIslandsCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FaroeIslandsCountryRule.java deleted file mode 100644 index 41c50eaa063..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FaroeIslandsCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class FaroeIslandsCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FinlandCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FinlandCountryRule.java deleted file mode 100644 index e16319ca964..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FinlandCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class FinlandCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FranceCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FranceCountryRule.java deleted file mode 100644 index dc999897851..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/FranceCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for French roads - * - * @author Thomas Butz - */ -public class FranceCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GermanyCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GermanyCountryRule.java deleted file mode 100644 index 640896d5593..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GermanyCountryRule.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.MaxSpeed; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * @author Robin Boldt - */ -public class GermanyCountryRule implements CountryRule { - - @Override - public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportationMode, RoadAccess currentRoadAccess) { - if (currentRoadAccess != RoadAccess.YES) - return currentRoadAccess; - if (!transportationMode.isMotorVehicle()) - return RoadAccess.YES; - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case TRACK: - return RoadAccess.DESTINATION; - case PATH: - case BRIDLEWAY: - case CYCLEWAY: - case FOOTWAY: - case PEDESTRIAN: - return RoadAccess.NO; - default: - return RoadAccess.YES; - } - } - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) { - return Toll.HGV; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GibraltarCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GibraltarCountryRule.java deleted file mode 100644 index f9a1e35cc58..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GibraltarCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class GibraltarCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GreeceCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GreeceCountryRule.java deleted file mode 100644 index 0be76db3a8e..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GreeceCountryRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class GreeceCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY) { - return Toll.ALL; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GuernseyCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GuernseyCountryRule.java deleted file mode 100644 index 6d50fb64ca7..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/GuernseyCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class GuernseyCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java deleted file mode 100644 index 4955ab2aeaa..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/HungaryCountryRule.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Hungarian roads - * - * @author Thomas Butz - */ -public class HungaryCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case MOTORWAY: - return Toll.ALL; - case TRUNK: - case PRIMARY: - return Toll.HGV; - default: - return currentToll; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IcelandCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IcelandCountryRule.java deleted file mode 100644 index d5ff358f297..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IcelandCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class IcelandCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IsleOfManCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IsleOfManCountryRule.java deleted file mode 100644 index b0d0638bcde..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IsleOfManCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class IsleOfManCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/ItalyCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/ItalyCountryRule.java deleted file mode 100644 index ebe34e43a5a..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/ItalyCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Italian roads - * - * @author Thomas Butz - */ -public class ItalyCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/JerseyCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/JerseyCountryRule.java deleted file mode 100644 index ab188db022e..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/JerseyCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class JerseyCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LatviaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LatviaCountryRule.java deleted file mode 100644 index b128df7627f..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LatviaCountryRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class LatviaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) { - return Toll.HGV; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LiechtensteinCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LiechtensteinCountryRule.java deleted file mode 100644 index 1f9f5bcdcc0..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LiechtensteinCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class LiechtensteinCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LithuaniaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LithuaniaCountryRule.java deleted file mode 100644 index 84805a5b4ff..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LithuaniaCountryRule.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class LithuaniaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) { - return Toll.HGV; - } - - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LuxembourgCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LuxembourgCountryRule.java deleted file mode 100644 index 722c54cc88c..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/LuxembourgCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Luxembourgish roads - * - * @author Thomas Butz - */ -public class LuxembourgCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MaltaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MaltaCountryRule.java deleted file mode 100644 index bc535ae1e4f..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MaltaCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class MaltaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MoldovaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MoldovaCountryRule.java deleted file mode 100644 index c5e4beddfce..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MoldovaCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class MoldovaCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MonacoCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MonacoCountryRule.java deleted file mode 100644 index 770cdda92c9..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MonacoCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class MonacoCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MontenegroCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MontenegroCountryRule.java deleted file mode 100644 index e505b0b5c1e..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/MontenegroCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class MontenegroCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NetherlandsCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NetherlandsCountryRule.java deleted file mode 100644 index 9cac3121068..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NetherlandsCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Netherlands roads - * - * @author Thomas Butz - */ -public class NetherlandsCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorthMacedoniaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorthMacedoniaCountryRule.java deleted file mode 100644 index ffb94a76356..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorthMacedoniaCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class NorthMacedoniaCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorwayCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorwayCountryRule.java deleted file mode 100644 index c670d9af40a..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/NorwayCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class NorwayCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PolandCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PolandCountryRule.java deleted file mode 100644 index c9b96943bb5..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PolandCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Polish roads - * - * @author Thomas Butz - */ -public class PolandCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PortugalCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PortugalCountryRule.java deleted file mode 100644 index 8214e9847a9..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/PortugalCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Portuguese roads - * - * @author Thomas Butz - */ -public class PortugalCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaCountryRule.java deleted file mode 100644 index 104471dd59c..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Croatian roads - * - * @author Thomas Butz - */ -public class RomaniaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaSpatialRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaSpatialRule.java deleted file mode 100644 index 93bbdf80a58..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RomaniaSpatialRule.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Romanian roads - * - * @author Thomas Butz - */ -public class RomaniaSpatialRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case MOTORWAY: - case TRUNK: - case PRIMARY: - return Toll.ALL; - default: - return currentToll; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RussiaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RussiaCountryRule.java deleted file mode 100644 index 11776a22080..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/RussiaCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class RussiaCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SanMarinoCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SanMarinoCountryRule.java deleted file mode 100644 index a6bc8e35e78..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SanMarinoCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class SanMarinoCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SerbiaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SerbiaCountryRule.java deleted file mode 100644 index 95b17ead157..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SerbiaCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Serbian roads - * - * @author Thomas Butz - */ -public class SerbiaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SlovakiaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SlovakiaCountryRule.java deleted file mode 100644 index 4a3dc00b0f1..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SlovakiaCountryRule.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Slovakian roads - * - * @author Thomas Butz - */ -public class SlovakiaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case MOTORWAY: - case TRUNK: - return Toll.ALL; - default: - return currentToll; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SloveniaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SloveniaCountryRule.java deleted file mode 100644 index c39f4ccbcd5..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SloveniaCountryRule.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Slovenian roads - * - * @author Thomas Butz - */ -public class SloveniaCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - switch (roadClass) { - case MOTORWAY: - case TRUNK: - return Toll.ALL; - default: - return currentToll; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SpainCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SpainCountryRule.java deleted file mode 100644 index 8b08ca00c37..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SpainCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Spanish roads - * - * @author Thomas Butz - */ -public class SpainCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwedenCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwedenCountryRule.java deleted file mode 100644 index 7cf8d43a2a9..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwedenCountryRule.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Swedish roads - * - * @author Thomas Butz - */ -public class SwedenCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.HGV; - return currentToll; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwitzerlandCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwitzerlandCountryRule.java deleted file mode 100644 index 1039c3846ad..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/SwitzerlandCountryRule.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -/** - * Defines the default rules for Swiss roads - * - * @author Thomas Butz - */ -public class SwitzerlandCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (currentToll != null) - return currentToll; - - switch (roadClass) { - case MOTORWAY: - case TRUNK: - return Toll.ALL; - default: - return currentToll; - } - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UkraineCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UkraineCountryRule.java deleted file mode 100644 index 13e84f6c2aa..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UkraineCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class UkraineCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UnitedKingdomCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UnitedKingdomCountryRule.java deleted file mode 100644 index 5bc7e4e5856..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/UnitedKingdomCountryRule.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class UnitedKingdomCountryRule implements CountryRule { - -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/VaticanCityCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/VaticanCityCountryRule.java deleted file mode 100644 index 0fe662ccf18..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/VaticanCityCountryRule.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.routing.util.countryrules.europe; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; - -public class VaticanCityCountryRule implements CountryRule { - - @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; - } - - return Toll.NO; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java index 50522b10e7e..423e4131f21 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAccessParser.java @@ -1,13 +1,26 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.ConditionalOSMTagInspector; -import com.graphhopper.reader.osm.conditional.ConditionalTagInspector; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.util.TransportationMode; import com.graphhopper.storage.IntsRef; import java.util.*; @@ -17,63 +30,41 @@ public abstract class AbstractAccessParser implements TagParser { static final Collection INTENDED = Arrays.asList("yes", "designated", "official", "permissive"); // order is important - protected final List restrictions = new ArrayList<>(5); + protected final List restrictionKeys; protected final Set restrictedValues = new HashSet<>(5); - protected final Set intendedValues = new HashSet<>(INTENDED); - protected final Set oneways = new HashSet<>(ONEWAYS); + protected final Set allowedValues = new HashSet<>(INTENDED); // http://wiki.openstreetmap.org/wiki/Mapfeatures#Barrier protected final Set barriers = new HashSet<>(5); protected final BooleanEncodedValue accessEnc; - private boolean blockFords = true; - private ConditionalTagInspector conditionalTagInspector; - protected AbstractAccessParser(BooleanEncodedValue accessEnc, TransportationMode transportationMode) { + protected AbstractAccessParser(BooleanEncodedValue accessEnc, List restrictionKeys) { this.accessEnc = accessEnc; + this.restrictionKeys = restrictionKeys; + + allowedValues.add("destination"); restrictedValues.add("no"); restrictedValues.add("restricted"); restrictedValues.add("military"); restrictedValues.add("emergency"); + restrictedValues.add("unknown"); + restrictedValues.add("private"); + restrictedValues.add("service"); restrictedValues.add("permit"); - - restrictions.addAll(OSMRoadAccessParser.toOSMRestrictions(transportationMode)); - } - - public AbstractAccessParser init(DateRangeParser dateRangeParser) { - setConditionalTagInspector(new ConditionalOSMTagInspector(Collections.singletonList(dateRangeParser), - restrictions, restrictedValues, intendedValues, false)); - return this; - } - - protected void setConditionalTagInspector(ConditionalTagInspector inspector) { - conditionalTagInspector = inspector; - } - - public boolean isBlockFords() { - return blockFords; - } - - protected void blockFords(boolean blockFords) { - this.blockFords = blockFords; } protected void blockPrivate(boolean blockPrivate) { if (!blockPrivate) { - if (!restrictedValues.remove("private")) - throw new IllegalStateException("no 'private' found in restrictedValues"); - if (!restrictedValues.remove("permit")) - throw new IllegalStateException("no 'permit' found in restrictedValues"); - intendedValues.add("private"); - intendedValues.add("permit"); + if (!restrictedValues.remove("private") || !restrictedValues.remove("permit") || !restrictedValues.remove("service")) + throw new IllegalStateException("no 'private', 'permit' or 'service' value found in restrictedValues"); + allowedValues.add("private"); + allowedValues.add("permit"); + allowedValues.add("service"); } } - public ConditionalTagInspector getConditionalTagInspector() { - return conditionalTagInspector; - } - protected void handleBarrierEdge(int edgeId, EdgeIntAccess edgeIntAccess, Map nodeTags) { // for now we just create a dummy reader node, because our encoders do not make use of the coordinates anyway ReaderNode readerNode = new ReaderNode(0, 0, 0, nodeTags); @@ -93,27 +84,28 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way public abstract void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way); /** - * @return true if the given OSM node blocks access for this vehicle, false otherwise + * @return true if the given OSM node blocks access for the specified restrictions, false otherwise */ public boolean isBarrier(ReaderNode node) { // note that this method will be only called for certain nodes as defined by OSMReader! - String firstValue = node.getFirstPriorityTag(restrictions); - if (restrictedValues.contains(firstValue) || node.hasTag("locked", "yes")) + String firstValue = node.getFirstValue(restrictionKeys); + + if (restrictedValues.contains(firstValue)) return true; - else if (intendedValues.contains(firstValue)) - return false; - else if (node.hasTag("barrier", barriers)) + else if (node.hasTag("locked", "yes") && !allowedValues.contains(firstValue)) return true; + else if (allowedValues.contains(firstValue)) + return false; else - return blockFords && node.hasTag("ford", "yes"); + return node.hasTag("barrier", barriers); } public final BooleanEncodedValue getAccessEnc() { return accessEnc; } - public final List getRestrictions() { - return restrictions; + public final List getRestrictionKeys() { + return restrictionKeys; } public final String getName() { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAverageSpeedParser.java index 2749b4b19b7..f2eecef36e3 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/AbstractAverageSpeedParser.java @@ -3,34 +3,15 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.util.parsers.helpers.OSMValueExtractor; import com.graphhopper.storage.IntsRef; public abstract class AbstractAverageSpeedParser implements TagParser { // http://wiki.openstreetmap.org/wiki/Mapfeatures#Barrier protected final DecimalEncodedValue avgSpeedEnc; - protected final DecimalEncodedValue ferrySpeedEnc; - protected AbstractAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeedEnc) { + protected AbstractAverageSpeedParser(DecimalEncodedValue speedEnc) { this.avgSpeedEnc = speedEnc; - this.ferrySpeedEnc = ferrySpeedEnc; - } - - /** - * @return {@link Double#NaN} if no maxspeed found - */ - public static double getMaxSpeed(ReaderWay way, boolean bwd) { - double maxSpeed = OSMValueExtractor.stringToKmh(way.getTag("maxspeed")); - double directedMaxSpeed = OSMValueExtractor.stringToKmh(way.getTag(bwd ? "maxspeed:backward" : "maxspeed:forward")); - return isValidSpeed(directedMaxSpeed) ? directedMaxSpeed : maxSpeed; - } - - /** - * @return true if the given speed is not {@link Double#NaN} - */ - protected static boolean isValidSpeed(double speed) { - return !Double.isNaN(speed); } public final DecimalEncodedValue getAverageSpeedEnc() { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAccessParser.java index 28ffbeb33c1..cf7010ab1e9 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAccessParser.java @@ -12,7 +12,6 @@ public BikeAccessParser(EncodedValueLookup lookup, PMap properties) { this(lookup.getBooleanEncodedValue(VehicleAccess.key("bike")), lookup.getBooleanEncodedValue(Roundabout.KEY)); blockPrivate(properties.getBool("block_private", true)); - blockFords(properties.getBool("block_fords", false)); } public BikeAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAverageSpeedParser.java index 436c7936201..a27e9123146 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeAverageSpeedParser.java @@ -7,11 +7,12 @@ public class BikeAverageSpeedParser extends BikeCommonAverageSpeedParser { public BikeAverageSpeedParser(EncodedValueLookup lookup) { this(lookup.getDecimalEncodedValue(VehicleSpeed.key("bike")), lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class), - lookup.getDecimalEncodedValue(FerrySpeed.KEY)); + lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class)); } - public BikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) { - super(speedEnc, smoothnessEnc, ferrySpeedEnc); - addPushingSection("path"); + public BikeAverageSpeedParser(DecimalEncodedValue speedEnc, + EnumEncodedValue smoothnessEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, bikeRouteEnc); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java index 685f073cc85..ffc2405297a 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAccessParser.java @@ -4,19 +4,27 @@ import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.routing.util.FerrySpeedCalculator; -import com.graphhopper.routing.util.TransportationMode; import com.graphhopper.routing.util.WayAccess; import java.util.*; +import static com.graphhopper.routing.util.parsers.OSMTemporalAccessParser.hasPermissiveTemporalRestriction; + public abstract class BikeCommonAccessParser extends AbstractAccessParser implements TagParser { private static final Set OPP_LANES = new HashSet<>(Arrays.asList("opposite", "opposite_lane", "opposite_track")); private final Set allowedHighways = new HashSet<>(); private final BooleanEncodedValue roundaboutEnc; + /** + * The access restriction list returned from OSMRoadAccessParser.toOSMRestrictions(TransportationMode.Bike) + * contains "vehicle". But here we want to allow walking via dismount. + */ + private static final List RESTRICTIONS = Arrays.asList("bicycle", "access"); + private static final Collection FWDONEWAYS = Arrays.asList("yes", "true", "1"); + protected BikeCommonAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) { - super(accessEnc, TransportationMode.BIKE); + super(accessEnc, RESTRICTIONS); this.roundaboutEnc = roundaboutEnc; @@ -24,11 +32,6 @@ protected BikeCommonAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedVa restrictedValues.add("forestry"); restrictedValues.add("delivery"); - intendedValues.add("yes"); - intendedValues.add("designated"); - intendedValues.add("official"); - intendedValues.add("permissive"); - barriers.add("fence"); allowedHighways.addAll(Arrays.asList("living_street", "steps", "cycleway", "path", "footway", "platform", @@ -45,7 +48,7 @@ public WayAccess getAccess(ReaderWay way) { if (FerrySpeedCalculator.isFerry(way)) { // if bike is NOT explicitly tagged allow bike but only if foot is not specified either String bikeTag = way.getTag("bicycle"); - if (bikeTag == null && !way.hasTag("foot") || intendedValues.contains(bikeTag)) + if (bikeTag == null && !way.hasTag("foot") || allowedValues.contains(bikeTag) || "dismount".equals(bikeTag)) access = WayAccess.FERRY; } @@ -57,7 +60,7 @@ public WayAccess getAccess(ReaderWay way) { access = WayAccess.WAY; if (!access.canSkip()) { - if (way.hasTag(restrictions, restrictedValues) && !getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way)) + if (way.hasTag(restrictionKeys, restrictedValues)) return WayAccess.CAN_SKIP; return access; } @@ -68,79 +71,50 @@ public WayAccess getAccess(ReaderWay way) { if (!allowedHighways.contains(highwayValue)) return WayAccess.CAN_SKIP; - String sacScale = way.getTag("sac_scale"); - if (sacScale != null) { - if (!isSacScaleAllowed(sacScale)) - return WayAccess.CAN_SKIP; - } - - // use the way if it is tagged for bikes - if (way.hasTag("bicycle", "dismount") || way.hasTag("highway", "cycleway")) + if (way.hasTag("bicycle", "dismount") // use the way for pushing + || "cycleway".equals(highwayValue) && !way.hasTag("bicycle", "no")) // cycleway gets bicycle=yes by default return WayAccess.WAY; - boolean permittedWayConditionallyRestricted = getConditionalTagInspector().isPermittedWayConditionallyRestricted(way); - boolean restrictedWayConditionallyPermitted = getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way); - String firstValue = way.getFirstPriorityTag(restrictions); - if (!firstValue.isEmpty()) { + int firstIndex = way.getFirstIndex(restrictionKeys); + if (firstIndex >= 0) { + String firstValue = way.getTag(restrictionKeys.get(firstIndex), ""); String[] restrict = firstValue.split(";"); + // if any of the values allows access then return early (regardless of the order) for (String value : restrict) { - if (restrictedValues.contains(value) && !restrictedWayConditionallyPermitted) - return WayAccess.CAN_SKIP; - if (intendedValues.contains(value) && !permittedWayConditionallyRestricted) + if (allowedValues.contains(value)) return WayAccess.WAY; } + for (String value : restrict) { + if (restrictedValues.contains(value) && !hasPermissiveTemporalRestriction(way, firstIndex, restrictionKeys, allowedValues)) + return WayAccess.CAN_SKIP; + } } // accept only if explicitly tagged for bike usage - if ("motorway".equals(highwayValue) || "motorway_link".equals(highwayValue) || "bridleway".equals(highwayValue)) + if ("motorway".equals(highwayValue) || "motorway_link".equals(highwayValue)) return WayAccess.CAN_SKIP; if (way.hasTag("motorroad", "yes")) return WayAccess.CAN_SKIP; - if (isBlockFords() && ("ford".equals(highwayValue) || way.hasTag("ford"))) - return WayAccess.CAN_SKIP; - - if (permittedWayConditionallyRestricted) - return WayAccess.CAN_SKIP; - return WayAccess.WAY; } - boolean isSacScaleAllowed(String sacScale) { - // other scales are nearly impossible by an ordinary bike, see http://wiki.openstreetmap.org/wiki/Key:sac_scale - return "hiking".equals(sacScale); - } - @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { WayAccess access = getAccess(way); if (access.canSkip()) return; - if (access.isFerry()) { - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - } else { - handleAccess(edgeId, edgeIntAccess, way); - } - - if (way.hasTag("gh:barrier_edge")) { - List> nodeTags = way.getTag("node_tags", Collections.emptyList()); - handleBarrierEdge(edgeId, edgeIntAccess, nodeTags.get(0)); - } - } - - protected void handleAccess(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { // handle oneways. The value -1 means it is a oneway but for reverse direction of stored geometry. // The tagging oneway:bicycle=no or cycleway:right:oneway=no or cycleway:left:oneway=no lifts the generic oneway restriction of the way for bike - boolean isOneway = way.hasTag("oneway", ONEWAYS) && !way.hasTag("oneway", "-1") && !way.hasTag("bicycle:backward", INTENDED) - || way.hasTag("oneway", "-1") && !way.hasTag("bicycle:forward", INTENDED) + boolean isOneway = way.hasTag("oneway", ONEWAYS) && !way.hasTag("oneway", "-1") && !way.hasTag("bicycle:backward", allowedValues) + || way.hasTag("oneway", "-1") && !way.hasTag("bicycle:forward", allowedValues) || way.hasTag("oneway:bicycle", ONEWAYS) - || way.hasTag("cycleway:left:oneway", ONEWAYS) - || way.hasTag("cycleway:right:oneway", ONEWAYS) - || way.hasTag("vehicle:backward", restrictedValues) && !way.hasTag("bicycle:forward", INTENDED) - || way.hasTag("vehicle:forward", restrictedValues) && !way.hasTag("bicycle:backward", INTENDED) + || way.hasTag("cycleway:left:oneway", FWDONEWAYS) && !way.hasTag("cycleway:right:oneway", "-1") + || way.hasTag("cycleway:right:oneway", FWDONEWAYS) && !way.hasTag("cycleway:left:oneway", "-1") + || way.hasTag("vehicle:backward", restrictedValues) && !way.hasTag("bicycle:forward", allowedValues) + || way.hasTag("vehicle:forward", restrictedValues) && !way.hasTag("bicycle:backward", allowedValues) || way.hasTag("bicycle:forward", restrictedValues) || way.hasTag("bicycle:backward", restrictedValues); @@ -164,5 +138,10 @@ protected void handleAccess(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay w accessEnc.setBool(true, edgeId, edgeIntAccess, true); accessEnc.setBool(false, edgeId, edgeIntAccess, true); } + + if (way.hasTag("gh:barrier_edge")) { + List> nodeTags = way.getTag("node_tags", Collections.emptyList()); + handleBarrierEdge(edgeId, edgeIntAccess, nodeTags.get(0)); + } } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAverageSpeedParser.java index 4a86ac0950b..043210746f7 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonAverageSpeedParser.java @@ -1,66 +1,60 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.Smoothness; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.FerrySpeedCalculator; -import com.graphhopper.util.Helper; +import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; import java.util.Map; import java.util.Set; +import static com.graphhopper.routing.util.parsers.AbstractAccessParser.INTENDED; + public abstract class BikeCommonAverageSpeedParser extends AbstractAverageSpeedParser implements TagParser { protected static final int PUSHING_SECTION_SPEED = 4; protected static final int MIN_SPEED = 2; - // Pushing section highways are parts where you need to get off your bike and push it (German: Schiebestrecke) - protected final HashSet pushingSectionsHighways = new HashSet<>(); private final Map trackTypeSpeeds = new HashMap<>(); private final Map surfaceSpeeds = new HashMap<>(); private final Map smoothnessFactor = new HashMap<>(); private final Map highwaySpeeds = new HashMap<>(); private final EnumEncodedValue smoothnessEnc; - protected final Set intendedValues = new HashSet<>(5); - - protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) { - super(speedEnc, ferrySpeedEnc); + private final Set restrictedValues = Set.of("no", "agricultural", "forestry", "restricted", "military", "emergency", "private", "permit"); + private final EnumEncodedValue bikeRouteEnc; + + protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, + EnumEncodedValue smoothnessEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc); + this.bikeRouteEnc = bikeRouteEnc; this.smoothnessEnc = smoothnessEnc; - // duplicate code as also in BikeCommonPriorityParser - addPushingSection("footway"); - addPushingSection("pedestrian"); - addPushingSection("steps"); - addPushingSection("platform"); - setTrackTypeSpeed("grade1", 18); // paved - setTrackTypeSpeed("grade2", 12); // now unpaved ... - setTrackTypeSpeed("grade3", 8); - setTrackTypeSpeed("grade4", 6); - setTrackTypeSpeed("grade5", 4); // like sand/grass + setTrackTypeSpeed("grade2", 14); // like compacted + setTrackTypeSpeed("grade3", 12); // like unpaved + setTrackTypeSpeed("grade4", 10); // better than grass, more like dirt ("hard or compacted materials mixed in") + setTrackTypeSpeed("grade5", PUSHING_SECTION_SPEED); // like sand setSurfaceSpeed("paved", 18); setSurfaceSpeed("asphalt", 18); setSurfaceSpeed("cobblestone", 8); setSurfaceSpeed("cobblestone:flattened", 10); - setSurfaceSpeed("sett", 10); + setSurfaceSpeed("sett", 12); setSurfaceSpeed("concrete", 18); setSurfaceSpeed("concrete:lanes", 16); setSurfaceSpeed("concrete:plates", 16); - setSurfaceSpeed("paving_stones", 14); - setSurfaceSpeed("paving_stones:30", 14); + setSurfaceSpeed("paving_stones", 16); + setSurfaceSpeed("paving_stones:30", 16); setSurfaceSpeed("unpaved", 12); setSurfaceSpeed("compacted", 14); setSurfaceSpeed("dirt", 10); - setSurfaceSpeed("earth", 12); - setSurfaceSpeed("fine_gravel", 18); + setSurfaceSpeed("earth", 10); + setSurfaceSpeed("ground", 10); + setSurfaceSpeed("fine_gravel", 14); // should not be faster than compacted setSurfaceSpeed("grass", 8); setSurfaceSpeed("grass_paver", 8); setSurfaceSpeed("gravel", 12); - setSurfaceSpeed("ground", 12); setSurfaceSpeed("ice", MIN_SPEED); setSurfaceSpeed("metal", 10); setSurfaceSpeed("mud", 10); @@ -69,14 +63,15 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setSurfaceSpeed("sand", PUSHING_SECTION_SPEED); setSurfaceSpeed("wood", PUSHING_SECTION_SPEED); - setHighwaySpeed("living_street", PUSHING_SECTION_SPEED); + setHighwaySpeed("living_street", 6); setHighwaySpeed("steps", MIN_SPEED); setHighwaySpeed("cycleway", 18); - setHighwaySpeed("path", 10); + setHighwaySpeed("path", 6); setHighwaySpeed("footway", 6); - setHighwaySpeed("platform", PUSHING_SECTION_SPEED); - setHighwaySpeed("pedestrian", PUSHING_SECTION_SPEED); + setHighwaySpeed("platform", 6); + setHighwaySpeed("pedestrian", 6); + setHighwaySpeed("bridleway", 6); setHighwaySpeed("track", 12); setHighwaySpeed("service", 12); setHighwaySpeed("residential", 18); @@ -98,8 +93,6 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setHighwaySpeed("motorway", 18); setHighwaySpeed("motorway_link", 18); - setHighwaySpeed("bridleway", PUSHING_SECTION_SPEED); - // note that this factor reduces the speed but only until MIN_SPEED setSmoothnessSpeedFactor(Smoothness.MISSING, 1.0d); setSmoothnessSpeedFactor(Smoothness.OTHER, 0.7d); @@ -111,11 +104,6 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setSmoothnessSpeedFactor(Smoothness.HORRIBLE, 0.3d); setSmoothnessSpeedFactor(Smoothness.VERY_HORRIBLE, 0.1d); setSmoothnessSpeedFactor(Smoothness.IMPASSABLE, 0); - - intendedValues.add("yes"); - intendedValues.add("designated"); - intendedValues.add("official"); - intendedValues.add("permissive"); } /** @@ -124,105 +112,89 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded * @return The assumed average speed. */ double applyMaxSpeed(ReaderWay way, double speed, boolean bwd) { - double maxSpeed = getMaxSpeed(way, bwd); + double maxSpeed = OSMMaxSpeedParser.parseMaxSpeed(way, bwd); // We strictly obey speed limits, see #600 - return isValidSpeed(maxSpeed) && speed > maxSpeed ? maxSpeed : speed; + return Math.min(speed, maxSpeed); } @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { - String highwayValue = way.getTag("highway"); - if (highwayValue == null) { - if (FerrySpeedCalculator.isFerry(way)) { - double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc); - setSpeed(false, edgeId, edgeIntAccess, ferrySpeed); - if (avgSpeedEnc.isStoreTwoDirections()) - setSpeed(true, edgeId, edgeIntAccess, ferrySpeed); - } - if (!way.hasTag("railway", "platform") && !way.hasTag("man_made", "pier")) + String highwayValue = way.getTag("highway", ""); + if (highwayValue.isEmpty()) { + if (FerrySpeedCalculator.isFerry(way) || !way.hasTag("railway", "platform") && !way.hasTag("man_made", "pier")) return; } - double speed = getSpeed(way); - Smoothness smoothness = smoothnessEnc.getEnum(false, edgeId, edgeIntAccess); - speed = Math.max(MIN_SPEED, smoothnessFactor.get(smoothness) * speed); - setSpeed(false, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, false)); - if (avgSpeedEnc.isStoreTwoDirections()) - setSpeed(true, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, true)); - } + double speed = highwaySpeeds.getOrDefault(highwayValue, PUSHING_SECTION_SPEED); + String surfaceValue = way.getTag("surface"); + String trackTypeValue = way.getTag("tracktype"); + boolean pushingRestriction = Arrays.stream(way.getTag("vehicle", "").split(";")).anyMatch(restrictedValues::contains); + Integer surfaceSpeed = surfaceSpeeds.get(surfaceValue); + Integer trackTypeSpeed = trackTypeSpeeds.get(trackTypeValue); + if (trackTypeSpeed != null) + surfaceSpeed = surfaceSpeed == null ? trackTypeSpeed : Math.min(surfaceSpeed, trackTypeSpeed); + boolean bikeDesignated = RouteNetwork.MISSING != bikeRouteEnc.getEnum(false, edgeId, edgeIntAccess) + || BikeCommonPriorityParser.isBikeDesignated(way); + + if (way.hasTag("surface") && surfaceSpeed == null + || way.hasTag("bicycle", "dismount") + || way.hasTag("railway", "platform") + || pushingRestriction && !way.hasTag("bicycle", INTENDED)) { + speed = PUSHING_SECTION_SPEED; - int getSpeed(ReaderWay way) { - int speed = PUSHING_SECTION_SPEED; - String highwayTag = way.getTag("highway"); - Integer highwaySpeed = highwaySpeeds.get(highwayTag); - - if (way.hasTag("railway", "platform")) - highwaySpeed = PUSHING_SECTION_SPEED; - // Under certain conditions we need to increase the speed of pushing sections to the speed of a "highway=cycleway" - else if (way.hasTag("highway", pushingSectionsHighways) - && ((way.hasTag("foot", "yes") && way.hasTag("segregated", "yes")) - || (way.hasTag("bicycle", intendedValues)) && !way.hasTag("highway", "steps"))) - highwaySpeed = getHighwaySpeed("cycleway"); - - String s = way.getTag("surface"); - Integer surfaceSpeed = 0; - if (!Helper.isEmpty(s)) { - surfaceSpeed = surfaceSpeeds.get(s); - if (surfaceSpeed != null) { - speed = surfaceSpeed; - // boost handling for good surfaces but avoid boosting if pushing section - if (highwaySpeed != null && surfaceSpeed > highwaySpeed && pushingSectionsHighways.contains(highwayTag)) - speed = highwaySpeed; - } } else { - String tt = way.getTag("tracktype"); - if (!Helper.isEmpty(tt)) { - Integer tInt = trackTypeSpeeds.get(tt); - if (tInt != null) - speed = tInt; - } else if (highwaySpeed != null) { - if (!way.hasTag("service")) - speed = highwaySpeed; - else - speed = highwaySpeeds.get("living_street"); + + boolean bikeAllowed = way.hasTag("bicycle", "yes") || bikeDesignated; + boolean isRacingBike = this instanceof RacingBikeAverageSpeedParser; + + // increase speed for certain highway tags because of a good surface or a more permissive bike access + switch (highwayValue) { + case "track": // assume bicycle=yes even if no bicycle tag + bikeAllowed = bikeAllowed || !way.hasTag("bicycle"); + + case "path", "bridleway": + if (surfaceSpeed != null) + speed = Math.max(speed, bikeAllowed ? surfaceSpeed : surfaceSpeed * 0.7); + else if (isRacingBike) + break; // no speed increase if no surface tag + + case "footway", "pedestrian", "platform": + // speed increase if bike allowed or even designated + if (bikeDesignated) + speed = Math.max(speed, highwaySpeeds.get("cycleway")); + else if (bikeAllowed) + speed = Math.max(speed, 12); + break; + case "living_street": + if (bikeAllowed) + // if explicitly allowed then allow speeds above limit to get more realistic routes and ETAs + speed = bikeDesignated ? Math.max(speed, 12) : Math.max(speed, 10); + break; } - } - // Until now we assumed that the way is no pushing section - // Now we check that, but only in case that our speed computed so far is bigger compared to the PUSHING_SECTION_SPEED - if (speed > PUSHING_SECTION_SPEED - && (way.hasTag("highway", pushingSectionsHighways) || way.hasTag("bicycle", "dismount"))) { - if (!way.hasTag("bicycle", intendedValues)) { - // Here we set the speed for pushing sections and set speed for steps as even lower: - speed = way.hasTag("highway", "steps") ? MIN_SPEED : PUSHING_SECTION_SPEED; - } else if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official") || - way.hasTag("segregated", "yes") || way.hasTag("bicycle", "yes")) { - // Here we handle the cases where the OSM tagging results in something similar to "highway=cycleway" - if (way.hasTag("segregated", "yes")) - speed = highwaySpeeds.get("cycleway"); - else - speed = way.hasTag("bicycle", "yes") ? 10 : highwaySpeeds.get("cycleway"); - - // valid surface speed? - if (surfaceSpeed > 0) - speed = Math.min(speed, surfaceSpeed); + if (way.hasTag("service", "parking_aisle") && !bikeDesignated) + speed = Math.min(speed, 8); + + double smoothSpeed = smoothnessFactor.get(smoothnessEnc.getEnum(false, edgeId, edgeIntAccess)) * speed; + + // speed reduction if bad surface + if (surfaceSpeed != null) { + // pick the smallest of smoothness<->surface, if both are present + speed = Math.max(MIN_SPEED, Math.min(Math.min(surfaceSpeed, speed), smoothSpeed)); + } else { + speed = Math.max(MIN_SPEED, smoothSpeed); } } - return speed; + + setSpeed(false, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, false)); + if (avgSpeedEnc.isStoreTwoDirections()) + setSpeed(true, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, true)); } void setHighwaySpeed(String highway, int speed) { highwaySpeeds.put(highway, speed); } - int getHighwaySpeed(String key) { - return highwaySpeeds.get(key); - } - - void addPushingSection(String highway) { - pushingSectionsHighways.add(highway); - } - void setTrackTypeSpeed(String tracktype, int speed) { trackTypeSpeeds.put(tracktype, speed); } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonPriorityParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonPriorityParser.java index add8fa9489d..a9dd508633e 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonPriorityParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikeCommonPriorityParser.java @@ -3,69 +3,46 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.RouteNetwork; +import com.graphhopper.routing.ev.MaxSpeed; import com.graphhopper.routing.util.FerrySpeedCalculator; import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.storage.IntsRef; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; -import static com.graphhopper.routing.ev.RouteNetwork.*; import static com.graphhopper.routing.util.PriorityCode.*; import static com.graphhopper.routing.util.parsers.AbstractAccessParser.INTENDED; -import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.getMaxSpeed; -import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.isValidSpeed; public abstract class BikeCommonPriorityParser implements TagParser { + private static final Set CYCLEWAY_KEYS = Set.of("cycleway", "cycleway:left", "cycleway:both", "cycleway:right"); - // Bicycle tracks subject to compulsory use in Germany and Poland (https://wiki.openstreetmap.org/wiki/DE:Key:cycleway) - private static final List CYCLEWAY_ACCESS_KEYS = Arrays.asList("cycleway:bicycle", "cycleway:both:bicycle", "cycleway:left:bicycle", "cycleway:right:bicycle"); + // rare use case when a bicycle lane has access tag + private static final List CYCLEWAY_BICYCLE_KEYS = List.of("cycleway:bicycle", "cycleway:both:bicycle", "cycleway:left:bicycle", "cycleway:right:bicycle"); - // Pushing section highways are parts where you need to get off your bike and push it (German: Schiebestrecke) + // pushing section highways are parts where you need to get off your bike and push it protected final HashSet pushingSectionsHighways = new HashSet<>(); protected final Set preferHighwayTags = new HashSet<>(); protected final Map avoidHighwayTags = new HashMap<>(); - protected final Set unpavedSurfaceTags = new HashSet<>(); - protected final Set intendedValues = new HashSet<>(INTENDED); - protected final DecimalEncodedValue avgSpeedEnc; protected final DecimalEncodedValue priorityEnc; // Car speed limit which switches the preference from UNCHANGED to AVOID_IF_POSSIBLE int avoidSpeedLimit; - EnumEncodedValue bikeRouteEnc; - Map routeMap = new HashMap<>(); + protected final Set goodSurface = Set.of("paved", "asphalt", "concrete"); // This is the specific bicycle class private String classBicycleKey; - protected BikeCommonPriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue avgSpeedEnc, - EnumEncodedValue bikeRouteEnc) { - this.bikeRouteEnc = bikeRouteEnc; + protected BikeCommonPriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue avgSpeedEnc) { this.priorityEnc = priorityEnc; this.avgSpeedEnc = avgSpeedEnc; - // duplicate code as also in BikeCommonAverageSpeedParser addPushingSection("footway"); addPushingSection("pedestrian"); addPushingSection("steps"); addPushingSection("platform"); - unpavedSurfaceTags.add("unpaved"); - unpavedSurfaceTags.add("gravel"); - unpavedSurfaceTags.add("ground"); - unpavedSurfaceTags.add("dirt"); - unpavedSurfaceTags.add("grass"); - unpavedSurfaceTags.add("compacted"); - unpavedSurfaceTags.add("earth"); - unpavedSurfaceTags.add("fine_gravel"); - unpavedSurfaceTags.add("grass_paver"); - unpavedSurfaceTags.add("ice"); - unpavedSurfaceTags.add("mud"); - unpavedSurfaceTags.add("salt"); - unpavedSurfaceTags.add("sand"); - unpavedSurfaceTags.add("wood"); - avoidHighwayTags.put("motorway", REACH_DESTINATION); avoidHighwayTags.put("motorway_link", REACH_DESTINATION); avoidHighwayTags.put("trunk", REACH_DESTINATION); @@ -76,171 +53,139 @@ protected BikeCommonPriorityParser(DecimalEncodedValue priorityEnc, DecimalEncod avoidHighwayTags.put("secondary_link", AVOID); avoidHighwayTags.put("bridleway", AVOID); - routeMap.put(INTERNATIONAL, BEST.getValue()); - routeMap.put(NATIONAL, BEST.getValue()); - routeMap.put(REGIONAL, VERY_NICE.getValue()); - routeMap.put(LOCAL, PREFER.getValue()); - avoidSpeedLimit = 71; } @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { String highwayValue = way.getTag("highway"); - Integer priorityFromRelation = routeMap.get(bikeRouteEnc.getEnum(false, edgeId, edgeIntAccess)); - if (highwayValue == null) { - if (FerrySpeedCalculator.isFerry(way)) { - priorityFromRelation = SLIGHT_AVOID.getValue(); - } else { - return; - } - } - - double maxSpeed = Math.max(avgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc.getDecimal(true, edgeId, edgeIntAccess)); - priorityEnc.setDecimal(false, edgeId, edgeIntAccess, PriorityCode.getValue(handlePriority(way, maxSpeed, priorityFromRelation))); - } + if (highwayValue == null && !FerrySpeedCalculator.isFerry(way)) return; - /** - * In this method we prefer cycleways or roads with designated bike access and avoid big roads - * or roads with trams or pedestrian. - * - * @return new priority based on priorityFromRelation and on the tags in ReaderWay. - */ - int handlePriority(ReaderWay way, double wayTypeSpeed, Integer priorityFromRelation) { TreeMap weightToPrioMap = new TreeMap<>(); - if (priorityFromRelation == null) - weightToPrioMap.put(0d, UNCHANGED); - else - weightToPrioMap.put(110d, PriorityCode.valueOf(priorityFromRelation)); - - collect(way, wayTypeSpeed, weightToPrioMap); - - // pick priority with biggest order value - return weightToPrioMap.lastEntry().getValue().getValue(); + weightToPrioMap.put(0d, UNCHANGED); + double maxSpeed = Math.max(avgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), + avgSpeedEnc.getDecimal(true, edgeId, edgeIntAccess)); + collect(way, maxSpeed, isBikeDesignated(way), weightToPrioMap); + + // pick priority with the biggest order value + double prio = PriorityCode.getValue(weightToPrioMap.lastEntry().getValue().getValue()); + priorityEnc.setDecimal(false, edgeId, edgeIntAccess, prio); } // Conversion of class value to priority. See http://wiki.openstreetmap.org/wiki/Class:bicycle private PriorityCode convertClassValueToPriority(String tagvalue) { - int classvalue; try { - classvalue = Integer.parseInt(tagvalue); + return switch (Integer.parseInt(tagvalue)) { + case 3 -> BEST; + case 2 -> VERY_NICE; + case 1 -> PREFER; + case -1 -> AVOID; + case -2 -> BAD; + case -3 -> REACH_DESTINATION; + default -> UNCHANGED; + }; } catch (NumberFormatException e) { return UNCHANGED; } - - switch (classvalue) { - case 3: - return BEST; - case 2: - return VERY_NICE; - case 1: - return PREFER; - case -1: - return SLIGHT_AVOID; - case -2: - return AVOID; - case -3: - return AVOID_MORE; - default: - return UNCHANGED; - } } /** * @param weightToPrioMap associate a weight with every priority. This sorted map allows * subclasses to 'insert' more important priorities as well as overwrite determined priorities. */ - void collect(ReaderWay way, double wayTypeSpeed, TreeMap weightToPrioMap) { + void collect(ReaderWay way, double wayTypeSpeed, boolean bikeDesignated, TreeMap weightToPrioMap) { String highway = way.getTag("highway"); - if (isDesignated(way)) { - if ("path".equals(highway)) + if (bikeDesignated) { + boolean isGoodSurface = way.getTag("tracktype", "").equals("grade1") || goodSurface.contains(way.getTag("surface", "")); + if ("path".equals(highway) || "track".equals(highway) && isGoodSurface) weightToPrioMap.put(100d, VERY_NICE); else weightToPrioMap.put(100d, PREFER); } if ("cycleway".equals(highway)) { - if (way.hasTag("foot", intendedValues) && !way.hasTag("segregated", "yes")) + if (way.hasTag("foot", INTENDED) && !way.hasTag("segregated", "yes")) weightToPrioMap.put(100d, PREFER); else weightToPrioMap.put(100d, VERY_NICE); } - double maxSpeed = Math.max(getMaxSpeed(way, false), getMaxSpeed(way, true)); - if (preferHighwayTags.contains(highway) || (isValidSpeed(maxSpeed) && maxSpeed <= 30)) { - if (!isValidSpeed(maxSpeed) || maxSpeed < avoidSpeedLimit) { + double maxSpeed = Math.max(OSMMaxSpeedParser.parseMaxSpeed(way, false), OSMMaxSpeedParser.parseMaxSpeed(way, true)); + if (preferHighwayTags.contains(highway) || maxSpeed <= 30) { + if (maxSpeed == MaxSpeed.MAXSPEED_MISSING || maxSpeed < avoidSpeedLimit) { weightToPrioMap.put(40d, PREFER); - if (way.hasTag("tunnel", intendedValues)) + if (way.hasTag("tunnel", INTENDED)) weightToPrioMap.put(40d, UNCHANGED); } } else if (avoidHighwayTags.containsKey(highway) - || isValidSpeed(maxSpeed) && maxSpeed >= avoidSpeedLimit && !"track".equals(highway)) { + || (maxSpeed != MaxSpeed.MAXSPEED_MISSING && maxSpeed >= avoidSpeedLimit && !"track".equals(highway))) { PriorityCode priorityCode = avoidHighwayTags.get(highway); weightToPrioMap.put(50d, priorityCode == null ? AVOID : priorityCode); - if (way.hasTag("tunnel", intendedValues)) { + if (way.hasTag("tunnel", INTENDED)) { PriorityCode worse = priorityCode == null ? BAD : priorityCode.worse().worse(); weightToPrioMap.put(50d, worse == EXCLUDE ? REACH_DESTINATION : worse); } } - String cycleway = way.getFirstPriorityTag(Arrays.asList("cycleway", "cycleway:left", "cycleway:right", "cycleway:both")); - if (Arrays.asList("lane", "opposite_track", "shared_lane", "share_busway", "shoulder").contains(cycleway)) { - weightToPrioMap.put(100d, SLIGHT_PREFER); - } else if ("track".equals(cycleway)) { - weightToPrioMap.put(100d, PREFER); - } - if (way.hasTag("bicycle", "use_sidepath")) { weightToPrioMap.put(100d, REACH_DESTINATION); + } else if (way.hasTag("bicycle", "optional_sidepath")) { + weightToPrioMap.put(100d, AVOID); } - if (pushingSectionsHighways.contains(highway) || "parking_aisle".equals(way.getTag("service"))) { + Set cyclewayValues = Stream.of("cycleway", "cycleway:left", "cycleway:both", "cycleway:right").map(key -> way.getTag(key, "")).collect(Collectors.toSet()); + if (cyclewayValues.contains("track")) { + weightToPrioMap.put(100d, PREFER); + } else if (Stream.of("lane", "opposite_track", "shared_lane", "share_busway", "shoulder").anyMatch(cyclewayValues::contains)) { + weightToPrioMap.put(100d, SLIGHT_PREFER); + } else if (pushingSectionsHighways.contains(highway) || "parking_aisle".equals(way.getTag("service"))) { PriorityCode pushingSectionPrio = SLIGHT_AVOID; - if (way.hasTag("bicycle", "yes") || way.hasTag("bicycle", "permissive")) + if (way.hasTag("highway", "steps")) + pushingSectionPrio = BAD; + else if (way.hasTag("bicycle", "yes") || way.hasTag("bicycle", "permissive")) pushingSectionPrio = PREFER; - if (isDesignated(way) && (!way.hasTag("highway", "steps"))) + else if (bikeDesignated) pushingSectionPrio = VERY_NICE; - if (way.hasTag("foot", "yes")) { + + if (way.hasTag("foot", "yes") && !way.hasTag("segregated", "yes")) pushingSectionPrio = pushingSectionPrio.worse(); - if (way.hasTag("segregated", "yes")) - pushingSectionPrio = pushingSectionPrio.better(); - } - if (way.hasTag("highway", "steps")) { - pushingSectionPrio = BAD; - } + weightToPrioMap.put(100d, pushingSectionPrio); } if (way.hasTag("railway", "tram")) weightToPrioMap.put(50d, AVOID_MORE); - if (way.hasTag("lcn", "yes")) - weightToPrioMap.put(100d, PREFER); - String classBicycleValue = way.getTag(classBicycleKey); + if (classBicycleValue == null) classBicycleValue = way.getTag("class:bicycle"); + + // We assume that humans are better in classifying preferences compared to our algorithm above if (classBicycleValue != null) { - // We assume that humans are better in classifying preferences compared to our algorithm above -> weight = 100 - weightToPrioMap.put(100d, convertClassValueToPriority(classBicycleValue)); - } else { - String classBicycle = way.getTag("class:bicycle"); - if (classBicycle != null) - weightToPrioMap.put(100d, convertClassValueToPriority(classBicycle)); + PriorityCode prio = convertClassValueToPriority(classBicycleValue); + // do not overwrite if e.g. designated + weightToPrioMap.compute(100d, (key, existing) -> + existing == null || existing.getValue() < prio.getValue() ? prio : existing + ); } - // Increase the priority for scenic routes or in case that maxspeed limits our average speed as compensation. See #630 - if (way.hasTag("scenic", "yes") || maxSpeed > 0 && maxSpeed <= wayTypeSpeed) { + // Increase priority in case that maxspeed limits our average speed as compensation. See #630 + if (maxSpeed > 0 && maxSpeed <= wayTypeSpeed) { PriorityCode lastEntryValue = weightToPrioMap.lastEntry().getValue(); if (lastEntryValue.getValue() < BEST.getValue()) weightToPrioMap.put(110d, lastEntryValue.better()); } } - boolean isDesignated(ReaderWay way) { - return way.hasTag("bicycle", "designated") || way.hasTag(CYCLEWAY_ACCESS_KEYS, "designated") - || way.hasTag("bicycle_road", "yes") || way.hasTag("cyclestreet", "yes") || way.hasTag("bicycle", "official"); + static boolean isBikeDesignated(ReaderWay way) { + return way.hasTag("bicycle", "designated") + || way.hasTag("bicycle", "official") + || way.hasTag("segregated", "yes") + || way.hasTag("bicycle_road", "yes") + || way.hasTag("cyclestreet", "yes") + || CYCLEWAY_KEYS.stream().anyMatch(k -> way.getTag(k, "").equals("track")) + || way.hasTag(CYCLEWAY_BICYCLE_KEYS, "designated"); } - // TODO duplicated in average speed void addPushingSection(String highway) { pushingSectionsHighways.add(highway); } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/BikePriorityParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/BikePriorityParser.java index 3c8266e323e..7d1b1a13084 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/BikePriorityParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/BikePriorityParser.java @@ -1,25 +1,23 @@ package com.graphhopper.routing.util.parsers; -import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.EncodedValueLookup; +import com.graphhopper.routing.ev.VehiclePriority; +import com.graphhopper.routing.ev.VehicleSpeed; public class BikePriorityParser extends BikeCommonPriorityParser { public BikePriorityParser(EncodedValueLookup lookup) { - this( - lookup.getDecimalEncodedValue(VehiclePriority.key("bike")), - lookup.getDecimalEncodedValue(VehicleSpeed.key("bike")), - lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class) - ); + this(lookup.getDecimalEncodedValue(VehiclePriority.key("bike")), + lookup.getDecimalEncodedValue(VehicleSpeed.key("bike"))); } - public BikePriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc, EnumEncodedValue bikeRouteEnc) { - super(priorityEnc, speedEnc, bikeRouteEnc); + public BikePriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc) { + super(priorityEnc, speedEnc); addPushingSection("path"); preferHighwayTags.add("service"); - preferHighwayTags.add("tertiary"); - preferHighwayTags.add("tertiary_link"); preferHighwayTags.add("residential"); preferHighwayTags.add("unclassified"); diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java index 7bf053d7990..d2be4b40154 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAccessParser.java @@ -26,6 +26,8 @@ import java.util.*; +import static com.graphhopper.routing.util.parsers.OSMTemporalAccessParser.hasPermissiveTemporalRestriction; + public class CarAccessParser extends AbstractAccessParser implements TagParser { protected final Set trackTypeValues = new HashSet<>(); @@ -37,25 +39,20 @@ public CarAccessParser(EncodedValueLookup lookup, PMap properties) { lookup.getBooleanEncodedValue(VehicleAccess.key("car")), lookup.getBooleanEncodedValue(Roundabout.KEY), properties, - TransportationMode.CAR + OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR) ); } public CarAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc, PMap properties, - TransportationMode transportationMode) { - super(accessEnc, transportationMode); + List restrictionsKeys) { + super(accessEnc, restrictionsKeys); this.roundaboutEnc = roundaboutEnc; restrictedValues.add("agricultural"); restrictedValues.add("forestry"); restrictedValues.add("delivery"); blockPrivate(properties.getBool("block_private", true)); - blockFords(properties.getBool("block_fords", false)); - - intendedValues.add("yes"); - intendedValues.add("designated"); - intendedValues.add("permissive"); barriers.add("kissing_gate"); barriers.add("fence"); @@ -71,7 +68,7 @@ public CarAccessParser(BooleanEncodedValue accessEnc, highwayValues.addAll(Arrays.asList("motorway", "motorway_link", "trunk", "trunk_link", "primary", "primary_link", "secondary", "secondary_link", "tertiary", "tertiary_link", - "unclassified", "residential", "living_street", "service", "road", "track")); + "unclassified", "residential", "living_street", "service", "road", "track", "pedestrian")); trackTypeValues.addAll(Arrays.asList("grade1", "grade2", "grade3", null)); } @@ -79,21 +76,29 @@ public CarAccessParser(BooleanEncodedValue accessEnc, public WayAccess getAccess(ReaderWay way) { // TODO: Ferries have conditionals, like opening hours or are closed during some time in the year String highwayValue = way.getTag("highway"); - String firstValue = way.getFirstPriorityTag(restrictions); + int firstIndex = way.getFirstIndex(restrictionKeys); + String firstValue = firstIndex < 0 ? "" : way.getTag(restrictionKeys.get(firstIndex), ""); if (highwayValue == null) { if (FerrySpeedCalculator.isFerry(way)) { - if (restrictedValues.contains(firstValue)) - return WayAccess.CAN_SKIP; - if (intendedValues.contains(firstValue) || + if (allowedValues.contains(firstValue) || // implied default is allowed only if foot and bicycle is not specified: firstValue.isEmpty() && !way.hasTag("foot") && !way.hasTag("bicycle") || // if hgv is allowed then smaller trucks and cars are allowed too way.hasTag("hgv", "yes")) return WayAccess.FERRY; + if (restrictedValues.contains(firstValue)) + return WayAccess.CAN_SKIP; } return WayAccess.CAN_SKIP; } + if ("pedestrian".equals(highwayValue) + && !allowedValues.contains(firstValue) + && !hasPermissiveTemporalRestriction(way, restrictionKeys.size() - 1, restrictionKeys, allowedValues)) { + // allow pedestrian if explicitly tagged + return WayAccess.CAN_SKIP; + } + if ("service".equals(highwayValue) && "emergency_access".equals(way.getTag("service"))) return WayAccess.CAN_SKIP; @@ -103,28 +108,24 @@ public WayAccess getAccess(ReaderWay way) { if (!highwayValues.contains(highwayValue)) return WayAccess.CAN_SKIP; + // this is a very rare tagging which we should/could remove (the status key itself is described as "vague") if (way.hasTag("impassable", "yes") || way.hasTag("status", "impassable")) return WayAccess.CAN_SKIP; // multiple restrictions needs special handling - boolean permittedWayConditionallyRestricted = getConditionalTagInspector().isPermittedWayConditionallyRestricted(way); - boolean restrictedWayConditionallyPermitted = getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way); - if (!firstValue.isEmpty()) { + if (firstIndex >= 0) { String[] restrict = firstValue.split(";"); + // if any of the values allows access then return early (regardless of the order) for (String value : restrict) { - if (restrictedValues.contains(value) && !restrictedWayConditionallyPermitted) - return WayAccess.CAN_SKIP; - if (intendedValues.contains(value) && !permittedWayConditionallyRestricted) + if (allowedValues.contains(value)) return WayAccess.WAY; } + for (String value : restrict) { + if (restrictedValues.contains(value) && !hasPermissiveTemporalRestriction(way, firstIndex, restrictionKeys, allowedValues)) + return WayAccess.CAN_SKIP; + } } - if (isBlockFords() && ("ford".equals(highwayValue) || way.hasTag("ford"))) - return WayAccess.CAN_SKIP; - - if (permittedWayConditionallyRestricted) - return WayAccess.CAN_SKIP; - return WayAccess.WAY; } @@ -134,18 +135,12 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way if (access.canSkip()) return; - if (!access.isFerry()) { - boolean isRoundabout = roundaboutEnc.getBool(false, edgeId, edgeIntAccess); - if (isOneway(way) || isRoundabout) { - if (isForwardOneway(way)) - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - if (isBackwardOneway(way)) - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - } else { + boolean isRoundabout = roundaboutEnc.getBool(false, edgeId, edgeIntAccess); + if (isOneway(way) || isRoundabout) { + if (isForwardOneway(way)) accessEnc.setBool(false, edgeId, edgeIntAccess, true); + if (isBackwardOneway(way)) accessEnc.setBool(true, edgeId, edgeIntAccess, true); - } - } else { accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -176,7 +171,7 @@ protected boolean isForwardOneway(ReaderWay way) { } protected boolean isOneway(ReaderWay way) { - return way.hasTag("oneway", oneways) + return way.hasTag("oneway", ONEWAYS) || way.hasTag("vehicle:backward", restrictedValues) || way.hasTag("vehicle:forward", restrictedValues) || way.hasTag("motor_vehicle:backward", restrictedValues) diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAverageSpeedParser.java index 4ef43ed30ea..528ca425a4d 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/CarAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/CarAverageSpeedParser.java @@ -42,12 +42,11 @@ public class CarAverageSpeedParser extends AbstractAverageSpeedParser implements protected final Map defaultSpeedMap = new HashMap<>(); public CarAverageSpeedParser(EncodedValueLookup lookup) { - this(lookup.getDecimalEncodedValue(VehicleSpeed.key("car")), - lookup.getDecimalEncodedValue(FerrySpeed.KEY)); + this(lookup.getDecimalEncodedValue(VehicleSpeed.key("car"))); } - public CarAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeed) { - super(speedEnc, ferrySpeed); + public CarAverageSpeedParser(DecimalEncodedValue speedEnc) { + super(speedEnc); badSurfaceSpeedMap.add("cobblestone"); badSurfaceSpeedMap.add("unhewn_cobblestone"); @@ -83,8 +82,8 @@ public CarAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue f defaultSpeedMap.put("tertiary_link", 40); defaultSpeedMap.put("unclassified", 30); defaultSpeedMap.put("residential", 30); - // spielstraße - defaultSpeedMap.put("living_street", 5); + defaultSpeedMap.put("living_street", 6); + defaultSpeedMap.put("pedestrian", 6); defaultSpeedMap.put("service", 20); // unknown road defaultSpeedMap.put("road", 20); @@ -121,13 +120,8 @@ protected double getSpeed(ReaderWay way) { @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { - if (FerrySpeedCalculator.isFerry(way)) { - double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc); - setSpeed(false, edgeId, edgeIntAccess, ferrySpeed); - if (avgSpeedEnc.isStoreTwoDirections()) - setSpeed(true, edgeId, edgeIntAccess, ferrySpeed); + if (FerrySpeedCalculator.isFerry(way)) return; - } // get assumed speed from highway type double speed = getSpeed(way); @@ -143,8 +137,8 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way * @return The assumed speed. */ protected double applyMaxSpeed(ReaderWay way, double speed, boolean bwd) { - double maxSpeed = getMaxSpeed(way, bwd); - return Math.min(140, isValidSpeed(maxSpeed) ? Math.max(1, maxSpeed * 0.9) : speed); + double maxSpeed = OSMMaxSpeedParser.parseMaxSpeed(way, bwd); + return maxSpeed != MaxSpeed.MAXSPEED_MISSING ? Math.max(1, maxSpeed * 0.9) : speed; } /** @@ -154,7 +148,7 @@ protected double applyMaxSpeed(ReaderWay way, double speed, boolean bwd) { */ protected double applyBadSurfaceSpeed(ReaderWay way, double speed) { // limit speed if bad surface - if (badSurfaceSpeed > 0 && isValidSpeed(speed) && speed > badSurfaceSpeed) { + if (badSurfaceSpeed > 0 && speed > badSurfaceSpeed) { String surface = way.getTag("surface", ""); int colonIndex = surface.indexOf(":"); if (colonIndex != -1) diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultMaxSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultMaxSpeedParser.java index a143b758cd8..da9bd64528f 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultMaxSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultMaxSpeedParser.java @@ -1,10 +1,7 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.Country; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.State; +import com.graphhopper.routing.ev.*; import com.graphhopper.storage.IntsRef; import de.westnordost.osm_legal_default_speeds.LegalDefaultSpeeds; @@ -13,9 +10,8 @@ import java.util.LinkedHashMap; import java.util.Map; -import static com.graphhopper.routing.ev.MaxSpeed.UNLIMITED_SIGN_SPEED; -import static com.graphhopper.routing.ev.MaxSpeed.UNSET_SPEED; -import static com.graphhopper.routing.util.parsers.helpers.OSMValueExtractor.stringToKmh; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_150; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_MISSING; public class DefaultMaxSpeedParser implements TagParser { private final LegalDefaultSpeeds speeds; @@ -37,9 +33,9 @@ public void init(DecimalEncodedValue ruralMaxSpeedEnc, DecimalEncodedValue urban public void handleWayTags(int edgeId, EdgeIntAccess _ignoreAccess, ReaderWay way, IntsRef relationFlags) { if (externalAccess == null) throw new IllegalArgumentException("Call init before using " + getClass().getName()); - double maxSpeed = stringToKmh(way.getTag("maxspeed")); + double maxSpeed = Math.max(OSMMaxSpeedParser.parseMaxSpeed(way, false), OSMMaxSpeedParser.parseMaxSpeed(way, true)); Integer ruralSpeedInt = null, urbanSpeedInt = null; - if (Double.isNaN(maxSpeed)) { + if (maxSpeed == MAXSPEED_MISSING) { Country country = way.getTag("country", Country.MISSING); State state = way.getTag("country_state", State.MISSING); if (country != Country.MISSING) { @@ -56,7 +52,7 @@ public void handleWayTags(int edgeId, EdgeIntAccess _ignoreAccess, ReaderWay way if (tmpResult != null) { internRes.rural = parseInt(tmpResult.getTags().get("maxspeed")); if (internRes.rural == null && "130".equals(tmpResult.getTags().get("maxspeed:advisory"))) - internRes.rural = (int) UNLIMITED_SIGN_SPEED; + internRes.rural = (int) MAXSPEED_150; } tmpResult = speeds.getSpeedLimits(code, @@ -64,7 +60,7 @@ public void handleWayTags(int edgeId, EdgeIntAccess _ignoreAccess, ReaderWay way if (tmpResult != null) { internRes.urban = parseInt(tmpResult.getTags().get("maxspeed")); if (internRes.urban == null && "130".equals(tmpResult.getTags().get("maxspeed:advisory"))) - internRes.urban = (int) UNLIMITED_SIGN_SPEED; + internRes.urban = (int) MAXSPEED_150; } return internRes; }); @@ -74,8 +70,8 @@ public void handleWayTags(int edgeId, EdgeIntAccess _ignoreAccess, ReaderWay way } } - urbanMaxSpeedEnc.setDecimal(false, edgeId, externalAccess, urbanSpeedInt == null ? UNSET_SPEED : urbanSpeedInt); - ruralMaxSpeedEnc.setDecimal(false, edgeId, externalAccess, ruralSpeedInt == null ? UNSET_SPEED : ruralSpeedInt); + urbanMaxSpeedEnc.setDecimal(false, edgeId, externalAccess, urbanSpeedInt == null ? MAXSPEED_MISSING : urbanSpeedInt); + ruralMaxSpeedEnc.setDecimal(false, edgeId, externalAccess, ruralSpeedInt == null ? MAXSPEED_MISSING : ruralSpeedInt); } private Map filter(Map tags) { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java deleted file mode 100644 index 3816d9b4f39..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/DefaultTagParserFactory.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.routing.ev.*; -import com.graphhopper.routing.util.FerrySpeedCalculator; -import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.util.PMap; - -public class DefaultTagParserFactory implements TagParserFactory { - - @Override - public TagParser create(EncodedValueLookup lookup, String name, PMap properties) { - if (Roundabout.KEY.equals(name)) - return new OSMRoundaboutParser(lookup.getBooleanEncodedValue(Roundabout.KEY)); - else if (name.equals(RoadClass.KEY)) - return new OSMRoadClassParser(lookup.getEnumEncodedValue(RoadClass.KEY, RoadClass.class)); - else if (name.equals(RoadClassLink.KEY)) - return new OSMRoadClassLinkParser(lookup.getBooleanEncodedValue(RoadClassLink.KEY)); - else if (name.equals(RoadEnvironment.KEY)) - return new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)); - else if (name.equals(RoadAccess.KEY)) - return new OSMRoadAccessParser(lookup.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class), OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)); - else if (name.equals(MaxSpeed.KEY)) - return new OSMMaxSpeedParser(lookup.getDecimalEncodedValue(MaxSpeed.KEY)); - else if (name.equals(MaxWeight.KEY)) - return new OSMMaxWeightParser(lookup.getDecimalEncodedValue(MaxWeight.KEY)); - else if (name.equals(MaxWeightExcept.KEY)) - return new MaxWeightExceptParser(lookup.getEnumEncodedValue(MaxWeightExcept.KEY, MaxWeightExcept.class)); - else if (name.equals(MaxHeight.KEY)) - return new OSMMaxHeightParser(lookup.getDecimalEncodedValue(MaxHeight.KEY)); - else if (name.equals(MaxWidth.KEY)) - return new OSMMaxWidthParser(lookup.getDecimalEncodedValue(MaxWidth.KEY)); - else if (name.equals(MaxAxleLoad.KEY)) - return new OSMMaxAxleLoadParser(lookup.getDecimalEncodedValue(MaxAxleLoad.KEY)); - else if (name.equals(MaxLength.KEY)) - return new OSMMaxLengthParser(lookup.getDecimalEncodedValue(MaxLength.KEY)); - else if (name.equals(Surface.KEY)) - return new OSMSurfaceParser(lookup.getEnumEncodedValue(Surface.KEY, Surface.class)); - else if (name.equals(Smoothness.KEY)) - return new OSMSmoothnessParser(lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class)); - else if (name.equals(Toll.KEY)) - return new OSMTollParser(lookup.getEnumEncodedValue(Toll.KEY, Toll.class)); - else if (name.equals(TrackType.KEY)) - return new OSMTrackTypeParser(lookup.getEnumEncodedValue(TrackType.KEY, TrackType.class)); - else if (name.equals(Hgv.KEY)) - return new OSMHgvParser(lookup.getEnumEncodedValue(Hgv.KEY, Hgv.class)); - else if (name.equals(Hazmat.KEY)) - return new OSMHazmatParser(lookup.getEnumEncodedValue(Hazmat.KEY, Hazmat.class)); - else if (name.equals(HazmatTunnel.KEY)) - return new OSMHazmatTunnelParser(lookup.getEnumEncodedValue(HazmatTunnel.KEY, HazmatTunnel.class)); - else if (name.equals(HazmatWater.KEY)) - return new OSMHazmatWaterParser(lookup.getEnumEncodedValue(HazmatWater.KEY, HazmatWater.class)); - else if (name.equals(Lanes.KEY)) - return new OSMLanesParser(lookup.getIntEncodedValue(Lanes.KEY)); - else if (name.equals(OSMWayID.KEY)) - return new OSMWayIDParser(lookup.getIntEncodedValue(OSMWayID.KEY)); - else if (name.equals(MtbRating.KEY)) - return new OSMMtbRatingParser(lookup.getIntEncodedValue(MtbRating.KEY)); - else if (name.equals(HikeRating.KEY)) - return new OSMHikeRatingParser(lookup.getIntEncodedValue(HikeRating.KEY)); - else if (name.equals(HorseRating.KEY)) - return new OSMHorseRatingParser(lookup.getIntEncodedValue(HorseRating.KEY)); - else if (name.equals(Footway.KEY)) - return new OSMFootwayParser(lookup.getEnumEncodedValue(Footway.KEY, Footway.class)); - else if (name.equals(Country.KEY)) - return new CountryParser(lookup.getEnumEncodedValue(Country.KEY, Country.class)); - else if (name.equals(State.KEY)) - return new StateParser(lookup.getEnumEncodedValue(State.KEY, State.class)); - else if (name.equals(Crossing.KEY)) - return new OSMCrossingParser(lookup.getEnumEncodedValue(Crossing.KEY, Crossing.class)); - else if (name.equals(FerrySpeed.KEY)) - return new FerrySpeedCalculator(lookup.getDecimalEncodedValue(FerrySpeed.KEY)); - else if (name.equals(BusAccess.KEY)) - return new ModeAccessParser(TransportationMode.BUS, lookup.getBooleanEncodedValue(BusAccess.KEY), lookup.getBooleanEncodedValue(Roundabout.KEY)); - else if (name.equals(Hov.KEY)) - return new OSMHovParser(lookup.getEnumEncodedValue(Hov.KEY, Hov.class)); - return null; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java index e2b87b8ac3a..091762017e0 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAccessParser.java @@ -28,27 +28,21 @@ import static com.graphhopper.routing.ev.RouteNetwork.*; import static com.graphhopper.routing.util.PriorityCode.UNCHANGED; +import static com.graphhopper.routing.util.parsers.OSMTemporalAccessParser.hasPermissiveTemporalRestriction; public class FootAccessParser extends AbstractAccessParser implements TagParser { final Set allowedHighwayTags = new HashSet<>(); - final Set allowedSacScale = new HashSet<>(); protected HashSet sidewalkValues = new HashSet<>(5); protected Map routeMap = new HashMap<>(); public FootAccessParser(EncodedValueLookup lookup, PMap properties) { this(lookup.getBooleanEncodedValue(VehicleAccess.key("foot"))); blockPrivate(properties.getBool("block_private", true)); - blockFords(properties.getBool("block_fords", false)); } protected FootAccessParser(BooleanEncodedValue accessEnc) { - super(accessEnc, TransportationMode.FOOT); - - intendedValues.add("yes"); - intendedValues.add("designated"); - intendedValues.add("official"); - intendedValues.add("permissive"); + super(accessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.FOOT)); sidewalkValues.add("yes"); sidewalkValues.add("both"); @@ -77,17 +71,12 @@ protected FootAccessParser(BooleanEncodedValue accessEnc) { allowedHighwayTags.add("cycleway"); allowedHighwayTags.add("unclassified"); allowedHighwayTags.add("road"); - // disallowed in some countries - //allowedHighwayTags.add("bridleway"); + allowedHighwayTags.add("bridleway"); routeMap.put(INTERNATIONAL, UNCHANGED.getValue()); routeMap.put(NATIONAL, UNCHANGED.getValue()); routeMap.put(REGIONAL, UNCHANGED.getValue()); routeMap.put(LOCAL, UNCHANGED.getValue()); - - allowedSacScale.add("hiking"); - allowedSacScale.add("mountain_hiking"); - allowedSacScale.add("demanding_mountain_hiking"); } /** @@ -100,7 +89,7 @@ public WayAccess getAccess(ReaderWay way) { if (FerrySpeedCalculator.isFerry(way)) { String footTag = way.getTag("foot"); - if (footTag == null || intendedValues.contains(footTag)) + if (footTag == null || allowedValues.contains(footTag)) acceptPotentially = WayAccess.FERRY; } @@ -112,7 +101,7 @@ public WayAccess getAccess(ReaderWay way) { acceptPotentially = WayAccess.WAY; if (!acceptPotentially.canSkip()) { - if (way.hasTag(restrictions, restrictedValues) && !getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way)) + if (way.hasTag(restrictionKeys, restrictedValues)) return WayAccess.CAN_SKIP; return acceptPotentially; } @@ -120,21 +109,23 @@ public WayAccess getAccess(ReaderWay way) { return WayAccess.CAN_SKIP; } - // other scales are too dangerous, see http://wiki.openstreetmap.org/wiki/Key:sac_scale - if (way.getTag("sac_scale") != null && !way.hasTag("sac_scale", allowedSacScale)) + // via_ferrata is too dangerous, see #1326 + if ("via_ferrata".equals(highwayValue)) return WayAccess.CAN_SKIP; - boolean permittedWayConditionallyRestricted = getConditionalTagInspector().isPermittedWayConditionallyRestricted(way); - boolean restrictedWayConditionallyPermitted = getConditionalTagInspector().isRestrictedWayConditionallyPermitted(way); - String firstValue = way.getFirstPriorityTag(restrictions); - if (!firstValue.isEmpty()) { + int firstIndex = way.getFirstIndex(restrictionKeys); + if (firstIndex >= 0) { + String firstValue = way.getTag(restrictionKeys.get(firstIndex), ""); String[] restrict = firstValue.split(";"); + // if any of the values allows access then return early (regardless of the order) for (String value : restrict) { - if (restrictedValues.contains(value) && !restrictedWayConditionallyPermitted) - return WayAccess.CAN_SKIP; - if (intendedValues.contains(value) && !permittedWayConditionallyRestricted) + if (allowedValues.contains(value)) return WayAccess.WAY; } + for (String value : restrict) { + if (restrictedValues.contains(value) && !hasPermissiveTemporalRestriction(way, firstIndex, restrictionKeys, allowedValues)) + return WayAccess.CAN_SKIP; + } } if (way.hasTag("sidewalk", sidewalkValues)) @@ -146,12 +137,6 @@ public WayAccess getAccess(ReaderWay way) { if (way.hasTag("motorroad", "yes")) return WayAccess.CAN_SKIP; - if (isBlockFords() && ("ford".equals(highwayValue) || way.hasTag("ford"))) - return WayAccess.CAN_SKIP; - - if (permittedWayConditionallyRestricted) - return WayAccess.CAN_SKIP; - return WayAccess.WAY; } @@ -161,9 +146,8 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way if (access.canSkip()) return; - if (way.hasTag("oneway:foot", oneways) || way.hasTag("foot:backward") || way.hasTag("foot:forward") - || way.hasTag("oneway", oneways) && way.hasTag("highway", "steps") // outdated mapping style - ) { + if (way.hasTag("oneway:foot", ONEWAYS) || way.hasTag("foot:backward") || way.hasTag("foot:forward") + || way.hasTag("oneway", ONEWAYS) && (way.hasTag("highway", "steps") /* <- outdated mapping style */ || access.isFerry())) { boolean reverse = way.hasTag("oneway:foot", "-1") || way.hasTag("foot:backward", "yes") || way.hasTag("foot:forward", "no"); accessEnc.setBool(reverse, edgeId, edgeIntAccess, true); } else { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java index 0fb1942055d..57b06d893e8 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/FootAverageSpeedParser.java @@ -16,12 +16,11 @@ public class FootAverageSpeedParser extends AbstractAverageSpeedParser implement protected Map routeMap = new HashMap<>(); public FootAverageSpeedParser(EncodedValueLookup lookup) { - this(lookup.getDecimalEncodedValue(VehicleSpeed.key("foot")), - lookup.getDecimalEncodedValue(FerrySpeed.KEY)); + this(lookup.getDecimalEncodedValue(VehicleSpeed.key("foot"))); } - protected FootAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeedEnc) { - super(speedEnc, ferrySpeedEnc); + public FootAverageSpeedParser(DecimalEncodedValue speedEnc) { + super(speedEnc); routeMap.put(INTERNATIONAL, UNCHANGED.getValue()); routeMap.put(NATIONAL, UNCHANGED.getValue()); @@ -33,13 +32,7 @@ protected FootAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedVal public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { String highwayValue = way.getTag("highway"); if (highwayValue == null) { - if (FerrySpeedCalculator.isFerry(way)) { - double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc); - setSpeed(false, edgeId, edgeIntAccess, ferrySpeed); - if (avgSpeedEnc.isStoreTwoDirections()) - setSpeed(true, edgeId, edgeIntAccess, ferrySpeed); - } - if (!way.hasTag("railway", "platform") && !way.hasTag("man_made", "pier")) + if (FerrySpeedCalculator.isFerry(way) || !way.hasTag("railway", "platform") && !way.hasTag("man_made", "pier")) return; } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/FootPriorityParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/FootPriorityParser.java index abf3f840894..c1f8b3ef6b5 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/FootPriorityParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/FootPriorityParser.java @@ -8,30 +8,20 @@ import java.util.*; -import static com.graphhopper.routing.ev.RouteNetwork.*; import static com.graphhopper.routing.util.PriorityCode.*; import static com.graphhopper.routing.util.parsers.AbstractAccessParser.INTENDED; -import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.getMaxSpeed; -import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.isValidSpeed; public class FootPriorityParser implements TagParser { - final Set intendedValues = new HashSet<>(INTENDED); final Set safeHighwayTags = new HashSet<>(); - final Set avoidHighwayTags = new HashSet<>(); - protected HashSet sidewalkValues = new HashSet<>(5); + final Map avoidHighwayTags = new HashMap<>(); protected HashSet sidewalksNoValues = new HashSet<>(5); protected final DecimalEncodedValue priorityWayEncoder; - protected EnumEncodedValue footRouteEnc; - protected Map routeMap = new HashMap<>(); public FootPriorityParser(EncodedValueLookup lookup) { - this(lookup.getDecimalEncodedValue(VehiclePriority.key("foot")), - lookup.getEnumEncodedValue(FootNetwork.KEY, RouteNetwork.class) - ); + this(lookup.getDecimalEncodedValue(VehiclePriority.key("foot"))); } - protected FootPriorityParser(DecimalEncodedValue priorityEnc, EnumEncodedValue footRouteEnc) { - this.footRouteEnc = footRouteEnc; + protected FootPriorityParser(DecimalEncodedValue priorityEnc) { priorityWayEncoder = priorityEnc; sidewalksNoValues.add("no"); @@ -39,11 +29,6 @@ protected FootPriorityParser(DecimalEncodedValue priorityEnc, EnumEncodedValue weightToPrioMap = new TreeMap<>(); + weightToPrioMap.put(0d, UNCHANGED); + collect(way, weightToPrioMap); + + // pick priority with the biggest order value + double priority = PriorityCode.getValue(weightToPrioMap.lastEntry().getValue().getValue()); + if (highwayValue == null) { if (FerrySpeedCalculator.isFerry(way)) - priorityWayEncoder.setDecimal(false, edgeId, edgeIntAccess, PriorityCode.getValue(handlePriority(way, priorityFromRelation))); + priorityWayEncoder.setDecimal(false, edgeId, edgeIntAccess, priority); } else { - priorityWayEncoder.setDecimal(false, edgeId, edgeIntAccess, PriorityCode.getValue(handlePriority(way, priorityFromRelation))); + priorityWayEncoder.setDecimal(false, edgeId, edgeIntAccess, priority); } } - public int handlePriority(ReaderWay way, Integer priorityFromRelation) { - TreeMap weightToPrioMap = new TreeMap<>(); - if (priorityFromRelation == null) - weightToPrioMap.put(0d, UNCHANGED.getValue()); - else - weightToPrioMap.put(110d, priorityFromRelation); - - collect(way, weightToPrioMap); - - // pick priority with biggest order value - return weightToPrioMap.lastEntry().getValue(); - } - /** * @param weightToPrioMap associate a weight with every priority. This sorted map allows * subclasses to 'insert' more important priorities as well as overwrite determined priorities. */ - void collect(ReaderWay way, TreeMap weightToPrioMap) { + void collect(ReaderWay way, TreeMap weightToPrioMap) { String highway = way.getTag("highway"); if (way.hasTag("foot", "designated")) - weightToPrioMap.put(100d, PREFER.getValue()); + weightToPrioMap.put(100d, PREFER); + + if (way.hasTag("foot", "use_sidepath")) { + weightToPrioMap.put(100d, VERY_BAD); // see #3035 + } - double maxSpeed = Math.max(getMaxSpeed(way, false), getMaxSpeed(way, true)); - if (safeHighwayTags.contains(highway) || (isValidSpeed(maxSpeed) && maxSpeed <= 20)) { - weightToPrioMap.put(40d, PREFER.getValue()); - if (way.hasTag("tunnel", intendedValues)) { + double maxSpeed = Math.max(OSMMaxSpeedParser.parseMaxSpeed(way, false), OSMMaxSpeedParser.parseMaxSpeed(way, true)); + if (safeHighwayTags.contains(highway) || maxSpeed <= 20) { + weightToPrioMap.put(40d, PREFER); + if (way.hasTag("tunnel", INTENDED)) { if (way.hasTag("sidewalk", sidewalksNoValues)) - weightToPrioMap.put(40d, AVOID.getValue()); + weightToPrioMap.put(40d, AVOID); else - weightToPrioMap.put(40d, UNCHANGED.getValue()); + weightToPrioMap.put(40d, UNCHANGED); } - } else if ((isValidSpeed(maxSpeed) && maxSpeed > 50) || avoidHighwayTags.contains(highway)) { + } else if ((maxSpeed != MaxSpeed.MAXSPEED_MISSING && maxSpeed > 50) || avoidHighwayTags.containsKey(highway)) { + PriorityCode priorityCode = avoidHighwayTags.get(highway); if (way.hasTag("sidewalk", sidewalksNoValues)) - weightToPrioMap.put(40d, VERY_BAD.getValue()); - else if (!way.hasTag("sidewalk", sidewalkValues)) - weightToPrioMap.put(40d, AVOID.getValue()); + weightToPrioMap.put(40d, priorityCode == null ? BAD : priorityCode); else - weightToPrioMap.put(40d, SLIGHT_AVOID.getValue()); + weightToPrioMap.put(40d, priorityCode == null ? AVOID : priorityCode.better().better()); } else if (way.hasTag("sidewalk", sidewalksNoValues)) - weightToPrioMap.put(40d, AVOID.getValue()); + weightToPrioMap.put(40d, AVOID); if (way.hasTag("bicycle", "official") || way.hasTag("bicycle", "designated")) - weightToPrioMap.put(44d, SLIGHT_AVOID.getValue()); + weightToPrioMap.put(44d, SLIGHT_AVOID); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java index 4631d050062..a3f842df686 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/ModeAccessParser.java @@ -4,56 +4,92 @@ import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.routing.util.FerrySpeedCalculator; -import com.graphhopper.routing.util.TransportationMode; import com.graphhopper.storage.IntsRef; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.graphhopper.routing.util.parsers.OSMTemporalAccessParser.hasPermissiveTemporalRestriction; public class ModeAccessParser implements TagParser { - private final static Set onewaysForward = new HashSet<>(Arrays.asList("yes", "true", "1")); - private final static Set restrictedValues = new HashSet<>(Arrays.asList("no", "restricted", "military", "emergency", "private", "permit")); + private static final Set CAR_BARRIERS = Set.of("kissing_gate", "fence", + "bollard", "stile", "turnstile", "cycle_barrier", "motorcycle_barrier", "block", + "bus_trap", "sump_buster", "jersey_barrier"); + private static final Set INTENDED = Set.of("yes", "designated", "official", "permissive", "private", "permit"); + private static final Set ONEWAYS_FW = Set.of("yes", "true", "1"); + private final Set restrictedValues; private final List restrictionKeys; private final List vehicleForward; private final List vehicleBackward; + private final List ignoreOnewayKeys; private final BooleanEncodedValue accessEnc; private final BooleanEncodedValue roundaboutEnc; + private final boolean skipEmergency; + private final Set barriers; - public ModeAccessParser(TransportationMode mode, BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) { + public ModeAccessParser(List restrictionKeys, BooleanEncodedValue accessEnc, + boolean skipEmergency, BooleanEncodedValue roundaboutEnc, + Set restrictions, Set barriers) { this.accessEnc = accessEnc; this.roundaboutEnc = roundaboutEnc; - restrictionKeys = OSMRoadAccessParser.toOSMRestrictions(mode); + this.restrictionKeys = restrictionKeys; vehicleForward = restrictionKeys.stream().map(r -> r + ":forward").toList(); vehicleBackward = restrictionKeys.stream().map(r -> r + ":backward").toList(); + ignoreOnewayKeys = restrictionKeys.stream().map(r -> "oneway:" + r).toList(); + restrictedValues = restrictions.isEmpty() ? Set.of("no", "restricted", "military", "emergency") : restrictions; + this.barriers = barriers.isEmpty() ? CAR_BARRIERS : barriers; + if (restrictedValues.contains("")) + throw new IllegalArgumentException("restriction values cannot contain empty string"); + this.skipEmergency = skipEmergency; } @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - String firstValue = way.getFirstPriorityTag(restrictionKeys); - if (restrictedValues.contains(firstValue)) + String highwayValue = way.getTag("highway"); + if (skipEmergency && "service".equals(highwayValue) && "emergency_access".equals(way.getTag("service"))) + return; + + int firstIndex = way.getFirstIndex(restrictionKeys); + String firstValue = firstIndex < 0 ? "" : way.getTag(restrictionKeys.get(firstIndex), ""); + if (restrictedValues.contains(firstValue) && !hasPermissiveTemporalRestriction(way, firstIndex, restrictionKeys, INTENDED)) return; if (way.hasTag("gh:barrier_edge") && way.hasTag("node_tags")) { List> nodeTags = way.getTag("node_tags", null); + Map firstNodeTags = nodeTags.get(0); // a barrier edge has the restriction in both nodes and the tags are the same -> get(0) - firstValue = getFirstPriorityNodeTag(nodeTags.get(0), restrictionKeys); - if (restrictedValues.contains(firstValue)) + firstValue = getFirstPriorityNodeTag(firstNodeTags, restrictionKeys); + String barrierValue = firstNodeTags.containsKey("barrier") ? (String) firstNodeTags.get("barrier") : ""; + if (restrictedValues.contains(firstValue) || barriers.contains(barrierValue) + || "yes".equals(firstNodeTags.get("locked")) && !INTENDED.contains(firstValue)) return; } if (FerrySpeedCalculator.isFerry(way)) { - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - } else { - boolean isRoundabout = roundaboutEnc.getBool(false, edgeId, edgeIntAccess); - boolean isBwd = isBackwardOneway(way); - if (isBwd || isRoundabout || isForwardOneway(way)) { - accessEnc.setBool(isBwd, edgeId, edgeIntAccess, true); + boolean isCar = restrictionKeys.contains("motorcar"); + if (INTENDED.contains(firstValue) + // implied default is allowed only if foot and bicycle is not specified: + || isCar && firstValue.isEmpty() && !way.hasTag("foot") && !way.hasTag("bicycle") + // if hgv is allowed then smaller trucks and cars are allowed too even if not specified + || isCar && way.hasTag("hgv", "yes")) { + // ferry is allowed via explicit tag } else { - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - accessEnc.setBool(true, edgeId, edgeIntAccess, true); + return; } } + + boolean isRoundabout = roundaboutEnc.getBool(false, edgeId, edgeIntAccess); + boolean ignoreOneway = "no".equals(way.getFirstValue(ignoreOnewayKeys)); + boolean isBwd = isBackwardOneway(way); + if (!ignoreOneway && (isBwd || isRoundabout || isForwardOneway(way))) { + accessEnc.setBool(isBwd, edgeId, edgeIntAccess, true); + } else { + accessEnc.setBool(false, edgeId, edgeIntAccess, true); + accessEnc.setBool(true, edgeId, edgeIntAccess, true); + } + } private static String getFirstPriorityNodeTag(Map nodeTags, List restrictionKeys) { @@ -66,11 +102,11 @@ private static String getFirstPriorityNodeTag(Map nodeTags, List protected boolean isBackwardOneway(ReaderWay way) { // vehicle:forward=no is like oneway=-1 - return way.hasTag("oneway", "-1") || way.hasTag(vehicleForward, "no"); + return way.hasTag("oneway", "-1") || "no".equals(way.getFirstValue(vehicleForward)); } protected boolean isForwardOneway(ReaderWay way) { // vehicle:backward=no is like oneway=yes - return way.hasTag("oneway", onewaysForward) || way.hasTag(vehicleBackward, "no"); + return way.hasTag("oneway", ONEWAYS_FW) || "no".equals(way.getFirstValue(vehicleBackward)); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAccessParser.java index 64e855ec308..987b4e06e24 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAccessParser.java @@ -12,17 +12,10 @@ public MountainBikeAccessParser(EncodedValueLookup lookup, PMap properties) { this(lookup.getBooleanEncodedValue(VehicleAccess.key("mtb")), lookup.getBooleanEncodedValue(Roundabout.KEY)); blockPrivate(properties.getBool("block_private", true)); - blockFords(properties.getBool("block_fords", false)); } protected MountainBikeAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) { super(accessEnc, roundaboutEnc); } - @Override - boolean isSacScaleAllowed(String sacScale) { - // other scales are too dangerous even for MTB, see http://wiki.openstreetmap.org/wiki/Key:sac_scale - return "hiking".equals(sacScale) || "mountain_hiking".equals(sacScale) - || "demanding_mountain_hiking".equals(sacScale) || "alpine_hiking".equals(sacScale); - } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAverageSpeedParser.java index 0c73a84b4c1..0c747fd003d 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikeAverageSpeedParser.java @@ -7,37 +7,28 @@ public class MountainBikeAverageSpeedParser extends BikeCommonAverageSpeedParser public MountainBikeAverageSpeedParser(EncodedValueLookup lookup) { this(lookup.getDecimalEncodedValue(VehicleSpeed.key("mtb")), lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class), - lookup.getDecimalEncodedValue(FerrySpeed.KEY)); + lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class)); } - protected MountainBikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) { - super(speedEnc, smoothnessEnc, ferrySpeedEnc); + protected MountainBikeAverageSpeedParser(DecimalEncodedValue speedEnc, + EnumEncodedValue smoothnessEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, bikeRouteEnc); setTrackTypeSpeed("grade1", 18); // paved setTrackTypeSpeed("grade2", 16); // now unpaved ... setTrackTypeSpeed("grade3", 12); setTrackTypeSpeed("grade4", 8); - setTrackTypeSpeed("grade5", 6); // like sand/grass + setTrackTypeSpeed("grade5", PUSHING_SECTION_SPEED); // like sand - setSurfaceSpeed("concrete", 14); - setSurfaceSpeed("concrete:lanes", 16); - setSurfaceSpeed("concrete:plates", 16); + // +4km/h on certain surfaces (max 16km/h) due to wide MTB tires setSurfaceSpeed("dirt", 14); setSurfaceSpeed("earth", 14); - setSurfaceSpeed("fine_gravel", 18); - setSurfaceSpeed("grass", 14); - setSurfaceSpeed("grass_paver", 14); + setSurfaceSpeed("ground", 14); + setSurfaceSpeed("fine_gravel", 16); setSurfaceSpeed("gravel", 16); - setSurfaceSpeed("ground", 16); - setSurfaceSpeed("ice", MIN_SPEED); - setSurfaceSpeed("metal", 10); - setSurfaceSpeed("mud", 12); - setSurfaceSpeed("salt", 12); - setSurfaceSpeed("sand", 10); - setSurfaceSpeed("wood", 10); - - setHighwaySpeed("path", 18); - setHighwaySpeed("footway", PUSHING_SECTION_SPEED); - setHighwaySpeed("track", 18); - setHighwaySpeed("residential", 16); + setSurfaceSpeed("pebblestone", 16); + setSurfaceSpeed("compacted", 16); + setSurfaceSpeed("grass", 12); + setSurfaceSpeed("grass_paver", 12); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikePriorityParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikePriorityParser.java index f124c7d4d3c..a069e46e4bc 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikePriorityParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/MountainBikePriorityParser.java @@ -13,18 +13,11 @@ public class MountainBikePriorityParser extends BikeCommonPriorityParser { public MountainBikePriorityParser(EncodedValueLookup lookup) { this(lookup.getDecimalEncodedValue(VehicleSpeed.key("mtb")), - lookup.getDecimalEncodedValue(VehiclePriority.key("mtb")), - lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class)); + lookup.getDecimalEncodedValue(VehiclePriority.key("mtb"))); } - protected MountainBikePriorityParser(DecimalEncodedValue speedEnc, DecimalEncodedValue priorityEnc, - EnumEncodedValue bikeRouteEnc) { - super(priorityEnc, speedEnc, bikeRouteEnc); - - routeMap.put(INTERNATIONAL, PREFER.getValue()); - routeMap.put(NATIONAL, PREFER.getValue()); - routeMap.put(REGIONAL, PREFER.getValue()); - routeMap.put(LOCAL, BEST.getValue()); + protected MountainBikePriorityParser(DecimalEncodedValue speedEnc, DecimalEncodedValue priorityEnc) { + super(priorityEnc, speedEnc); preferHighwayTags.add("road"); preferHighwayTags.add("track"); @@ -39,14 +32,14 @@ protected MountainBikePriorityParser(DecimalEncodedValue speedEnc, DecimalEncode } @Override - void collect(ReaderWay way, double wayTypeSpeed, TreeMap weightToPrioMap) { - super.collect(way, wayTypeSpeed, weightToPrioMap); + void collect(ReaderWay way, double wayTypeSpeed, boolean bikeDesignated, TreeMap weightToPrioMap) { + super.collect(way, wayTypeSpeed, bikeDesignated, weightToPrioMap); String highway = way.getTag("highway"); if ("track".equals(highway)) { String trackType = way.getTag("tracktype"); - if ("grade1".equals(trackType)) - weightToPrioMap.put(50d, UNCHANGED); + if ("grade1".equals(trackType) || goodSurface.contains(way.getTag("surface",""))) + weightToPrioMap.put(50d, SLIGHT_PREFER); else if (trackType == null) weightToPrioMap.put(90d, PREFER); else if (trackType.startsWith("grade")) diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMBikeNetworkTagParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMBikeNetworkTagParser.java index dd9aebe1051..47a9eca37e2 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMBikeNetworkTagParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMBikeNetworkTagParser.java @@ -27,11 +27,14 @@ public class OSMBikeNetworkTagParser implements RelationTagParser { private final EnumEncodedValue bikeRouteEnc; - // used only for internal transformation from relations into edge flags - private final EnumEncodedValue transformerRouteRelEnc = new EnumEncodedValue<>(getKey("bike", "route_relation"), RouteNetwork.class); + private final String routeValue; + // used only for class internal transformation from relations into edge flags + private final EnumEncodedValue transformerRouteRelEnc; - public OSMBikeNetworkTagParser(EnumEncodedValue bikeRouteEnc, EncodedValue.InitializerConfig relConfig) { + public OSMBikeNetworkTagParser(EnumEncodedValue bikeRouteEnc, EncodedValue.InitializerConfig relConfig, String routeValue) { this.bikeRouteEnc = bikeRouteEnc; + this.routeValue = routeValue; + this.transformerRouteRelEnc = new EnumEncodedValue<>(getKey(routeValue, "route_relation"), RouteNetwork.class); this.transformerRouteRelEnc.init(relConfig); } @@ -39,7 +42,7 @@ public OSMBikeNetworkTagParser(EnumEncodedValue bikeRouteEnc, Enco public void handleRelationTags(IntsRef relFlags, ReaderRelation relation) { IntsRefEdgeIntAccess relIntAccess = new IntsRefEdgeIntAccess(relFlags); RouteNetwork oldBikeNetwork = transformerRouteRelEnc.getEnum(false, -1, relIntAccess); - if (relation.hasTag("route", "bicycle")) { + if (relation.hasTag("route", routeValue)) { String tag = Helper.toLowerCase(relation.getTag("network", "")); RouteNetwork newBikeNetwork = BikeNetworkParserHelper.determine(tag); if (oldBikeNetwork == RouteNetwork.MISSING || oldBikeNetwork.ordinal() > newBikeNetwork.ordinal()) @@ -52,6 +55,9 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way // just copy value into different bit range IntsRefEdgeIntAccess relIntAccess = new IntsRefEdgeIntAccess(relationFlags); RouteNetwork routeNetwork = transformerRouteRelEnc.getEnum(false, -1, relIntAccess); + // if lcn=yes is mapped in OSM way consider this as route=bicycle + if (routeValue.equals("bicycle") && routeNetwork == RouteNetwork.MISSING && way.hasTag("lcn", "yes")) + routeNetwork = RouteNetwork.LOCAL; bikeRouteEnc.setEnum(false, edgeId, edgeIntAccess, routeNetwork); } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMCyclewayParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMCyclewayParser.java new file mode 100644 index 00000000000..a32dd083520 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMCyclewayParser.java @@ -0,0 +1,86 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.Cycleway; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.ev.EnumEncodedValue; +import com.graphhopper.storage.IntsRef; + +/** + * Parses the cycleway type from cycleway, cycleway:left, cycleway:right and cycleway:both OSM tags. + * Stores values per direction: cycleway:right maps to forward, cycleway:left maps to reverse, + * cycleway:both and cycleway (without direction qualifier) map to both directions. + * The deprecated opposite_lane and opposite_track values are stored as LANE/TRACK in the reverse direction. + * + * @see Key:cycleway + */ +public class OSMCyclewayParser implements TagParser { + + private final EnumEncodedValue cyclewayEnc; + + public OSMCyclewayParser(EnumEncodedValue cyclewayEnc) { + this.cyclewayEnc = cyclewayEnc; + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) { + Cycleway bestFwd = Cycleway.MISSING; + Cycleway bestRev = Cycleway.MISSING; + + Cycleway both = Cycleway.find(readerWay.getTag("cycleway:both")); + if (both != Cycleway.MISSING) { + bestFwd = both; + bestRev = both; + } + + Cycleway right = Cycleway.find(readerWay.getTag("cycleway:right")); + if (right != Cycleway.MISSING) + bestFwd = better(bestFwd, right); + + Cycleway left = Cycleway.find(readerWay.getTag("cycleway:left")); + if (left != Cycleway.MISSING) + bestRev = better(bestRev, left); + + // cycleway (generic) used for both directions, unless opposite_* which maps to reverse only + String generic = readerWay.getTag("cycleway"); + if (generic != null) { + if (generic.startsWith("opposite_")) { + Cycleway c = Cycleway.find(generic.substring("opposite_".length())); + if (c != Cycleway.MISSING) + bestRev = better(bestRev, c); + } else { + Cycleway c = Cycleway.find(generic); + if (c != Cycleway.MISSING) { + bestFwd = better(bestFwd, c); + bestRev = better(bestRev, c); + } + } + } + + cyclewayEnc.setEnum(false, edgeId, edgeIntAccess, bestFwd); + cyclewayEnc.setEnum(true, edgeId, edgeIntAccess, bestRev); + } + + private static Cycleway better(Cycleway current, Cycleway candidate) { + if (current == Cycleway.MISSING || candidate.ordinal() < current.ordinal()) + return candidate; + return current; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParser.java index 96ff99ce6d8..b473bd3a9e7 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParser.java @@ -32,9 +32,13 @@ public OSMGetOffBikeParser(BooleanEncodedValue getOffBikeEnc, BooleanEncodedValu @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { String highway = way.getTag("highway"); + String vehicle = way.getTag("vehicle", ""); boolean notIntended = !way.hasTag("bicycle", INTENDED) && (GET_OFF_BIKE.contains(highway) || way.hasTag("railway", "platform") + || !"cycleway".equals(highway) && way.hasTag("vehicle", "no") + || vehicle.contains("forestry") + || vehicle.contains("agricultural") || "path".equals(highway) && way.hasTag("foot", "designated") && !way.hasTag("segregated", "yes")); if ("steps".equals(highway) || way.hasTag("bicycle", "dismount") || notIntended) { getOffBikeEnc.setBool(false, edgeId, edgeIntAccess, true); diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMHovParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMHovParser.java deleted file mode 100644 index 9afbbadcfcf..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMHovParser.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.Hov; -import com.graphhopper.storage.IntsRef; - -public class OSMHovParser implements TagParser { - EnumEncodedValue hovEnc; - - public OSMHovParser(EnumEncodedValue hovEnc) { - this.hovEnc = hovEnc; - } - - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - String surfaceTag = way.getTag("hov"); - Hov hov = Hov.find(surfaceTag); - if (hov != Hov.MISSING) - hovEnc.setEnum(false, edgeId, edgeIntAccess, hov); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BulgariaCountryRule.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMLitParser.java similarity index 53% rename from core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BulgariaCountryRule.java rename to core/src/main/java/com/graphhopper/routing/util/parsers/OSMLitParser.java index bb1dc372fad..bc9d05259fc 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/BulgariaCountryRule.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMLitParser.java @@ -15,29 +15,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.graphhopper.routing.util.countryrules.europe; + +package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.storage.IntsRef; /** - * Defines the default rules for Bulgarian roads - * - * @author Thomas Butz + * https://wiki.openstreetmap.org/wiki/Key:lit */ -public class BulgariaCountryRule implements CountryRule { +public class OSMLitParser implements TagParser { + private final BooleanEncodedValue litEnc; + + public OSMLitParser(BooleanEncodedValue litEnc) { + this.litEnc = litEnc; + } @Override - public Toll getToll(ReaderWay readerWay, Toll currentToll) { - if (currentToll != Toll.MISSING) { - return currentToll; + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + Boolean litValue = false; + if (way.hasTag("lit", "yes") || way.hasTag("lit", "24/7") || way.hasTag("lit", "automatic") ) { + litValue = true; } - - RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); - if (RoadClass.MOTORWAY == roadClass) - return Toll.ALL; - return currentToll; + litEnc.setBool(false, edgeId, edgeIntAccess, litValue); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParser.java index 3b01bf826fc..d0ea906712c 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParser.java @@ -21,13 +21,20 @@ import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.routing.ev.MaxSpeed; -import com.graphhopper.routing.util.parsers.helpers.OSMValueExtractor; import com.graphhopper.storage.IntsRef; +import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.Helper; -import static com.graphhopper.routing.ev.MaxSpeed.UNSET_SPEED; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_150; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_MISSING; public class OSMMaxSpeedParser implements TagParser { + /** + * Special value to represent `maxspeed=none` internally, not exposed via the maxspeed encoded value + */ + public static final double MAXSPEED_NONE = -1; + private final DecimalEncodedValue carMaxSpeedEnc; public OSMMaxSpeedParser(DecimalEncodedValue carMaxSpeedEnc) { @@ -39,24 +46,89 @@ public OSMMaxSpeedParser(DecimalEncodedValue carMaxSpeedEnc) { @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - carMaxSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, getMaxSpeed(way, false)); - carMaxSpeedEnc.setDecimal(true, edgeId, edgeIntAccess, getMaxSpeed(way, true)); + carMaxSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, parseMaxSpeed(way, false)); + carMaxSpeedEnc.setDecimal(true, edgeId, edgeIntAccess, parseMaxSpeed(way, true)); + } + + /** + * @return The maxspeed for the given way. It can be anything between 0 and {@link MaxSpeed.MAXSPEED_150}, + * or {@link MaxSpeed.MAXSPEED_MISSING} in case there is no valid maxspeed tagged for this way in this direction. + */ + public static double parseMaxSpeed(ReaderWay way, boolean reverse) { + double directedMaxSpeed = parseMaxSpeedTag(way, reverse ? "maxspeed:backward" : "maxspeed:forward"); + if (directedMaxSpeed != MAXSPEED_MISSING) + return directedMaxSpeed; + else { + return parseMaxSpeedTag(way, "maxspeed"); + } } - private double getMaxSpeed(ReaderWay way, boolean reverse) { - final double maxSpeed = OSMValueExtractor.stringToKmh(way.getTag("maxspeed")); - final double directedMaxSpeed = OSMValueExtractor.stringToKmh(way.getTag(reverse ? "maxspeed:backward" : "maxspeed:forward")); - return isValidSpeed(directedMaxSpeed) - ? Math.min(directedMaxSpeed, MaxSpeed.UNLIMITED_SIGN_SPEED) - : isValidSpeed(maxSpeed) - ? Math.min(maxSpeed, MaxSpeed.UNLIMITED_SIGN_SPEED) - : UNSET_SPEED; + private static double parseMaxSpeedTag(ReaderWay way, String tag) { + double maxSpeed = parseMaxspeedString(way.getTag(tag)); + if (maxSpeed != MAXSPEED_MISSING && maxSpeed != MAXSPEED_NONE) + // there is no actual use for maxspeeds above 150 so we simply truncate here + return Math.min(MAXSPEED_150, maxSpeed); + else if (maxSpeed == MAXSPEED_NONE && way.hasTag("highway", "motorway", "motorway_link", "trunk", "trunk_link", "primary")) + // We ignore maxspeed=none with some exceptions where unlimited speed is actually allowed like on some + // motorways, trunks and (very rarely) primary roads in Germany, or the Isle of Man. In other cases + // maxspeed=none is only used because mappers have a false understanding of this tag. + return MaxSpeed.MAXSPEED_150; + else + return MAXSPEED_MISSING; } /** - * @return true if the given speed is not {@link Double#NaN} + ** @return the speed in km/h, or {@link MAXSPEED_MISSING} if the string is invalid, or {@link MAXSPEED_NONE} in case it equals 'none' */ - private boolean isValidSpeed(double speed) { - return !Double.isNaN(speed); + public static double parseMaxspeedString(String str) { + if (Helper.isEmpty(str)) + return MAXSPEED_MISSING; + + if ("walk".equals(str.trim())) + return 6; + + if ("none".equals(str.trim())) + // Special case intended to be used when there is actually no speed limit and drivers + // can go as fast as they want like on parts of the German Autobahn. However, in OSM + // this is sometimes misused by mappers trying to indicate that there is no additional + // sign apart from the general speed limit. + return MAXSPEED_NONE; + + int mpInteger = str.indexOf("mp"); + int knotInteger = str.indexOf("knots"); + int kmInteger = str.indexOf("km"); + int kphInteger = str.indexOf("kph"); + + double factor; + if (mpInteger > 0) { + str = str.substring(0, mpInteger).trim(); + factor = DistanceCalcEarth.KM_MILE; + } else if (knotInteger > 0) { + str = str.substring(0, knotInteger).trim(); + factor = 1.852; // see https://en.wikipedia.org/wiki/Knot_%28unit%29#Definitions + } else { + if (kmInteger > 0) { + str = str.substring(0, kmInteger).trim(); + } else if (kphInteger > 0) { + str = str.substring(0, kphInteger).trim(); + } + factor = 1; + } + + double value; + try { + value = Double.parseDouble(str) * factor; + } catch (Exception ex) { + return MAXSPEED_MISSING; + } + + if (value < 4.8) + // We consider maxspeed < 4.8km/h a bug in OSM data and act as if the tag wasn't there. + // The limit is chosen such that maxspeed=3mph is still valid, because there actually are + // some road signs using 3mph. + // https://github.com/graphhopper/graphhopper/pull/3077#discussion_r1826842203 + return MAXSPEED_MISSING; + + return value; } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java index 43b268cb764..0043cc90e07 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParser.java @@ -31,7 +31,7 @@ public class OSMMaxWeightParser implements TagParser { // do not include OSM tag "height" here as it has completely different meaning (height of peak) - private static final List MAX_WEIGHT_TAGS = Arrays.asList("maxweight", "maxgcweight"/*abandoned*/, "maxweightrating:hgv"); + private static final List MAX_WEIGHT_TAGS = Arrays.asList("maxweight", "maxweightrating", "maxweightrating:hgv", "maxgcweight"/*abandoned*/); private static final List HGV_RESTRICTIONS = OSMRoadAccessParser.toOSMRestrictions(TransportationMode.HGV).stream() .map(e -> e + ":conditional").collect(Collectors.toList()); private final DecimalEncodedValue weightEncoder; @@ -44,7 +44,7 @@ public OSMMaxWeightParser(DecimalEncodedValue weightEncoder) { public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { OSMValueExtractor.extractTons(edgeId, edgeIntAccess, way, weightEncoder, MAX_WEIGHT_TAGS); - // vehicle:conditional no @ (weight > 7.5) + // vehicle:conditional = no @ (weight > 7.5) for (String restriction : HGV_RESTRICTIONS) { String value = way.getTag(restriction, ""); if (value.startsWith("no") && value.indexOf("@") < 6) { // no,none[ ]@ diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbNetworkTagParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbNetworkTagParser.java deleted file mode 100644 index 2de8d4bdc6d..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbNetworkTagParser.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.reader.ReaderRelation; -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.*; -import com.graphhopper.storage.IntsRef; -import com.graphhopper.util.Helper; - -import static com.graphhopper.routing.util.EncodingManager.getKey; - -public class OSMMtbNetworkTagParser implements RelationTagParser { - private final EnumEncodedValue bikeRouteEnc; - // used only for internal transformation from relations into edge flags - private final EnumEncodedValue transformerRouteRelEnc = new EnumEncodedValue<>(getKey("mtb", "route_relation"), RouteNetwork.class); - - public OSMMtbNetworkTagParser(EnumEncodedValue bikeRouteEnc, EncodedValue.InitializerConfig relConfig) { - this.bikeRouteEnc = bikeRouteEnc; - this.transformerRouteRelEnc.init(relConfig); - } - - @Override - public void handleRelationTags(IntsRef relFlags, ReaderRelation relation) { - IntsRefEdgeIntAccess relIntAccess = new IntsRefEdgeIntAccess(relFlags); - RouteNetwork oldBikeNetwork = transformerRouteRelEnc.getEnum(false, -1, relIntAccess); - if (relation.hasTag("route", "mtb")) { - String tag = Helper.toLowerCase(relation.getTag("network", "")); - RouteNetwork newBikeNetwork = BikeNetworkParserHelper.determine(tag); - if (oldBikeNetwork == RouteNetwork.MISSING || oldBikeNetwork.ordinal() > newBikeNetwork.ordinal()) - transformerRouteRelEnc.setEnum(false, -1, relIntAccess, newBikeNetwork); - } - } - - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - // just copy value into different bit range - IntsRefEdgeIntAccess relIntAccess = new IntsRefEdgeIntAccess(relationFlags); - RouteNetwork routeNetwork = transformerRouteRelEnc.getEnum(false, -1, relIntAccess); - bikeRouteEnc.setEnum(false, edgeId, edgeIntAccess, routeNetwork); - } - - public EnumEncodedValue getTransformerRouteRelEnc() { - return transformerRouteRelEnc; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbRatingParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbRatingParser.java index ec425dbe1b5..c2bf99e8e31 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbRatingParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMMtbRatingParser.java @@ -24,7 +24,8 @@ /** * Parses the mountain biking difficulty. - * A mapping mtb:scale=0 corresponds to 1 and mtb:scale=1 to 2 until 7. + * Note that the tag mtb:scale=0 corresponds to mtb_rating=1 because mtb_rating=0 is the default, + * i.e. mtb_rating goes until 7. * * @see Key:mtb:scale for details on OSM mountain biking difficulties. * @see Single Trail Scale diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java index 5f7ad0f0bf5..190aedc5cb9 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParser.java @@ -18,59 +18,66 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.RoadAccess; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.routing.util.countryrules.CountryRule; import com.graphhopper.storage.IntsRef; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; -import static com.graphhopper.routing.ev.RoadAccess.YES; - -public class OSMRoadAccessParser implements TagParser { - protected final EnumEncodedValue roadAccessEnc; +public class OSMRoadAccessParser implements TagParser { + protected final EnumEncodedValue accessEnc; private final List restrictions; + private final Function valueFinder; + private final RoadAccessDefaultHandler roadAccessDefaultHandler; - public OSMRoadAccessParser(EnumEncodedValue roadAccessEnc, List restrictions) { - this.roadAccessEnc = roadAccessEnc; + public OSMRoadAccessParser(EnumEncodedValue accessEnc, List restrictions, + RoadAccessDefaultHandler roadAccessDefaultHandler, + Function valueFinder) { + this.accessEnc = accessEnc; this.restrictions = restrictions; + this.valueFinder = valueFinder; + this.roadAccessDefaultHandler = roadAccessDefaultHandler; } @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) { - RoadAccess accessValue = YES; + T accessValue = null; List> nodeTags = readerWay.getTag("node_tags", Collections.emptyList()); // a barrier edge has the restriction in both nodes and the tags are the same if (readerWay.hasTag("gh:barrier_edge")) for (String restriction : restrictions) { Object value = nodeTags.get(0).get(restriction); - if (value != null) accessValue = getRoadAccess((String) value, accessValue); + accessValue = getRoadAccess((String) value, accessValue); + if (accessValue != null) break; } for (String restriction : restrictions) { accessValue = getRoadAccess(readerWay.getTag(restriction), accessValue); + if (accessValue != null) break; } - CountryRule countryRule = readerWay.getTag("country_rule", null); - if (countryRule != null) - accessValue = countryRule.getAccess(readerWay, TransportationMode.CAR, accessValue); + if (accessValue == null) { + Country country = readerWay.getTag("country", Country.MISSING); + accessValue = roadAccessDefaultHandler.getAccess(readerWay, country); + } - roadAccessEnc.setEnum(false, edgeId, edgeIntAccess, accessValue); + if (accessValue != null) + accessEnc.setEnum(false, edgeId, edgeIntAccess, accessValue); } - private RoadAccess getRoadAccess(String tagValue, RoadAccess accessValue) { - RoadAccess tmpAccessValue; + private T getRoadAccess(String tagValue, T accessValue) { + T tmpAccessValue; if (tagValue != null) { String[] complex = tagValue.split(";"); for (String simple : complex) { - tmpAccessValue = simple.equals("permit") ? RoadAccess.PRIVATE : RoadAccess.find(simple); - if (tmpAccessValue != null && tmpAccessValue.ordinal() > accessValue.ordinal()) { + tmpAccessValue = valueFinder.apply(simple); + if (tmpAccessValue == null) continue; + if (accessValue == null || tmpAccessValue.ordinal() < accessValue.ordinal()) { accessValue = tmpAccessValue; } } @@ -78,26 +85,206 @@ private RoadAccess getRoadAccess(String tagValue, RoadAccess accessValue) { return accessValue; } - public static List toOSMRestrictions(TransportationMode mode) { - switch (mode) { - case FOOT: - return Arrays.asList("foot", "access"); - case VEHICLE: - return Arrays.asList("vehicle", "access"); - case BIKE: - return Arrays.asList("bicycle", "vehicle", "access"); - case CAR: - return Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access"); - case MOTORCYCLE: - return Arrays.asList("motorcycle", "motor_vehicle", "vehicle", "access"); - case HGV: - return Arrays.asList("hgv", "motor_vehicle", "vehicle", "access"); - case PSV: - return Arrays.asList("psv", "motor_vehicle", "vehicle", "access"); - case BUS: - return Arrays.asList("bus", "psv", "motor_vehicle", "vehicle", "access"); - default: - throw new IllegalArgumentException("Cannot convert TransportationMode " + mode + " to list of restrictions"); + @FunctionalInterface + public interface RoadAccessDefaultHandler { + T getAccess(ReaderWay readerWay, Country country); + } + + public static RoadClass getRoadClass(ReaderWay readerWay) { + String hw = readerWay.getTag("highway", ""); + return RoadClass.find(hw.endsWith("_link") ? hw.substring(0, hw.length() - 5) : hw); + } + + public static RoadAccessDefaultHandler CAR_HANDLER = (ReaderWay readerWay, Country country) -> { + RoadClass roadClass = getRoadClass(readerWay); + return switch (country) { + case AUT -> switch (roadClass) { + case LIVING_STREET -> RoadAccess.DESTINATION; + case TRACK -> RoadAccess.FORESTRY; + case PATH, BRIDLEWAY, CYCLEWAY, FOOTWAY, PEDESTRIAN -> RoadAccess.NO; + default -> RoadAccess.YES; + }; + case DEU -> switch (roadClass) { + case TRACK -> RoadAccess.DESTINATION; + case PATH, BRIDLEWAY, CYCLEWAY, FOOTWAY, PEDESTRIAN -> RoadAccess.NO; + default -> RoadAccess.YES; + }; + case HUN -> { + if (roadClass == RoadClass.LIVING_STREET) yield RoadAccess.DESTINATION; + yield RoadAccess.YES; + } + default -> null; + }; + }; + + // Based on https://wiki.openstreetmap.org/wiki/OSM_tags_for_routing/Access_restrictions + // The motorroad tag is handled in FootAccessParser and BikeCommonAccessParser via always skipping. + // See https://wiki.openstreetmap.org/wiki/Tag:motorroad%3Dyes + public static RoadAccessDefaultHandler FOOT_HANDLER = (readerWay, country) -> { + RoadClass roadClass = getRoadClass(readerWay); + switch (country) { + case AUT, CHE, HRV, SVK, FRA -> { + if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.BRIDLEWAY) + return FootRoadAccess.NO; + } + case BEL -> { + if (roadClass == RoadClass.TRUNK /* foot=no implied for highway=trunk without motorroad=yes? */ || roadClass == RoadClass.BUSWAY) + return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) + return FootRoadAccess.YES; + } + case BLR, RUS, DEU, ESP, UKR -> { + if (roadClass == RoadClass.BRIDLEWAY) return FootRoadAccess.NO; + } + case BRA -> { + if (roadClass == RoadClass.BUSWAY) return FootRoadAccess.NO; + } + case CHN -> { + if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.YES; + else if (roadClass == RoadClass.BRIDLEWAY) return FootRoadAccess.NO; + } + case DNK -> { + if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.BRIDLEWAY) + return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) + return FootRoadAccess.YES; + } + case FIN -> { + if (roadClass == RoadClass.BRIDLEWAY) return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.YES; + } + case GBR, GRC, ISL, PHL, THA, USA, NOR -> { + if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.YES; + } + case HUN -> { + if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.BRIDLEWAY) + return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) + return FootRoadAccess.YES; + } + case NLD -> { + if (roadClass == RoadClass.BUSWAY + || roadClass == RoadClass.BRIDLEWAY) return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.YES; + } + case OMN -> { + if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.DESIGNATED; + } + case SWE -> { + if (roadClass == RoadClass.BUSWAY) return FootRoadAccess.NO; + else if (roadClass == RoadClass.CYCLEWAY) return FootRoadAccess.YES; + } + } + return null; + }; + + public static RoadAccessDefaultHandler BIKE_HANDLER = (ReaderWay readerWay, Country country) -> { + RoadClass roadClass = getRoadClass(readerWay); + switch (country) { + case AUT, HRV -> { + if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.BRIDLEWAY) + return BikeRoadAccess.NO; + } + case BEL -> { + if (roadClass == RoadClass.TRUNK /* bicycle=no implied for highway=trunk without motorroad=yes? */ + || roadClass == RoadClass.BUSWAY + || roadClass == RoadClass.BRIDLEWAY + || roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case BLR -> { + if (roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.DESIGNATED; + else if (roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.YES; + } + case BRA -> { + if (roadClass == RoadClass.BUSWAY) return BikeRoadAccess.NO; + } + case CHE, DNK, SVK -> { + if (roadClass == RoadClass.TRUNK + || roadClass == RoadClass.BRIDLEWAY + || roadClass == RoadClass.PEDESTRIAN + || roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.NO; + } + case CHN -> { + if (roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case DEU, TUR, RUS, UKR -> { + if (roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + } + case ESP -> { + if (roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case FIN -> { + if (roadClass == RoadClass.FOOTWAY || roadClass == RoadClass.BRIDLEWAY) + return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case FRA -> { + if (roadClass == RoadClass.TRUNK + || roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case GRC, GBR, HKG, IRL -> { + if (roadClass == RoadClass.PEDESTRIAN + || roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.NO; + } + case HUN -> { + if (roadClass == RoadClass.TRUNK + || roadClass == RoadClass.BRIDLEWAY + || roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.NO; + } + case ISL, NOR -> { + if (roadClass == RoadClass.PEDESTRIAN + || roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.YES; + } + case ITA -> { + if (roadClass == RoadClass.FOOTWAY) return BikeRoadAccess.NO; + else if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } + case NLD -> { + if (roadClass == RoadClass.BUSWAY + || roadClass == RoadClass.BRIDLEWAY) return BikeRoadAccess.NO; + } + case OMN -> { + if (roadClass == RoadClass.MOTORWAY) return BikeRoadAccess.YES; + else if (roadClass == RoadClass.PEDESTRIAN || roadClass == RoadClass.FOOTWAY) + return BikeRoadAccess.NO; + } + case PHL, THA, USA, SWE -> { + if (roadClass == RoadClass.PEDESTRIAN) return BikeRoadAccess.YES; + } } + return null; + }; + + public static List toOSMRestrictions(TransportationMode mode) { + return switch (mode) { + case FOOT -> Arrays.asList("foot", "access"); + case VEHICLE -> Arrays.asList("vehicle", "access"); + case BIKE -> Arrays.asList("bicycle", "vehicle", "access"); + case CAR -> Arrays.asList("motorcar", "motor_vehicle", "vehicle", "access"); + case MOTORCYCLE -> Arrays.asList("motorcycle", "motor_vehicle", "vehicle", "access"); + case HGV -> Arrays.asList("hgv", "motor_vehicle", "vehicle", "access"); + case PSV -> Arrays.asList("psv", "motor_vehicle", "vehicle", "access"); + case BUS -> Arrays.asList("bus", "psv", "motor_vehicle", "vehicle", "access"); + case HOV -> Arrays.asList("hov", "motor_vehicle", "vehicle", "access"); + default -> + throw new IllegalArgumentException("Cannot convert TransportationMode " + mode + " to list of restrictions"); + }; + } + + public static OSMRoadAccessParser forCar(EnumEncodedValue roadAccessEnc) { + return new OSMRoadAccessParser<>(roadAccessEnc, toOSMRestrictions(TransportationMode.CAR), CAR_HANDLER, RoadAccess::find); + } + + public static OSMRoadAccessParser forBike(EnumEncodedValue roadAccessEnc) { + return new OSMRoadAccessParser<>(roadAccessEnc, toOSMRestrictions(TransportationMode.BIKE), BIKE_HANDLER, BikeRoadAccess::find); + } + + public static OSMRoadAccessParser forFoot(EnumEncodedValue roadAccessEnc) { + return new OSMRoadAccessParser<>(roadAccessEnc, toOSMRestrictions(TransportationMode.FOOT), FOOT_HANDLER, FootRoadAccess::find); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSidewalkParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSidewalkParser.java new file mode 100644 index 00000000000..488758238e6 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSidewalkParser.java @@ -0,0 +1,88 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.ev.EnumEncodedValue; +import com.graphhopper.routing.ev.Sidewalk; +import com.graphhopper.storage.IntsRef; + +/** + * Parses the sidewalk presence from sidewalk, sidewalk:left, sidewalk:right and sidewalk:both OSM tags. + * The main sidewalk tag encodes direction as its value (left, right, both), while the directional + * keys encode presence as their value (yes, no, separate). + * + * @see Key:sidewalk + */ +public class OSMSidewalkParser implements TagParser { + + private final EnumEncodedValue sidewalkEnc; + + public OSMSidewalkParser(EnumEncodedValue sidewalkEnc) { + this.sidewalkEnc = sidewalkEnc; + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) { + Sidewalk bestFwd = Sidewalk.MISSING; + Sidewalk bestRev = Sidewalk.MISSING; + + Sidewalk both = Sidewalk.find(readerWay.getTag("sidewalk:both")); + if (both != Sidewalk.MISSING) { + bestFwd = both; + bestRev = both; + } + + Sidewalk right = Sidewalk.find(readerWay.getTag("sidewalk:right")); + if (right != Sidewalk.MISSING) + bestFwd = right; + + Sidewalk left = Sidewalk.find(readerWay.getTag("sidewalk:left")); + if (left != Sidewalk.MISSING) + bestRev = left; + + // sidewalk, now the direction is encoded in the value + String main = readerWay.getTag("sidewalk"); + if (main != null) { + switch (main) { + case "both", "yes": + if (bestFwd == Sidewalk.MISSING) bestFwd = Sidewalk.YES; + if (bestRev == Sidewalk.MISSING) bestRev = Sidewalk.YES; + break; + case "right": + if (bestFwd == Sidewalk.MISSING) bestFwd = Sidewalk.YES; + break; + case "left": + if (bestRev == Sidewalk.MISSING) bestRev = Sidewalk.YES; + break; + case "no", "none": + if (bestFwd == Sidewalk.MISSING) bestFwd = Sidewalk.NO; + if (bestRev == Sidewalk.MISSING) bestRev = Sidewalk.NO; + break; + case "separate": + if (bestFwd == Sidewalk.MISSING) bestFwd = Sidewalk.SEPARATE; + if (bestRev == Sidewalk.MISSING) bestRev = Sidewalk.SEPARATE; + break; + } + } + + sidewalkEnc.setEnum(false, edgeId, edgeIntAccess, bestFwd); + sidewalkEnc.setEnum(true, edgeId, edgeIntAccess, bestRev); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSmoothnessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSmoothnessParser.java index f9435793ed2..4ca381c3e4f 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSmoothnessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSmoothnessParser.java @@ -42,4 +42,4 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay rea smoothnessEnc.setEnum(false, edgeId, edgeIntAccess, smoothness); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSurfaceParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSurfaceParser.java index cbe2becb9d9..481ff756a64 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSurfaceParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMSurfaceParser.java @@ -23,7 +23,7 @@ import com.graphhopper.routing.ev.Surface; import com.graphhopper.storage.IntsRef; -import static com.graphhopper.routing.ev.Surface.*; +import static com.graphhopper.routing.ev.Surface.MISSING; public class OSMSurfaceParser implements TagParser { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java new file mode 100644 index 00000000000..73b2c39dc7c --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java @@ -0,0 +1,131 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.reader.osm.conditional.ConditionalValueParser; +import com.graphhopper.reader.osm.conditional.DateRangeParser; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.storage.IntsRef; +import com.graphhopper.util.Helper; + +import java.text.ParseException; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +/** + * This parser fills the different XYTemporalAccess enums from the OSM conditional + * restrictions based on the specified dateRangeParserDate. 'Temporal' means that both, temporary + * and seasonal restrictions will be considered. Node tags will be ignored for now. + */ +public class OSMTemporalAccessParser implements TagParser { + + private final Collection conditionals; + private final Setter restrictionSetter; + private final DateRangeParser parser; + + @FunctionalInterface + public interface Setter { + void setBoolean(int edgeId, EdgeIntAccess edgeIntAccess, boolean b); + } + + public OSMTemporalAccessParser(Collection conditionals, Setter restrictionSetter, String dateRangeParserDate) { + this.conditionals = conditionals; + this.restrictionSetter = restrictionSetter; + if (dateRangeParserDate.isEmpty()) + dateRangeParserDate = Helper.createFormatter("yyyy-MM-dd").format(new Date().getTime()); + + this.parser = DateRangeParser.createInstance(dateRangeParserDate); + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + // TODO for now the node tag overhead is not worth the effort due to very few data points + // List> nodeTags = way.getTag("node_tags", null); + + Boolean b = getTemporaryAccess(way.getTags()); + if (b != null) + restrictionSetter.setBoolean(edgeId, edgeIntAccess, b); + } + + public Boolean getTemporaryAccess(Map tags) { + for (Map.Entry entry : tags.entrySet()) { + if (!conditionals.contains(entry.getKey())) continue; + + String value = (String) entry.getValue(); + String[] strs = value.split("@"); + if (strs.length == 2) { + Boolean inRange = isInRange(parser, strs[1].trim()); + if (inRange != null) { + if (strs[0].trim().equals("no")) return !inRange; + if (strs[0].trim().equals("yes")) return inRange; + } + } + } + return null; + } + + private static Boolean isInRange(final DateRangeParser parser, final String value) { + if (value.isEmpty()) + return null; + + if (value.contains(";")) + return null; + + String conditionalValue = value.replace('(', ' ').replace(')', ' ').trim(); + try { + ConditionalValueParser.ConditionState res = parser.checkCondition(conditionalValue); + if (res.isValid()) + return res.isCheckPassed(); + } catch (ParseException ex) { + } + return null; + } + + /** + * This method checks the conditional restrictions starting from firstIndex and returns + * true if the access value is in the "accepted" collection AND the conditional value describes + * a time (e.g. date, time or interval). + */ + public static boolean hasPermissiveTemporalRestriction(ReaderWay way, int firstIndex, + List restrictionKeys, Collection accepted) { + for (int i = firstIndex; i >= 0; i--) { + String value = way.getTag(restrictionKeys.get(i) + ":conditional"); + if (acceptedAndInRange(value, accepted)) return true; + } + return false; + } + + private static boolean acceptedAndInRange(String value, Collection accepted) { + if (value == null) return false; + String[] strs = value.split("@"); + if (strs.length == 2) + try { + String conditionalValue = strs[1].replace('(', ' ').replace(')', ' ').trim(); + return accepted.contains(strs[0].trim()) && + (strs[1].contains(":") // time + || DateRangeParser.getRange(conditionalValue ) != null // date + ); + } catch (ParseException ex) { + } + return false; + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java index 25941bb0a6b..941b385e3c4 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java @@ -18,10 +18,7 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.Toll; -import com.graphhopper.routing.util.countryrules.CountryRule; +import com.graphhopper.routing.ev.*; import com.graphhopper.storage.IntsRef; import java.util.Arrays; @@ -49,11 +46,68 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay rea } else { toll = Toll.MISSING; } - - CountryRule countryRule = readerWay.getTag("country_rule", null); - if (countryRule != null) - toll = countryRule.getToll(readerWay, toll); + if (toll == Toll.MISSING) { + Country country = readerWay.getTag("country", Country.MISSING); + toll = getCountryDefault(country, readerWay); + } tollEnc.setEnum(false, edgeId, edgeIntAccess, toll); } + + private Toll getCountryDefault(Country country, ReaderWay readerWay) { + switch (country) { + case ROU, SVK, SVN -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) + return Toll.ALL; + else + return Toll.NO; + } + case CHE -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) + return Toll.ALL; + else + // 'Schwerlastabgabe' for the entire road network + return Toll.HGV; + } + case LIE -> { + // 'Schwerlastabgabe' for the entire road network + return Toll.HGV; + } + case HUN -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY) + return Toll.ALL; + else if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) + return Toll.HGV; + else + return Toll.NO; + } + case DEU, DNK, EST, LTU, LVA -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) + return Toll.HGV; + else + return Toll.NO; + } + case BEL, BLR, LUX, POL, SWE -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY) + return Toll.HGV; + else + return Toll.NO; + } + case BGR, CZE, FRA, GRC, HRV, ITA, PRT, SRB, ESP -> { + RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); + if (roadClass == RoadClass.MOTORWAY) + return Toll.ALL; + else + return Toll.NO; + } + default -> { + return Toll.NO; + } + } + } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java new file mode 100644 index 00000000000..945919a6fb5 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java @@ -0,0 +1,52 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.storage.IntsRef; +import com.graphhopper.util.PointList; + +import static com.graphhopper.util.AngleCalc.ANGLE_CALC; + +public class OrientationCalculator implements TagParser { + + private final DecimalEncodedValue orientationEnc; + + public OrientationCalculator(DecimalEncodedValue orientationEnc) { + this.orientationEnc = orientationEnc; + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + PointList pointList = way.getTag("point_list", null); + if (pointList != null) { + // store orientation in degrees and use the end of the edge + double azimuth = ANGLE_CALC.calcAzimuth(pointList.getLat(pointList.size() - 2), pointList.getLon(pointList.size() - 2), + pointList.getLat(pointList.size() - 1), pointList.getLon(pointList.size() - 1)); + orientationEnc.setDecimal(false, edgeId, edgeIntAccess, azimuth); + + // same for the opposite direction + double revAzimuth = ANGLE_CALC.calcAzimuth(pointList.getLat(1), pointList.getLon(1), + pointList.getLat(0), pointList.getLon(0)); + orientationEnc.setDecimal(true, edgeId, edgeIntAccess, revAzimuth); + } + } +} + diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAccessParser.java index 6d0c34325b3..b23dfddc823 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAccessParser.java @@ -12,7 +12,6 @@ public RacingBikeAccessParser(EncodedValueLookup lookup, PMap properties) { this(lookup.getBooleanEncodedValue(VehicleAccess.key("racingbike")), lookup.getBooleanEncodedValue(Roundabout.KEY)); blockPrivate(properties.getBool("block_private", true)); - blockFords(properties.getBool("block_fords", false)); } protected RacingBikeAccessParser(BooleanEncodedValue accessEnc, BooleanEncodedValue roundaboutEnc) { diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAverageSpeedParser.java index dabc0f82aba..759f53230b1 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAverageSpeedParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikeAverageSpeedParser.java @@ -7,23 +7,25 @@ public class RacingBikeAverageSpeedParser extends BikeCommonAverageSpeedParser { public RacingBikeAverageSpeedParser(EncodedValueLookup lookup) { this(lookup.getDecimalEncodedValue(VehicleSpeed.key("racingbike")), lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class), - lookup.getDecimalEncodedValue(FerrySpeed.KEY)); + lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class)); } - protected RacingBikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) { - super(speedEnc, smoothnessEnc, ferrySpeedEnc); + protected RacingBikeAverageSpeedParser(DecimalEncodedValue speedEnc, + EnumEncodedValue smoothnessEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, bikeRouteEnc); - setTrackTypeSpeed("grade1", 20); // paved + setTrackTypeSpeed("grade1", 24); // paved setTrackTypeSpeed("grade2", 10); // now unpaved ... setTrackTypeSpeed("grade3", PUSHING_SECTION_SPEED); setTrackTypeSpeed("grade4", PUSHING_SECTION_SPEED); setTrackTypeSpeed("grade5", PUSHING_SECTION_SPEED); - setSurfaceSpeed("paved", 20); - setSurfaceSpeed("asphalt", 20); - setSurfaceSpeed("concrete", 20); - setSurfaceSpeed("concrete:lanes", 16); - setSurfaceSpeed("concrete:plates", 16); + setSurfaceSpeed("paved", 24); + setSurfaceSpeed("asphalt", 24); + setSurfaceSpeed("concrete", 24); + setSurfaceSpeed("concrete:lanes", 20); + setSurfaceSpeed("concrete:plates", 20); setSurfaceSpeed("unpaved", MIN_SPEED); setSurfaceSpeed("compacted", MIN_SPEED); setSurfaceSpeed("dirt", MIN_SPEED); @@ -41,25 +43,22 @@ protected RacingBikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setSurfaceSpeed("sand", MIN_SPEED); setSurfaceSpeed("wood", MIN_SPEED); - setHighwaySpeed("path", 8); - setHighwaySpeed("footway", PUSHING_SECTION_SPEED); setHighwaySpeed("track", MIN_SPEED); // assume unpaved - setHighwaySpeed("trunk", 20); - setHighwaySpeed("trunk_link", 20); - setHighwaySpeed("primary", 20); - setHighwaySpeed("primary_link", 20); - setHighwaySpeed("secondary", 20); - setHighwaySpeed("secondary_link", 20); - setHighwaySpeed("tertiary", 20); - setHighwaySpeed("tertiary_link", 20); + setHighwaySpeed("trunk", 24); + setHighwaySpeed("trunk_link", 24); + setHighwaySpeed("primary", 24); + setHighwaySpeed("primary_link", 24); + setHighwaySpeed("secondary", 24); + setHighwaySpeed("secondary_link", 24); + setHighwaySpeed("tertiary", 24); + setHighwaySpeed("tertiary_link", 24); + setHighwaySpeed("cycleway", 24); - addPushingSection("path"); - - // overwite map from BikeCommon - setSmoothnessSpeedFactor(com.graphhopper.routing.ev.Smoothness.EXCELLENT, 1.2d); - setSmoothnessSpeedFactor(com.graphhopper.routing.ev.Smoothness.VERY_BAD, 0.1); - setSmoothnessSpeedFactor(com.graphhopper.routing.ev.Smoothness.HORRIBLE, 0.1); - setSmoothnessSpeedFactor(com.graphhopper.routing.ev.Smoothness.VERY_HORRIBLE, 0.1); + // overwrite map from BikeCommon + setSmoothnessSpeedFactor(Smoothness.EXCELLENT, 1.2d); + setSmoothnessSpeedFactor(Smoothness.VERY_BAD, 0.1); + setSmoothnessSpeedFactor(Smoothness.HORRIBLE, 0.1); + setSmoothnessSpeedFactor(Smoothness.VERY_HORRIBLE, 0.1); } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikePriorityParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikePriorityParser.java index 9ead7ff7ea3..2a89dd11497 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikePriorityParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/RacingBikePriorityParser.java @@ -6,20 +6,17 @@ import java.util.TreeMap; -import static com.graphhopper.routing.ev.RouteNetwork.*; import static com.graphhopper.routing.util.PriorityCode.*; public class RacingBikePriorityParser extends BikeCommonPriorityParser { public RacingBikePriorityParser(EncodedValueLookup lookup) { this(lookup.getDecimalEncodedValue(VehiclePriority.key("racingbike")), - lookup.getDecimalEncodedValue(VehicleSpeed.key("racingbike")), - lookup.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class)); + lookup.getDecimalEncodedValue(VehicleSpeed.key("racingbike"))); } - protected RacingBikePriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc, - EnumEncodedValue bikeRouteEnc) { - super(priorityEnc, speedEnc, bikeRouteEnc); + protected RacingBikePriorityParser(DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc) { + super(priorityEnc, speedEnc); addPushingSection("path"); @@ -37,27 +34,22 @@ protected RacingBikePriorityParser(DecimalEncodedValue priorityEnc, DecimalEncod avoidHighwayTags.put("primary", AVOID_MORE); avoidHighwayTags.put("primary_link", AVOID_MORE); - routeMap.put(INTERNATIONAL, BEST.getValue()); - routeMap.put(NATIONAL, BEST.getValue()); - routeMap.put(REGIONAL, VERY_NICE.getValue()); - routeMap.put(LOCAL, UNCHANGED.getValue()); - setSpecificClassBicycle("roadcycling"); avoidSpeedLimit = 81; } @Override - void collect(ReaderWay way, double wayTypeSpeed, TreeMap weightToPrioMap) { - super.collect(way, wayTypeSpeed, weightToPrioMap); + void collect(ReaderWay way, double wayTypeSpeed, boolean bikeDesignated, TreeMap weightToPrioMap) { + super.collect(way, wayTypeSpeed, bikeDesignated, weightToPrioMap); String highway = way.getTag("highway"); if ("service".equals(highway) || "residential".equals(highway)) { weightToPrioMap.put(40d, SLIGHT_AVOID); } else if ("track".equals(highway)) { String trackType = way.getTag("tracktype"); - if ("grade1".equals(trackType)) - weightToPrioMap.put(110d, PREFER); + if ("grade1".equals(trackType) || goodSurface.contains(way.getTag("surface", ""))) + weightToPrioMap.put(110d, VERY_NICE); else if (trackType == null || trackType.startsWith("grade")) weightToPrioMap.put(110d, AVOID_MORE); } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java index 17fffd77e99..dbbd1129b3f 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/RestrictionSetter.java @@ -18,180 +18,275 @@ package com.graphhopper.routing.util.parsers; -import com.carrotsearch.hppc.IntHashSet; -import com.carrotsearch.hppc.IntIntHashMap; -import com.carrotsearch.hppc.IntIntMap; -import com.carrotsearch.hppc.IntSet; +import com.carrotsearch.hppc.BitSet; +import com.carrotsearch.hppc.*; import com.carrotsearch.hppc.cursors.IntCursor; -import com.graphhopper.reader.osm.GraphRestriction; +import com.carrotsearch.hppc.procedures.IntProcedure; +import com.carrotsearch.hppc.procedures.LongIntProcedure; import com.graphhopper.reader.osm.Pair; -import com.graphhopper.reader.osm.RestrictionType; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.FetchMode; +import com.graphhopper.util.BitUtil; +import com.graphhopper.util.*; -import java.util.List; +import java.util.*; -import static com.graphhopper.reader.osm.RestrictionType.NO; -import static com.graphhopper.reader.osm.RestrictionType.ONLY; +import static com.graphhopper.util.EdgeIteratorState.REVERSE_STATE; +/** + * Used to add via-node and via-edge restrictions to a given graph. Via-edge restrictions are realized + * by augmenting the graph with artificial edges. For proper handling of overlapping turn restrictions + * (turn restrictions that share the same via-edges) and turn restrictions for different encoded values + * it is important to add all restrictions with a single call. + */ public class RestrictionSetter { + private static final IntSet EMPTY_SET = IntHashSet.from(); private final BaseGraph baseGraph; - private final EdgeExplorer edgeExplorer; - private final IntIntMap artificialEdgesByEdges = new IntIntHashMap(); + private final List turnRestrictionEncs; - public RestrictionSetter(BaseGraph baseGraph) { + public RestrictionSetter(BaseGraph baseGraph, List turnRestrictionEncs) { this.baseGraph = baseGraph; - this.edgeExplorer = baseGraph.createEdgeExplorer(); - } - - /** - * Adds all the turn restriction entries to the graph that are needed to enforce the given restrictions, for - * a single turn cost encoded value. - * Implementing via-way turn restrictions requires adding artificial edges to the graph, which is also handled here. - * Since we keep track of the added artificial edges here it is important to only use one RestrictionSetter instance - * for **all** turn restrictions and vehicle types. - */ - public void setRestrictions(List> restrictions, BooleanEncodedValue turnRestrictionEnc) { - // we first need to add all the artificial edges, because we might need to restrict turns between artificial - // edges created for different restrictions (when restrictions are overlapping) - addArtificialEdges(restrictions); - // now we can add all the via-way restrictions - addViaWayRestrictions(restrictions, turnRestrictionEnc); - // ... and finally all the via-node restrictions - addViaNodeRestrictions(restrictions, turnRestrictionEnc); - } - - private void addArtificialEdges(List> restrictions) { - for (Pair p : restrictions) { - if (p.first.isViaWayRestriction()) { - if (ignoreViaWayRestriction(p)) continue; - int viaEdge = p.first.getViaEdges().get(0); - int artificialEdge = artificialEdgesByEdges.getOrDefault(viaEdge, -1); - if (artificialEdge < 0) { - EdgeIteratorState viaEdgeState = baseGraph.getEdgeIteratorState(p.first.getViaEdges().get(0), Integer.MIN_VALUE); - EdgeIteratorState artificialEdgeState = baseGraph.edge(viaEdgeState.getBaseNode(), viaEdgeState.getAdjNode()) - .setFlags(viaEdgeState.getFlags()) - .setWayGeometry(viaEdgeState.fetchWayGeometry(FetchMode.PILLAR_ONLY)) - .setDistance(viaEdgeState.getDistance()) - .setKeyValues(viaEdgeState.getKeyValues()); - artificialEdge = artificialEdgeState.getEdge(); - artificialEdgesByEdges.put(viaEdge, artificialEdge); + this.turnRestrictionEncs = turnRestrictionEncs; + } + + public static Restriction createViaNodeRestriction(int fromEdge, int viaNode, int toEdge) { + return new Restriction(IntArrayList.from(fromEdge, toEdge), viaNode); + } + + public static Restriction createViaEdgeRestriction(IntArrayList edges) { + if (edges.size() < 3) + throw new IllegalArgumentException("Via-edge restrictions must have at least three edges, but got: " + edges.size()); + return new Restriction(edges, -1); + } + + public void setRestrictions(List restrictions, List encBits) { + if (restrictions.size() != encBits.size()) + throw new IllegalArgumentException("There must be as many encBits as restrictions. Got: " + encBits.size() + " and " + restrictions.size()); + List internalRestrictions = restrictions.stream().map(this::convertToInternal).toList(); + disableRedundantRestrictions(internalRestrictions, encBits); + LongIntMap artificialEdgeKeysByIncViaPairs = new LongIntScatterMap(); + IntObjectMap artificialEdgesByEdge = new IntObjectScatterMap<>(); + for (int i = 0; i < internalRestrictions.size(); i++) { + if (encBits.get(i).cardinality() < 1) continue; + InternalRestriction restriction = internalRestrictions.get(i); + if (restriction.getEdgeKeys().size() < 3) + continue; + int incomingEdge = restriction.getFromEdge(); + for (int j = 1; j < restriction.getEdgeKeys().size() - 1; ++j) { + int viaEdgeKey = restriction.getEdgeKeys().get(j); + long key = BitUtil.LITTLE.toLong(incomingEdge, viaEdgeKey); + int artificialEdgeKey; + if (artificialEdgeKeysByIncViaPairs.containsKey(key)) { + artificialEdgeKey = artificialEdgeKeysByIncViaPairs.get(key); + } else { + int viaEdge = GHUtility.getEdgeFromEdgeKey(viaEdgeKey); + EdgeIteratorState artificialEdgeState = baseGraph.copyEdge(viaEdge, true); + int artificialEdge = artificialEdgeState.getEdge(); + if (artificialEdgesByEdge.containsKey(viaEdge)) { + IntSet artificialEdges = artificialEdgesByEdge.get(viaEdge); + artificialEdges.forEach((IntProcedure) a -> { + for (BooleanEncodedValue turnRestrictionEnc : turnRestrictionEncs) + restrictTurnsBetweenEdges(turnRestrictionEnc, artificialEdgeState, a); + }); + artificialEdges.add(artificialEdge); + } else { + IntSet artificialEdges = new IntScatterSet(); + artificialEdges.add(artificialEdge); + artificialEdgesByEdge.put(viaEdge, artificialEdges); + } + for (BooleanEncodedValue turnRestrictionEnc : turnRestrictionEncs) + restrictTurnsBetweenEdges(turnRestrictionEnc, artificialEdgeState, viaEdge); + artificialEdgeKey = artificialEdgeState.getEdgeKey(); + if (baseGraph.getEdgeIteratorStateForKey(viaEdgeKey).get(REVERSE_STATE)) + artificialEdgeKey = GHUtility.reverseEdgeKey(artificialEdgeKey); + artificialEdgeKeysByIncViaPairs.put(key, artificialEdgeKey); } + restriction.actualEdgeKeys.set(j, artificialEdgeKey); + incomingEdge = GHUtility.getEdgeFromEdgeKey(artificialEdgeKey); } } - } - - private void addViaWayRestrictions(List> restrictions, BooleanEncodedValue turnRestrictionEnc) { - IntSet directedViaEdgesUsedByRestrictions = new IntHashSet(); - for (Pair p : restrictions) { - if (!p.first.isViaWayRestriction()) continue; - if (ignoreViaWayRestriction(p)) continue; - final int fromEdge = p.first.getFromEdges().get(0); - final int viaEdge = p.first.getViaEdges().get(0); - final int toEdge = p.first.getToEdges().get(0); - final int artificialVia = artificialEdgesByEdges.getOrDefault(viaEdge, viaEdge); - if (artificialVia == viaEdge) - throw new IllegalArgumentException("There should be an artificial edge for every via edge of a way restriction"); - - // With a single artificial edge per original edge there can only be one restriction - // that uses this edge as via-member **per direction**, see #2907. - // We can use the two via node ids to determine the via-edge direction in the restriction: - if (p.first.getViaEdges().size() != 1) - throw new IllegalStateException("At this point we assumed we were dealing with single via-way restrictions only"); - if (viaEdge == 0 && directedViaEdgesUsedByRestrictions.contains(viaEdge)) - // This approach does not work for edge 0... - throw new IllegalStateException("We cannot deal with multiple via-way restrictions if the via-edge is edge 0"); - final int directedViaEdge = viaEdge * (p.first.getViaNodes().get(0) < p.first.getViaNodes().get(1) ? +1 : -1); - if (!directedViaEdgesUsedByRestrictions.add(directedViaEdge)) - throw new IllegalStateException("We cannot deal with multiple via-way restrictions that use the same via edge in the same direction"); - - final int artificialFrom = artificialEdgesByEdges.getOrDefault(fromEdge, fromEdge); - final int artificialTo = artificialEdgesByEdges.getOrDefault(toEdge, toEdge); - final int fromToViaNode = p.first.getViaNodes().get(0); - final int viaToToNode = p.first.getViaNodes().get(1); - - // never turn between an artificial edge and its corresponding real edge - restrictTurn(turnRestrictionEnc, artificialVia, fromToViaNode, viaEdge); - restrictTurn(turnRestrictionEnc, viaEdge, fromToViaNode, artificialVia); - restrictTurn(turnRestrictionEnc, artificialVia, viaToToNode, viaEdge); - restrictTurn(turnRestrictionEnc, viaEdge, viaToToNode, artificialVia); - - if (p.second == NO) { - // This is how we implement via-way NO restrictions: we deny turning from the from-edge onto the via-edge, - // but allow turning onto the artificial edge instead. Then we deny turning from the artificial edge onto - // the to edge. - restrictTurn(turnRestrictionEnc, fromEdge, fromToViaNode, viaEdge); - restrictTurn(turnRestrictionEnc, artificialVia, viaToToNode, toEdge); - } else if (p.second == ONLY) { - // For via-way ONLY restrictions we have to turn from the from-edge onto the via-edge and from the via-edge - // onto the to-edge, but only if we actually start at the from-edge. Therefore we enforce turning onto - // the artificial via-edge when we are coming from the from-edge and only allow turning onto the to-edge - // when coming from the artificial via-edge. - EdgeIterator iter = edgeExplorer.setBaseNode(fromToViaNode); - while (iter.next()) - if (iter.getEdge() != fromEdge && iter.getEdge() != artificialVia) - restrictTurn(turnRestrictionEnc, fromEdge, fromToViaNode, iter.getEdge()); - iter = edgeExplorer.setBaseNode(viaToToNode); - while (iter.next()) - if (iter.getEdge() != artificialVia && iter.getEdge() != toEdge) - restrictTurn(turnRestrictionEnc, artificialVia, viaToToNode, iter.getEdge()); + artificialEdgeKeysByIncViaPairs.forEach((LongIntProcedure) (incViaPair, artificialEdgeKey) -> { + int incomingEdge = BitUtil.LITTLE.getIntLow(incViaPair); + int viaEdgeKey = BitUtil.LITTLE.getIntHigh(incViaPair); + int viaEdge = GHUtility.getEdgeFromEdgeKey(viaEdgeKey); + int node = baseGraph.getEdgeIteratorStateForKey(viaEdgeKey).getBaseNode(); + // we restrict turning onto the original edge and all artificial edges except the one we created for this in-edge + // i.e. we force turning onto the artificial edge we created for this in-edge + for (BooleanEncodedValue turnRestrictionEnc : turnRestrictionEncs) + restrictTurn(turnRestrictionEnc, incomingEdge, node, viaEdge); + IntSet artificialEdges = artificialEdgesByEdge.get(viaEdge); + artificialEdges.forEach((IntProcedure) a -> { + if (a != GHUtility.getEdgeFromEdgeKey(artificialEdgeKey)) + for (BooleanEncodedValue turnRestrictionEnc : turnRestrictionEncs) + restrictTurn(turnRestrictionEnc, incomingEdge, node, a); + }); + }); + for (int i = 0; i < internalRestrictions.size(); i++) { + if (encBits.get(i).cardinality() < 1) continue; + InternalRestriction restriction = internalRestrictions.get(i); + if (restriction.getEdgeKeys().size() < 3) { + IntSet fromEdges = artificialEdgesByEdge.getOrDefault(restriction.getFromEdge(), new IntScatterSet()); + fromEdges.add(restriction.getFromEdge()); + IntSet toEdges = artificialEdgesByEdge.getOrDefault(restriction.getToEdge(), new IntScatterSet()); + toEdges.add(restriction.getToEdge()); + for (int j = 0; j < turnRestrictionEncs.size(); j++) { + BooleanEncodedValue turnRestrictionEnc = turnRestrictionEncs.get(j); + if (encBits.get(i).get(j)) { + fromEdges.forEach((IntProcedure) from -> toEdges.forEach((IntProcedure) to -> { + restrictTurn(turnRestrictionEnc, from, restriction.getViaNodes().get(0), to); + })); + } + } } else { - throw new IllegalArgumentException("Unexpected restriction type: " + p.second); + int viaEdgeKey = restriction.getActualEdgeKeys().get(restriction.getActualEdgeKeys().size() - 2); + int viaEdge = GHUtility.getEdgeFromEdgeKey(viaEdgeKey); + int node = baseGraph.getEdgeIteratorStateForKey(viaEdgeKey).getAdjNode(); + // For via-edge restrictions we deny turning from the from-edge onto the via-edge, + // but allow turning onto the artificial edge(s) instead (see above). Then we deny + // turning from the artificial edge onto the to-edge here. + for (int j = 0; j < turnRestrictionEncs.size(); j++) { + BooleanEncodedValue turnRestrictionEnc = turnRestrictionEncs.get(j); + if (encBits.get(i).get(j)) { + restrictTurn(turnRestrictionEnc, viaEdge, node, restriction.getToEdge()); + // also restrict the turns to the artificial edges corresponding to the to-edge + artificialEdgesByEdge.getOrDefault(restriction.getToEdge(), EMPTY_SET).forEach( + (IntProcedure) toEdge -> restrictTurn(turnRestrictionEnc, viaEdge, node, toEdge) + ); + } + } } + } + } - // this is important for overlapping restrictions - if (artificialFrom != fromEdge) - restrictTurn(turnRestrictionEnc, artificialFrom, fromToViaNode, artificialVia); - if (artificialTo != toEdge) - restrictTurn(turnRestrictionEnc, artificialVia, viaToToNode, artificialTo); - } - } - - private void addViaNodeRestrictions(List> restrictions, BooleanEncodedValue turnRestrictionEnc) { - for (Pair p : restrictions) { - if (p.first.isViaWayRestriction()) continue; - final int viaNode = p.first.getViaNodes().get(0); - for (IntCursor fromEdgeCursor : p.first.getFromEdges()) { - for (IntCursor toEdgeCursor : p.first.getToEdges()) { - final int fromEdge = fromEdgeCursor.value; - final int toEdge = toEdgeCursor.value; - final int artificialFrom = artificialEdgesByEdges.getOrDefault(fromEdge, fromEdge); - final int artificialTo = artificialEdgesByEdges.getOrDefault(toEdge, toEdge); - if (p.second == NO) { - restrictTurn(turnRestrictionEnc, fromEdge, viaNode, toEdge); - // we also need to restrict this term in case there are artificial edges for the from- and/or to-edge - if (artificialFrom != fromEdge) - restrictTurn(turnRestrictionEnc, artificialFrom, viaNode, toEdge); - if (artificialTo != toEdge) - restrictTurn(turnRestrictionEnc, fromEdge, viaNode, artificialTo); - if (artificialFrom != fromEdge && artificialTo != toEdge) - restrictTurn(turnRestrictionEnc, artificialFrom, viaNode, artificialTo); - } else if (p.second == ONLY) { - // we need to restrict all turns except the one, but that also means not restricting the - // artificial counterparts of these turns, if they exist. - // we do not explicitly restrict the U-turn from the from-edge back to the from-edge though. - EdgeIterator iter = edgeExplorer.setBaseNode(viaNode); - while (iter.next()) { - if (iter.getEdge() != fromEdge && iter.getEdge() != toEdge && iter.getEdge() != artificialTo) - restrictTurn(turnRestrictionEnc, fromEdge, viaNode, iter.getEdge()); - // and the same for the artificial edge belonging to the from-edge if it exists - if (fromEdge != artificialFrom && iter.getEdge() != artificialFrom && iter.getEdge() != toEdge && iter.getEdge() != artificialTo) - restrictTurn(turnRestrictionEnc, artificialFrom, viaNode, iter.getEdge()); - } + private void disableRedundantRestrictions(List restrictions, List encBits) { + for (int encIdx = 0; encIdx < turnRestrictionEncs.size(); encIdx++) { + // first we disable all duplicates + Set uniqueRestrictions = new HashSet<>(); + for (int i = 0; i < restrictions.size(); i++) { + if (!encBits.get(i).get(encIdx)) + continue; + if (!uniqueRestrictions.add(restrictions.get(i))) + encBits.get(i).clear(encIdx); + } + // build an index of restrictions to quickly find all restrictions containing a given edge key + IntObjectScatterMap> restrictionsByEdgeKeys = new IntObjectScatterMap<>(); + for (int i = 0; i < restrictions.size(); i++) { + if (!encBits.get(i).get(encIdx)) + continue; + InternalRestriction restriction = restrictions.get(i); + for (IntCursor edgeKey : restriction.edgeKeys) { + int idx = restrictionsByEdgeKeys.indexOf(edgeKey.value); + if (idx < 0) { + List list = new ArrayList<>(); + list.add(restriction); + restrictionsByEdgeKeys.indexInsert(idx, edgeKey.value, list); } else { - throw new IllegalArgumentException("Unexpected restriction type: " + p.second); + restrictionsByEdgeKeys.indexGet(idx).add(restriction); } } } + // Only keep restrictions that do not contain another restriction. For example, it would be unnecessary to restrict + // 6-8-2 when 6-8 is restricted already + for (int i = 0; i < restrictions.size(); i++) { + if (!encBits.get(i).get(encIdx)) + continue; + if (containsAnotherRestriction(restrictions.get(i), restrictionsByEdgeKeys)) + encBits.get(i).clear(encIdx); + } } } - public IntIntMap getArtificialEdgesByEdges() { - return artificialEdgesByEdges; + private boolean containsAnotherRestriction(InternalRestriction restriction, IntObjectMap> restrictionsByEdgeKeys) { + for (IntCursor edgeKey : restriction.edgeKeys) { + List restrictionsWithThisEdgeKey = restrictionsByEdgeKeys.get(edgeKey.value); + for (InternalRestriction r : restrictionsWithThisEdgeKey) { + if (r == restriction) continue; + if (r.equals(restriction)) + throw new IllegalStateException("Equal restrictions should have already been filtered out here!"); + if (isSubsetOf(r.edgeKeys, restriction.edgeKeys)) + return true; + } + } + return false; + } + + private static boolean isSubsetOf(IntArrayList candidate, IntArrayList array) { + if (candidate.size() > array.size()) + return false; + for (int i = 0; i <= array.size() - candidate.size(); i++) { + boolean isSubset = true; + for (int j = 0; j < candidate.size(); j++) { + if (candidate.get(j) != array.get(i + j)) { + isSubset = false; + break; + } + } + if (isSubset) + return true; + } + return false; + } + + private void restrictTurnsBetweenEdges(BooleanEncodedValue turnRestrictionEnc, EdgeIteratorState edgeState, int otherEdge) { + restrictTurn(turnRestrictionEnc, otherEdge, edgeState.getBaseNode(), edgeState.getEdge()); + restrictTurn(turnRestrictionEnc, edgeState.getEdge(), edgeState.getBaseNode(), otherEdge); + restrictTurn(turnRestrictionEnc, otherEdge, edgeState.getAdjNode(), edgeState.getEdge()); + restrictTurn(turnRestrictionEnc, edgeState.getEdge(), edgeState.getAdjNode(), otherEdge); + } + + private InternalRestriction convertToInternal(Restriction restriction) { + IntArrayList edges = restriction.edges; + if (edges.size() < 2) + throw new IllegalArgumentException("Invalid restriction, there must be at least two edges"); + else if (edges.size() == 2) { + int fromKey = baseGraph.getEdgeIteratorState(edges.get(0), restriction.viaNode).getEdgeKey(); + int toKey = baseGraph.getEdgeIteratorState(edges.get(1), restriction.viaNode).getReverseEdgeKey(); + return new InternalRestriction(IntArrayList.from(restriction.viaNode), IntArrayList.from(fromKey, toKey)); + } else { + Pair p = findNodesAndEdgeKeys(baseGraph, edges); + p.first.remove(p.first.size() - 1); + return new InternalRestriction(p.first, p.second); + } + } + + private Pair findNodesAndEdgeKeys(BaseGraph baseGraph, IntArrayList edges) { + // we get a list of edges and need to find the directions of the edges and the connecting nodes + List> solutions = new ArrayList<>(); + findEdgeChain(baseGraph, edges, 0, IntArrayList.from(), IntArrayList.from(), solutions); + if (solutions.isEmpty()) { + throw new IllegalArgumentException("Disconnected edges: " + edges + " " + edgesToLocationString(baseGraph, edges)); + } else if (solutions.size() > 1) { + throw new IllegalArgumentException("Ambiguous edge restriction: " + edges + " " + edgesToLocationString(baseGraph, edges)); + } else { + return solutions.get(0); + } + } + + private static String edgesToLocationString(BaseGraph baseGraph, IntArrayList edges) { + return Arrays.stream(edges.buffer, 0, edges.size()).mapToObj(e -> baseGraph.getEdgeIteratorState(e, Integer.MIN_VALUE).fetchWayGeometry(FetchMode.ALL)) + .toList().toString(); + } + + private void findEdgeChain(BaseGraph baseGraph, IntArrayList edges, int index, IntArrayList nodes, IntArrayList edgeKeys, List> solutions) { + if (index == edges.size()) { + solutions.add(new Pair<>(new IntArrayList(nodes), new IntArrayList(edgeKeys))); + return; + } + EdgeIteratorState edgeState = baseGraph.getEdgeIteratorState(edges.get(index), Integer.MIN_VALUE); + if (index == 0 || edgeState.getBaseNode() == nodes.get(nodes.size() - 1)) { + nodes.add(edgeState.getAdjNode()); + edgeKeys.add(edgeState.getEdgeKey()); + findEdgeChain(baseGraph, edges, index + 1, nodes, edgeKeys, solutions); + nodes.elementsCount--; + edgeKeys.elementsCount--; + } + if (index == 0 || edgeState.getAdjNode() == nodes.get(nodes.size() - 1)) { + nodes.add(edgeState.getBaseNode()); + edgeKeys.add(edgeState.getReverseEdgeKey()); + findEdgeChain(baseGraph, edges, index + 1, nodes, edgeKeys, solutions); + nodes.elementsCount--; + edgeKeys.elementsCount--; + } } private void restrictTurn(BooleanEncodedValue turnRestrictionEnc, int fromEdge, int viaNode, int toEdge) { @@ -200,15 +295,77 @@ private void restrictTurn(BooleanEncodedValue turnRestrictionEnc, int fromEdge, baseGraph.getTurnCostStorage().set(turnRestrictionEnc, fromEdge, viaNode, toEdge, true); } - private static boolean ignoreViaWayRestriction(Pair p) { - // todo: how frequent are these? - if (p.first.getViaEdges().size() > 1) - // no multi-restrictions yet - return true; - if (p.first.getFromEdges().size() > 1 || p.first.getToEdges().size() > 1) - // no multi-from or -to yet - return true; - return false; + public static BitSet copyEncBits(BitSet encBits) { + return new BitSet(Arrays.copyOf(encBits.bits, encBits.bits.length), encBits.wlen); } + public static class Restriction { + public final IntArrayList edges; + private final int viaNode; + + private Restriction(IntArrayList edges, int viaNode) { + this.edges = edges; + this.viaNode = viaNode; + } + + @Override + public String toString() { + return "edges: " + edges.toString() + ", viaNode: " + viaNode; + } + } + + private static class InternalRestriction { + private final IntArrayList viaNodes; + private final IntArrayList edgeKeys; + private final IntArrayList actualEdgeKeys; + + public InternalRestriction(IntArrayList viaNodes, IntArrayList edgeKeys) { + this.edgeKeys = edgeKeys; + this.viaNodes = viaNodes; + this.actualEdgeKeys = ArrayUtil.constant(edgeKeys.size(), -1); + this.actualEdgeKeys.set(0, edgeKeys.get(0)); + this.actualEdgeKeys.set(edgeKeys.size() - 1, edgeKeys.get(edgeKeys.size() - 1)); + } + + public IntArrayList getViaNodes() { + return viaNodes; + } + + public int getFromEdge() { + return GHUtility.getEdgeFromEdgeKey(edgeKeys.get(0)); + } + + public IntArrayList getEdgeKeys() { + return edgeKeys; + } + + public IntArrayList getActualEdgeKeys() { + return actualEdgeKeys; + } + + public int getToEdge() { + return GHUtility.getEdgeFromEdgeKey(edgeKeys.get(edgeKeys.size() - 1)); + } + + @Override + public int hashCode() { + return 31 * viaNodes.hashCode() + edgeKeys.hashCode(); + } + + @Override + public boolean equals(Object obj) { + // this is actually needed, because we build a Set of InternalRestrictions to remove duplicates + // no need to compare the actualEdgeKeys + if (!(obj instanceof InternalRestriction)) return false; + return ((InternalRestriction) obj).viaNodes.equals(viaNodes) && ((InternalRestriction) obj).edgeKeys.equals(edgeKeys); + } + + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + for (int i = 0; i < viaNodes.size(); i++) + result.append(GHUtility.getEdgeFromEdgeKey(edgeKeys.get(i))).append("-(").append(viaNodes.get(i)).append(")-"); + return result + "" + GHUtility.getEdgeFromEdgeKey(edgeKeys.get(edgeKeys.size() - 1)); + } + } } diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAccessParser.java deleted file mode 100644 index e68a88bb171..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAccessParser.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.routing.ev.VehicleAccess; -import com.graphhopper.storage.IntsRef; - -/** - * Access parser (boolean) for the 'roads' vehicle. Not to be confused with OSMRoadAccessParser that fills road_access - * enum (for car). - */ -public class RoadsAccessParser implements TagParser { - private final BooleanEncodedValue accessEnc; - - public RoadsAccessParser(EncodedValueLookup lookup) { - this(lookup.getBooleanEncodedValue(VehicleAccess.key("roads"))); - } - - public RoadsAccessParser(BooleanEncodedValue accessEnc) { - this.accessEnc = accessEnc; - } - - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - } - - @Override - public String toString() { - return accessEnc.getName(); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAverageSpeedParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAverageSpeedParser.java deleted file mode 100644 index 59b85b16dcc..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/RoadsAverageSpeedParser.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.routing.ev.VehicleSpeed; -import com.graphhopper.storage.IntsRef; - -public class RoadsAverageSpeedParser implements TagParser { - private final DecimalEncodedValue avgSpeedEnc; - private final double maxPossibleSpeed; - - public RoadsAverageSpeedParser(EncodedValueLookup lookup) { - this(lookup.getDecimalEncodedValue(VehicleSpeed.key("roads"))); - } - - public RoadsAverageSpeedParser(DecimalEncodedValue avgSpeedEnc) { - this.avgSpeedEnc = avgSpeedEnc; - this.maxPossibleSpeed = this.avgSpeedEnc.getMaxStorableDecimal(); - } - - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { - // let's make it high and let it be reduced in the custom model - avgSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, maxPossibleSpeed); - if (avgSpeedEnc.isStoreTwoDirections()) - avgSpeedEnc.setDecimal(true, edgeId, edgeIntAccess, maxPossibleSpeed); - } -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java b/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java deleted file mode 100644 index 2a0a676cd3c..00000000000 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/TagParserFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.util.PMap; - -public interface TagParserFactory { - TagParser create(EncodedValueLookup lookup, String name, PMap properties); -} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/helpers/OSMValueExtractor.java b/core/src/main/java/com/graphhopper/routing/util/parsers/helpers/OSMValueExtractor.java index 04b8eaf9f97..08bf8474973 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/helpers/OSMValueExtractor.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/helpers/OSMValueExtractor.java @@ -3,9 +3,6 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.MaxSpeed; -import com.graphhopper.util.DistanceCalcEarth; -import com.graphhopper.util.Helper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +27,7 @@ private OSMValueExtractor() { } public static void extractTons(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, DecimalEncodedValue valueEncoder, List keys) { - final String rawValue = way.getFirstPriorityTag(keys); + final String rawValue = way.getFirstValue(keys); double value = stringToTons(rawValue); if (Double.isNaN(value)) value = Double.POSITIVE_INFINITY; @@ -55,7 +52,7 @@ public static double conditionalWeightToTons(String value) { } if (index > 0) { int lastIndex = value.indexOf(')', index); // (value) or value - if (lastIndex < 0) lastIndex = value.length() - 1; + if (lastIndex < 0) lastIndex = value.length(); if (lastIndex > index) return OSMValueExtractor.stringToTons(value.substring(index, lastIndex)); } @@ -93,7 +90,7 @@ public static double stringToTons(String value) { } public static void extractMeter(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, DecimalEncodedValue valueEncoder, List keys) { - final String rawValue = way.getFirstPriorityTag(keys); + final String rawValue = way.getFirstValue(keys); double value = stringToMeter(rawValue); if (Double.isNaN(value)) value = Double.POSITIVE_INFINITY; @@ -168,52 +165,4 @@ public static boolean isInvalidValue(String value) { || value.contains(","); } - /** - * @return the speed in km/h - */ - public static double stringToKmh(String str) { - if (Helper.isEmpty(str)) - return Double.NaN; - - if ("walk".equals(str)) - return 6; - - // on some German autobahns and a very few other places - if ("none".equals(str)) - return MaxSpeed.UNLIMITED_SIGN_SPEED; - - int mpInteger = str.indexOf("mp"); - int knotInteger = str.indexOf("knots"); - int kmInteger = str.indexOf("km"); - int kphInteger = str.indexOf("kph"); - - double factor; - if (mpInteger > 0) { - str = str.substring(0, mpInteger).trim(); - factor = DistanceCalcEarth.KM_MILE; - } else if (knotInteger > 0) { - str = str.substring(0, knotInteger).trim(); - factor = 1.852; // see https://en.wikipedia.org/wiki/Knot_%28unit%29#Definitions - } else { - if (kmInteger > 0) { - str = str.substring(0, kmInteger).trim(); - } else if (kphInteger > 0) { - str = str.substring(0, kphInteger).trim(); - } - factor = 1; - } - - double value; - try { - value = Integer.parseInt(str) * factor; - } catch (Exception ex) { - return Double.NaN; - } - - if (value <= 0) { - return Double.NaN; - } - - return value; - } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java deleted file mode 100644 index 9d530a09ac7..00000000000 --- a/core/src/main/java/com/graphhopper/routing/weighting/AbstractWeighting.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.weighting; - -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.FetchMode; - -import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; - -/** - * @author Peter Karich - */ -public abstract class AbstractWeighting implements Weighting { - protected final BooleanEncodedValue accessEnc; - protected final DecimalEncodedValue speedEnc; - private final TurnCostProvider turnCostProvider; - - protected AbstractWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, TurnCostProvider turnCostProvider) { - if (!isValidName(getName())) - throw new IllegalStateException("Not a valid name for a Weighting: " + getName()); - this.accessEnc = accessEnc; - this.speedEnc = speedEnc; - this.turnCostProvider = turnCostProvider; - } - - @Override - public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { - if (reverse && !edgeState.getReverse(accessEnc) || !reverse && !edgeState.get(accessEnc)) - throw new IllegalStateException("Calculating time should not require to read speed from edge in wrong direction. " + - "(" + edgeState.getBaseNode() + " - " + edgeState.getAdjNode() + ") " - + edgeState.fetchWayGeometry(FetchMode.ALL) + ", dist: " + edgeState.getDistance() + " " - + "Reverse:" + reverse + ", fwd:" + edgeState.get(accessEnc) + ", bwd:" + edgeState.getReverse(accessEnc) + ", fwd-speed: " + edgeState.get(speedEnc) + ", bwd-speed: " + edgeState.getReverse(speedEnc)); - - double speed = reverse ? edgeState.getReverse(speedEnc) : edgeState.get(speedEnc); - if (Double.isInfinite(speed) || Double.isNaN(speed) || speed < 0) - throw new IllegalStateException("Invalid speed stored in edge! " + speed); - if (speed == 0) - throw new IllegalStateException("Speed cannot be 0 for unblocked edge, use access properties to mark edge blocked! Should only occur for shortest path calculation. See #242."); - - return Math.round(edgeState.getDistance() / speed * 3.6 * 1000); - } - - @Override - public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { - return turnCostProvider.calcTurnWeight(inEdge, viaNode, outEdge); - } - - @Override - public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { - return turnCostProvider.calcTurnMillis(inEdge, viaNode, outEdge); - } - - @Override - public boolean hasTurnCosts() { - return turnCostProvider != NO_TURN_COST_PROVIDER; - } - - static boolean isValidName(String name) { - if (name == null || name.isEmpty()) - return false; - - return name.matches("[\\|_a-z]+"); - } - - @Override - public String toString() { - return getName() + "|" + speedEnc.getName(); - } - -} diff --git a/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java b/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java index 299dd04a658..72b641bdee5 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java @@ -19,62 +19,72 @@ package com.graphhopper.routing.weighting; import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.routing.weighting.custom.CustomWeighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.EdgeIterator; +import com.graphhopper.util.TurnCostsConfig; -import static com.graphhopper.routing.weighting.Weighting.INFINITE_U_TURN_COSTS; +import static com.graphhopper.util.TurnCostsConfig.INFINITE_U_TURN_COSTS; public class DefaultTurnCostProvider implements TurnCostProvider { private final BooleanEncodedValue turnRestrictionEnc; private final TurnCostStorage turnCostStorage; private final int uTurnCostsInt; private final double uTurnCosts; + private final BaseGraph graph; + private final EdgeIntAccess edgeIntAccess; + private final CustomWeighting.TurnPenaltyMapping turnPenaltyMapping; - public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, TurnCostStorage turnCostStorage) { - this(turnRestrictionEnc, turnCostStorage, Weighting.INFINITE_U_TURN_COSTS); - } - - /** - * @param uTurnCosts the costs of a u-turn in seconds, for {@link Weighting#INFINITE_U_TURN_COSTS} the u-turn costs - * will be infinite - */ - public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, TurnCostStorage turnCostStorage, int uTurnCosts) { - if (uTurnCosts < 0 && uTurnCosts != INFINITE_U_TURN_COSTS) { + public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, + Graph graph, TurnCostsConfig tcConfig, + CustomWeighting.TurnPenaltyMapping turnPenaltyMapping) { + this.uTurnCostsInt = tcConfig.getUTurnCosts(); + if (uTurnCostsInt < 0 && uTurnCostsInt != INFINITE_U_TURN_COSTS) { throw new IllegalArgumentException("u-turn costs must be positive, or equal to " + INFINITE_U_TURN_COSTS + " (=infinite costs)"); } - this.uTurnCostsInt = uTurnCosts; - this.uTurnCosts = uTurnCosts < 0 ? Double.POSITIVE_INFINITY : uTurnCosts; - if (turnCostStorage == null) { + this.uTurnCosts = uTurnCostsInt < 0 ? Double.POSITIVE_INFINITY : uTurnCostsInt; + if (graph.getTurnCostStorage() == null) { throw new IllegalArgumentException("No storage set to calculate turn weight"); } // if null the TurnCostProvider can be still useful for edge-based routing this.turnRestrictionEnc = turnRestrictionEnc; - this.turnCostStorage = turnCostStorage; - } + this.turnCostStorage = graph.getTurnCostStorage(); - public BooleanEncodedValue getTurnRestrictionEnc() { - return turnRestrictionEnc; + this.graph = graph.getBaseGraph(); + this.edgeIntAccess = graph.getBaseGraph().getEdgeAccess(); + + this.turnPenaltyMapping = turnPenaltyMapping; } @Override - public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) { - if (!EdgeIterator.Edge.isValid(edgeFrom) || !EdgeIterator.Edge.isValid(edgeTo)) { + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + if (!EdgeIterator.Edge.isValid(inEdge) || !EdgeIterator.Edge.isValid(outEdge)) { return 0; } - double tCost = 0; - if (edgeFrom == edgeTo) { + + if (inEdge == outEdge) { // note that the u-turn costs overwrite any turn costs set in TurnCostStorage - tCost = uTurnCosts; - } else { - if (turnRestrictionEnc != null) - tCost = turnCostStorage.get(turnRestrictionEnc, edgeFrom, nodeVia, edgeTo) ? Double.POSITIVE_INFINITY : 0; + return uTurnCosts; + } else if (turnRestrictionEnc != null) { + if (turnCostStorage.get(turnRestrictionEnc, inEdge, viaNode, outEdge)) + return Double.POSITIVE_INFINITY; } - return tCost; + if (turnPenaltyMapping != null) + return turnPenaltyMapping.get(graph, edgeIntAccess, inEdge, viaNode, outEdge); + return 0; } @Override public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { - return (long) (1000 * calcTurnWeight(inEdge, viaNode, outEdge)); + // Making a proper assumption about the turn time is very hard. Assuming zero is the + // simplest way to deal with this. This also means the u-turn time is zero. Provided that + // the u-turn weight is large enough, u-turns only occur in special situations like curbsides + // pointing to the end of dead-end streets where it is unclear if a finite u-turn time would + // be a good choice. + return 0; } @Override diff --git a/core/src/main/java/com/graphhopper/routing/weighting/FastestWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/FastestWeighting.java deleted file mode 100644 index df4ae4c164e..00000000000 --- a/core/src/main/java/com/graphhopper/routing/weighting/FastestWeighting.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.weighting; - -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.EnumEncodedValue; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.PMap; -import com.graphhopper.util.Parameters.Routing; - -import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; - -/** - * Calculates the fastest route with the specified vehicle (VehicleEncoder). Calculates the weight - * in seconds. - *

- * - * @author Peter Karich - */ -public class FastestWeighting extends AbstractWeighting { - public static String DESTINATION_FACTOR = "road_access_destination_factor"; - public static String PRIVATE_FACTOR = "road_access_private_factor"; - /** - * Converting to seconds is not necessary but makes adding other penalties easier (e.g. turn - * costs or traffic light costs etc) - */ - protected final static double SPEED_CONV = 3.6; - private final double headingPenalty; - private final double maxSpeed; - private final EnumEncodedValue roadAccessEnc; - // this factor puts a penalty on roads with a "destination"-only or private access, see #733 and #1936 - private final double destinationPenalty, privatePenalty; - - public FastestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc) { - this(accessEnc, speedEnc, NO_TURN_COST_PROVIDER); - } - - public FastestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, TurnCostProvider turnCostProvider) { - this(accessEnc, speedEnc, null, new PMap(0), turnCostProvider); - } - - public FastestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, EnumEncodedValue roadAccessEnc, PMap map, TurnCostProvider turnCostProvider) { - super(accessEnc, speedEnc, turnCostProvider); - headingPenalty = map.getDouble(Routing.HEADING_PENALTY, Routing.DEFAULT_HEADING_PENALTY); - maxSpeed = speedEnc.getMaxOrMaxStorableDecimal() / SPEED_CONV; - - destinationPenalty = map.getDouble(DESTINATION_FACTOR, 1); - privatePenalty = map.getDouble(PRIVATE_FACTOR, 1); - // ensure that we do not need to change getMinWeight, i.e. both factors need to be >= 1 - checkBounds(DESTINATION_FACTOR, destinationPenalty, 1, 10); - checkBounds(PRIVATE_FACTOR, privatePenalty, 1, 10); - if (destinationPenalty > 1 || privatePenalty > 1) { - if (roadAccessEnc == null) - throw new IllegalArgumentException("road_access must not be null when destination or private penalties are > 1"); - this.roadAccessEnc = roadAccessEnc; - } else - this.roadAccessEnc = null; - } - - @Override - public double calcMinWeightPerDistance() { - return 1 / maxSpeed; - } - - @Override - public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { - if (reverse ? !edgeState.getReverse(accessEnc) : !edgeState.get(accessEnc)) - return Double.POSITIVE_INFINITY; - double speed = reverse ? edgeState.getReverse(speedEnc) : edgeState.get(speedEnc); - if (speed == 0) - return Double.POSITIVE_INFINITY; - - double time = edgeState.getDistance() / speed * SPEED_CONV; - if (roadAccessEnc != null) { - RoadAccess access = edgeState.get(roadAccessEnc); - if (access == RoadAccess.DESTINATION) - time *= destinationPenalty; - else if (access == RoadAccess.PRIVATE) - time *= privatePenalty; - } - // add direction penalties at start/stop/via points - boolean unfavoredEdge = edgeState.get(EdgeIteratorState.UNFAVORED_EDGE); - if (unfavoredEdge) - time += headingPenalty; - - return time; - } - - static double checkBounds(String key, double val, double from, double to) { - if (val < from || val > to) - throw new IllegalArgumentException(key + " has invalid range should be within [" + from + ", " + to + "]"); - - return val; - } - - @Override - public String getName() { - return "fastest"; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java index e8db6064533..64a5246d513 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/QueryGraphWeighting.java @@ -19,7 +19,13 @@ package com.graphhopper.routing.weighting; import com.carrotsearch.hppc.IntArrayList; +import com.carrotsearch.hppc.IntDoubleMap; +import com.carrotsearch.hppc.IntLongMap; import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.routing.querygraph.VirtualEdgeIterator; +import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.EdgeIteratorState; @@ -29,16 +35,22 @@ * edges will not be calculated correctly. */ public class QueryGraphWeighting implements Weighting { + private final BaseGraph graph; private final Weighting weighting; private final int firstVirtualNodeId; private final int firstVirtualEdgeId; private final IntArrayList closestEdges; + private final IntDoubleMap virtualWeightsByEdgeKey; + private final IntLongMap virtualTimesByEdgeKey; - public QueryGraphWeighting(Weighting weighting, int firstVirtualNodeId, int firstVirtualEdgeId, IntArrayList closestEdges) { + public QueryGraphWeighting(BaseGraph graph, Weighting weighting, IntArrayList closestEdges, IntDoubleMap virtualWeightsByEdgeKey, IntLongMap virtualTimesByEdgeKey) { + this.graph = graph; this.weighting = weighting; - this.firstVirtualNodeId = firstVirtualNodeId; - this.firstVirtualEdgeId = firstVirtualEdgeId; + this.firstVirtualNodeId = graph.getNodes(); + this.firstVirtualEdgeId = graph.getEdges(); this.closestEdges = closestEdges; + this.virtualWeightsByEdgeKey = virtualWeightsByEdgeKey; + this.virtualTimesByEdgeKey = virtualTimesByEdgeKey; } @Override @@ -48,6 +60,14 @@ public double calcMinWeightPerDistance() { @Override public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { + if (isVirtualEdge(edgeState.getEdge()) && !edgeState.get(EdgeIteratorState.UNFAVORED_EDGE)) { + if (edgeState instanceof VirtualEdgeIteratorState v) + return virtualWeightsByEdgeKey.get(reverse ? v.getReverseEdgeKey() : v.getEdgeKey()); + else if (edgeState instanceof VirtualEdgeIterator v) + return virtualWeightsByEdgeKey.get(reverse ? v.getReverseEdgeKey() : v.getEdgeKey()); + else + throw new IllegalStateException("Unexpected virtual edge state: " + edgeState); + } return weighting.calcEdgeWeight(edgeState, reverse); } @@ -67,15 +87,49 @@ public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { return 0; } } + return getMinWeightAndOriginalEdges(inEdge, viaNode, outEdge).minTurnWeight; + } + + private Result getMinWeightAndOriginalEdges(int inEdge, int viaNode, int outEdge) { // to calculate the actual turn costs or detect u-turns we need to look at the original edge of each virtual // edge, see #1593 - if (isVirtualEdge(inEdge)) { - inEdge = getOriginalEdge(inEdge); + Result result = new Result(); + if (isVirtualEdge(inEdge) && isVirtualEdge(outEdge)) { + EdgeExplorer innerExplorer = graph.createEdgeExplorer(); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(inEdge), p -> { + graph.forEdgeAndCopiesOfEdge(innerExplorer, viaNode, getOriginalEdge(outEdge), q -> { + double w = weighting.calcTurnWeight(p, viaNode, q); + if (w < result.minTurnWeight) { + result.origInEdge = p; + result.origOutEdge = q; + result.minTurnWeight = w; + } + }); + }); + } else if (isVirtualEdge(inEdge)) { + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(inEdge), e -> { + double w = weighting.calcTurnWeight(e, viaNode, outEdge); + if (w < result.minTurnWeight) { + result.origInEdge = e; + result.origOutEdge = outEdge; + result.minTurnWeight = w; + } + }); + } else if (isVirtualEdge(outEdge)) { + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), viaNode, getOriginalEdge(outEdge), e -> { + double w = weighting.calcTurnWeight(inEdge, viaNode, e); + if (w < result.minTurnWeight) { + result.origInEdge = inEdge; + result.origOutEdge = e; + result.minTurnWeight = w; + } + }); + } else { + result.origInEdge = inEdge; + result.origOutEdge = outEdge; + result.minTurnWeight = weighting.calcTurnWeight(inEdge, viaNode, outEdge); } - if (isVirtualEdge(outEdge)) { - outEdge = getOriginalEdge(outEdge); - } - return weighting.calcTurnWeight(inEdge, viaNode, outEdge); + return result; } private boolean isUTurn(int inEdge, int outEdge) { @@ -84,13 +138,28 @@ private boolean isUTurn(int inEdge, int outEdge) { @Override public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { + if (isVirtualEdge(edgeState.getEdge()) && !edgeState.get(EdgeIteratorState.UNFAVORED_EDGE)) { + if (edgeState instanceof VirtualEdgeIteratorState v) + return virtualTimesByEdgeKey.get(reverse ? v.getReverseEdgeKey() : v.getEdgeKey()); + else if (edgeState instanceof VirtualEdgeIterator v) + return virtualTimesByEdgeKey.get(reverse ? v.getReverseEdgeKey() : v.getEdgeKey()); + else + throw new IllegalStateException("Unexpected virtual edge state: " + edgeState); + } return weighting.calcEdgeMillis(edgeState, reverse); } @Override public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { - // todo: here we do not allow calculating turn weights that aren't turn times, also see #1590 - return (long) (1000 * calcTurnWeight(inEdge, viaNode, outEdge)); + if (isVirtualNode(viaNode)) + // see calcTurnWeight + return 0; + else { + // we want the turn time given by the actual weighting for the edges with minimum weight + // (the same ones that would be selected when routing) + Result result = getMinWeightAndOriginalEdges(inEdge, viaNode, outEdge); + return weighting.calcTurnMillis(result.origInEdge, viaNode, result.origOutEdge); + } } @Override @@ -119,4 +188,10 @@ private boolean isVirtualNode(int node) { private boolean isVirtualEdge(int edge) { return edge >= firstVirtualEdgeId; } + + private static class Result { + int origInEdge = -1; + int origOutEdge = -1; + double minTurnWeight = Double.POSITIVE_INFINITY; + } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/ShortestWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/ShortestWeighting.java deleted file mode 100644 index 360a5d76457..00000000000 --- a/core/src/main/java/com/graphhopper/routing/weighting/ShortestWeighting.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.weighting; - -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.util.EdgeIteratorState; - -/** - * Calculates the shortest route - independent of a vehicle as the calculation is based on the - * distance only. - * - * @author Peter Karich - */ -public class ShortestWeighting implements Weighting { - - final BooleanEncodedValue accessEnc; - final DecimalEncodedValue speedEnc; - final TurnCostProvider tcProvider; - - public ShortestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc) { - this(accessEnc, speedEnc, TurnCostProvider.NO_TURN_COST_PROVIDER); - } - - public ShortestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, TurnCostProvider tcProvider) { - this.accessEnc = accessEnc; - this.speedEnc = speedEnc; - this.tcProvider = tcProvider; - } - - @Override - public double calcMinWeightPerDistance() { - return 1; - } - - @Override - public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { - if (reverse ? !edgeState.getReverse(accessEnc) : !edgeState.get(accessEnc)) - return Double.POSITIVE_INFINITY; - return edgeState.getDistance(); - } - - @Override - public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { - double speed = reverse ? edgeState.getReverse(speedEnc) : edgeState.get(speedEnc); - return Math.round(edgeState.getDistance() / speed * 3.6 * 1000); - } - - @Override - public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { - return tcProvider.calcTurnWeight(inEdge, viaNode, outEdge); - } - - @Override - public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { - return tcProvider.calcTurnMillis(inEdge, viaNode, outEdge); - } - - @Override - public boolean hasTurnCosts() { - return tcProvider != TurnCostProvider.NO_TURN_COST_PROVIDER; - } - - @Override - public String getName() { - return "shortest"; - } -} diff --git a/core/src/main/java/com/graphhopper/routing/weighting/SpeedWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/SpeedWeighting.java index c8e793e2d63..447625557c9 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/SpeedWeighting.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/SpeedWeighting.java @@ -61,24 +61,27 @@ public SpeedWeighting(DecimalEncodedValue speedEnc, TurnCostProvider turnCostPro @Override public double calcMinWeightPerDistance() { - return 1 / speedEnc.getMaxStorableDecimal(); + return 10.0 / speedEnc.getMaxStorableDecimal(); } @Override public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { double speed = reverse ? edgeState.getReverse(speedEnc) : edgeState.get(speedEnc); if (speed == 0) return Double.POSITIVE_INFINITY; - return edgeState.getDistance() / speed; + return Weighting.roundWeight(10 * edgeState.getDistance() / speed); } @Override public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { - return (long) (1000 * calcEdgeWeight(edgeState, reverse)); + double speed = reverse ? edgeState.getReverse(speedEnc) : edgeState.get(speedEnc); + if (speed == 0) return Long.MAX_VALUE; + return (long) (1000 * (edgeState.getDistance() / speed)); } @Override public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { - return turnCostProvider.calcTurnWeight(inEdge, viaNode, outEdge); + double turnWeight = turnCostProvider.calcTurnWeight(inEdge, viaNode, outEdge); + return Weighting.roundWeight(10 * turnWeight); } @Override @@ -91,6 +94,11 @@ public boolean hasTurnCosts() { return turnCostProvider != TurnCostProvider.NO_TURN_COST_PROVIDER; } + @Override + public String toString() { + return getName(); + } + @Override public String getName() { return "speed"; diff --git a/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java b/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java index 999fa6c56e7..9b3c21d59ef 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/Weighting.java @@ -25,7 +25,6 @@ * @author Peter Karich */ public interface Weighting { - int INFINITE_U_TURN_COSTS = -1; /** * Used only for the heuristic estimation in A* @@ -44,7 +43,8 @@ public interface Weighting { * @param reverse if the specified edge is specified in reverse direction e.g. from the reverse * case of a bidirectional search. * @return the calculated weight with the specified velocity has to be in the range of 0 and - * +Infinity. Make sure your method does not return NaN which can e.g. occur for 0/0. + * +Infinity. GraphHopper expects weights to be whole numbers only. Consider using {@link Weighting#roundWeight(double)} + * to post-process all weights. Make sure your method does not return NaN which can e.g. occur for 0/0. */ double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse); @@ -67,4 +67,20 @@ public interface Weighting { String getName(); + static boolean isValidName(String name) { + if (name == null || name.isEmpty()) + return false; + + return name.matches("[\\|_a-z]+"); + } + + static double roundWeight(double w) { + assert !Double.isNaN(w) : "weights should not be NaN"; + assert w >= 0 : "weights should be >= 0, got: " + w; + if (Double.isInfinite(w)) return Double.POSITIVE_INFINITY; + if (w != 0 && w < 0.5) + // we round up to weight 1, because weight 0 introduces ambiguity for shortest paths + return 1; + return Math.round(w); + } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java index dc6318b61aa..c92b442f08f 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ClassHelper.java @@ -1,5 +1,5 @@ package com.graphhopper.routing.weighting.custom; -interface ClassHelper { +public interface ClassHelper { String getClassName(String encVal); } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java index e030898a167..772f608652f 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitor.java @@ -31,9 +31,9 @@ */ class ConditionalExpressionVisitor implements Visitor.AtomVisitor { - private static final Set allowedMethodParents = new HashSet<>(Arrays.asList("edge", "Math")); + private static final Set allowedMethodParents = new HashSet<>(Arrays.asList("edge", "Math", "country")); private static final Set allowedMethods = new HashSet<>(Arrays.asList("ordinal", "getDistance", "getName", - "contains", "sqrt", "abs")); + "contains", "sqrt", "abs", "isRightHandTraffic", "equals")); private final ParseResult result; private final TreeMap replacements = new TreeMap<>(); private final NameValidator variableValidator; @@ -104,6 +104,10 @@ public Boolean visitRvalue(Java.Rvalue rv) throws Exception { if (mi.arguments.length == 0) { result.guessedVariables.add(n.identifiers[0]); // return road_class return true; + } else if (mi.arguments.length == 1) { + // prev_street_name.equals(street_name) + result.guessedVariables.add(n.identifiers[0]); + return mi.arguments[0].accept(this); } } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java index 75e58035943..a2052a3b829 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomModelParser.java @@ -19,16 +19,16 @@ import com.graphhopper.json.Statement; import com.graphhopper.routing.ev.*; -import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import com.graphhopper.util.shapes.Polygon; import org.codehaus.commons.compiler.CompileException; import org.codehaus.commons.compiler.Location; import org.codehaus.commons.compiler.io.Readers; -import org.codehaus.janino.Scanner; import org.codehaus.janino.*; +import org.codehaus.janino.Scanner; import org.codehaus.janino.util.DeepCopier; import org.locationtech.jts.geom.Polygonal; import org.locationtech.jts.geom.prep.PreparedPolygon; @@ -37,11 +37,17 @@ import java.io.*; import java.util.*; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Function; + +import static com.graphhopper.json.Statement.Keyword.IF; public class CustomModelParser { private static final AtomicLong longVal = new AtomicLong(1); static final String IN_AREA_PREFIX = "in_"; static final String BACKWARD_PREFIX = "backward_"; + static final String PREV_PREFIX = "prev_"; + static final String CHANGE_ANGLE = "change_angle"; + static final String STREET_NAME = "street_name"; private static final boolean JANINO_DEBUG = Boolean.getBoolean(Scanner.SYSTEM_PROPERTY_SOURCE_DEBUGGING_ENABLE); private static final String SCRIPT_FILE_DIR = System.getProperty(Scanner.SYSTEM_PROPERTY_SOURCE_DEBUGGING_DIR, "./src/main/java/com/graphhopper/routing/weighting/custom"); @@ -50,7 +56,7 @@ public class CustomModelParser { // Use accessOrder==true to remove oldest accessed entry, not oldest inserted. private static final int CACHE_SIZE = Integer.getInteger("graphhopper.custom_weighting.cache_size", 1000); private static final Map> CACHE = Collections.synchronizedMap( - new LinkedHashMap>(CACHE_SIZE, 0.75f, true) { + new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) { protected boolean removeEldestEntry(Map.Entry eldest) { return size() > CACHE_SIZE; } @@ -66,35 +72,25 @@ private CustomModelParser() { // utility class } - public static CustomWeighting createWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, DecimalEncodedValue priorityEnc, - EncodedValueLookup lookup, TurnCostProvider turnCostProvider, CustomModel customModel) { + /** + * This method creates a weighting from a CustomModel that must limit the speed. Either as an + * unconditional statement { "if": "true", "limit_to": "car_average_speed" } or as + * an if-elseif-else group. + */ + public static CustomWeighting createWeighting(EncodedValueLookup lookup, TurnCostProvider turnCostProvider, CustomModel customModel) { if (customModel == null) throw new IllegalStateException("CustomModel cannot be null"); - CustomWeighting.Parameters parameters = createWeightingParameters(customModel, lookup, speedEnc, priorityEnc); - return new CustomWeighting(accessEnc, speedEnc, turnCostProvider, parameters); - } - - public static CustomWeighting createFastestWeighting(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, EncodingManager lookup) { - CustomModel cm = new CustomModel(); - return createWeighting(accessEnc, speedEnc, null, lookup, TurnCostProvider.NO_TURN_COST_PROVIDER, cm); + CustomWeighting.Parameters parameters = createWeightingParameters(customModel, lookup); + return new CustomWeighting(turnCostProvider, parameters); } /** - * This method compiles a new subclass of CustomWeightingHelper composed from the provided CustomModel caches this + * This method compiles a new subclass of CustomWeightingHelper composed of the provided CustomModel caches this * and returns an instance. - * - * @param priorityEnc can be null */ - public static CustomWeighting.Parameters createWeightingParameters(CustomModel customModel, EncodedValueLookup lookup, - DecimalEncodedValue avgSpeedEnc, - DecimalEncodedValue priorityEnc) { - - // if the same custom model is used with a different base profile we cannot use the cached version - String key = customModel.toString() + "," + avgSpeedEnc.getName() + "," + (priorityEnc == null ? "-" : priorityEnc.getName()); - if (key.length() > 100_000) - throw new IllegalArgumentException("Custom Model too big: " + key.length()); - + public static CustomWeighting.Parameters createWeightingParameters(CustomModel customModel, EncodedValueLookup lookup) { + String key = customModel.toString(); Class clazz = customModel.isInternal() ? INTERNAL_CACHE.get(key) : null; if (CACHE_SIZE > 0 && clazz == null) clazz = CACHE.get(key); @@ -116,10 +112,11 @@ public static CustomWeighting.Parameters createWeightingParameters(CustomModel c try { // The class does not need to be thread-safe as we create an instance per request CustomWeightingHelper prio = (CustomWeightingHelper) clazz.getDeclaredConstructor().newInstance(); - prio.init(customModel, lookup, avgSpeedEnc, priorityEnc, CustomModel.getAreasAsMap(customModel.getAreas())); + prio.init(customModel, lookup, CustomModel.getAreasAsMap(customModel.getAreas())); return new CustomWeighting.Parameters( prio::getSpeed, prio::calcMaxSpeed, prio::getPriority, prio::calcMaxPriority, + prio::getTurnPenalty, customModel.getDistanceInfluence() == null ? 0 : customModel.getDistanceInfluence(), customModel.getHeadingPenalty() == null ? Parameters.Routing.DEFAULT_HEADING_PENALTY : customModel.getHeadingPenalty()); } catch (ReflectiveOperationException ex) { @@ -130,15 +127,11 @@ public static CustomWeighting.Parameters createWeightingParameters(CustomModel c /** * This method does the following: *

    - *
  • 0. optionally we already checked the right-hand side expressions before this method call in FindMinMax.checkLMConstraints - * (only the client-side custom model statements) + *
  • + * 1. parse the value expressions (RHS) to know about additional encoded values ('findVariables') + * and check for multiplications with negative values. *
  • - *
  • 1. determine minimum and maximum values via parsing the right-hand side expression -> done in ValueExpressionVisitor. - * We need the maximum values for a simple negative check AND for the CustomWeighting.Parameters which is for - * Weighting.getMinWeight which is for A*. Note: we could make this step optional somehow for other algorithms, - * but parsing would be still required in the next step for security reasons. - *
  • - *
  • 2. parse condition value of priority and speed statements -> done in ConditionalExpressionVisitor (don't parse RHS expressions again) + *
  • 2. parse conditional expression of priority and speed statements -> done in ConditionalExpressionVisitor (don't parse RHS expressions again) *
  • *
  • 3. create class template as String, inject the created statements and create the Class *
  • @@ -149,17 +142,34 @@ private static Class createClazz(CustomModel customModel, EncodedValueLookup Set priorityVariables = ValueExpressionVisitor.findVariables(customModel.getPriority(), lookup); List priorityStatements = createGetPriorityStatements(priorityVariables, customModel, lookup); + if (customModel.getSpeed().isEmpty()) + throw new IllegalArgumentException("At least one initial statement under 'speed' is required."); + + List firstGroup = splitIntoGroup(customModel.getSpeed()).get(0); + if (firstGroup.size() > 1) { + Statement lastSt = firstGroup.get(firstGroup.size() - 1); + if (lastSt.operation() != Statement.Op.LIMIT || lastSt.keyword() != Statement.Keyword.ELSE) + throw new IllegalArgumentException("The first group needs to end with an 'else' (or contain a single unconditional 'if' statement)."); + } else { + Statement firstSt = firstGroup.get(0); + if (!"true".equals(firstSt.condition()) || firstSt.operation() != Statement.Op.LIMIT || firstSt.keyword() != Statement.Keyword.IF) + throw new IllegalArgumentException("The first group needs to contain a single unconditional 'if' statement (or end with an 'else')."); + } + Set speedVariables = ValueExpressionVisitor.findVariables(customModel.getSpeed(), lookup); List speedStatements = createGetSpeedStatements(speedVariables, customModel, lookup); + Set turnPenaltyVariables = ValueExpressionVisitor.findVariables(customModel.getTurnPenalty(), lookup); + List turnPenaltyStatements = createGetTurnPenaltyStatements(turnPenaltyVariables, customModel, lookup); + // Create different class name, which is required only for debugging. // TODO does it improve performance too? I.e. it could be that the JIT is confused if different classes // have the same name and it mixes performance stats. See https://github.com/janino-compiler/janino/issues/137 long counter = longVal.incrementAndGet(); - String classTemplate = createClassTemplate(counter, priorityVariables, speedVariables, lookup, CustomModel.getAreasAsMap(customModel.getAreas())); + String classTemplate = createClassTemplate(counter, priorityVariables, speedVariables, turnPenaltyVariables, lookup, CustomModel.getAreasAsMap(customModel.getAreas())); Java.CompilationUnit cu = (Java.CompilationUnit) new Parser(new Scanner("source", new StringReader(classTemplate))). parseAbstractCompilationUnit(); - cu = injectStatements(priorityStatements, speedStatements, cu); + cu = injectStatements(priorityStatements, speedStatements, turnPenaltyStatements, cu); SimpleCompiler sc = createCompiler(counter, cu); return sc.getClassLoader().loadClass("com.graphhopper.routing.weighting.custom.JaninoCustomWeightingHelperSubclass" + counter); } catch (Exception ex) { @@ -168,6 +178,55 @@ private static Class createClazz(CustomModel customModel, EncodedValueLookup } } + public static List findVariablesForEncodedValuesString(CustomModel model, NameValidator nameValidator, ClassHelper classHelper) { + Set variables = new LinkedHashSet<>(); + // avoid parsing exception for e.g. in_xy + NameValidator nameValidatorIntern = s -> { + // some literals are no variables and would throw an exception (encoded value not found) + if (Character.isUpperCase(s.charAt(0)) || s.startsWith(IN_AREA_PREFIX)) + return true; + if (nameValidator.isValid(s)) { + variables.add(s); + return true; + } + return false; + }; + findVariablesForEncodedValuesString(model.getPriority(), nameValidatorIntern, classHelper); + findVariablesForEncodedValuesString(model.getSpeed(), nameValidatorIntern, classHelper); + return new ArrayList<>(variables); + } + + private static void findVariablesForEncodedValuesString(List statements, NameValidator nameValidator, ClassHelper classHelper) { + List> groups = CustomModelParser.splitIntoGroup(statements); + for (List group : groups) { + for (Statement statement : group) { + if (statement.isBlock()) { + findVariablesForEncodedValuesString(statement.doBlock(), nameValidator, classHelper); + } else { + // ignore potential problems; collect only variables in this step + ConditionalExpressionVisitor.parse(statement.condition(), nameValidator, classHelper); + ValueExpressionVisitor.parse(statement.value(), nameValidator); + } + } + } + } + + /** + * Splits the specified list into several lists of statements starting with if. + * I.e. a group consists of one 'if' and zero or more 'else_if' and 'else' statements. + */ + static List> splitIntoGroup(List statements) { + List> result = new ArrayList<>(); + List group = null; + for (Statement st : statements) { + if (IF.equals(st.keyword())) result.add(group = new ArrayList<>()); + if (group == null) + throw new IllegalArgumentException("Every group must start with an if-statement"); + group.add(st); + } + return result; + } + /** * Parse the expressions from CustomModel relevant for the method getSpeed - see createClassTemplate. * @@ -177,8 +236,8 @@ private static List createGetSpeedStatements(Set sp CustomModel customModel, EncodedValueLookup lookup) throws Exception { List speedStatements = new ArrayList<>(verifyExpressions(new StringBuilder(), "speed entry", speedVariables, customModel.getSpeed(), lookup)); - String speedMethodStartBlock = "double value = super.getRawSpeed(edge, reverse);\n"; - // a bit inefficient to possibly define variables twice, but for now we have two separate methods + String speedMethodStartBlock = "double value = " + CustomWeightingHelper.GLOBAL_MAX_SPEED + ";\n"; + // potentially we fetch EncodedValues twice (one time here and one time for priority) for (String arg : speedVariables) { speedMethodStartBlock += getVariableDeclaration(lookup, arg); } @@ -194,9 +253,13 @@ private static List createGetSpeedStatements(Set sp */ private static List createGetPriorityStatements(Set priorityVariables, CustomModel customModel, EncodedValueLookup lookup) throws Exception { + for (Statement s : customModel.getPriority()) { + if (s.operation() == Statement.Op.ADD) + throw new IllegalArgumentException("'priority' statement must not have the operation 'add'"); + } List priorityStatements = new ArrayList<>(verifyExpressions(new StringBuilder(), "priority entry", priorityVariables, customModel.getPriority(), lookup)); - String priorityMethodStartBlock = "double value = super.getRawPriority(edge, reverse);\n"; + String priorityMethodStartBlock = "double value = " + CustomWeightingHelper.GLOBAL_PRIORITY + ";\n"; for (String arg : priorityVariables) { priorityMethodStartBlock += getVariableDeclaration(lookup, arg); } @@ -205,12 +268,67 @@ private static List createGetPriorityStatements(Set return priorityStatements; } + /** + * Parse the expressions from CustomModel relevant for the method getTurnPenalty - see createClassTemplate. + * + * @return the created statements (parsed expressions) + */ + private static List createGetTurnPenaltyStatements(Set turnPenaltyVariables, + CustomModel customModel, EncodedValueLookup lookup) throws Exception { + for (Statement s : customModel.getTurnPenalty()) { + if (s.operation() == Statement.Op.ADD && s.value().trim().startsWith("-")) + throw new IllegalArgumentException("The value for the 'add' operation must be positive, but was: " + s.value()); + if (s.isBlock()) + throw new IllegalArgumentException("'turn_penalty' statement cannot be a block (not yet implemented)"); + if (s.operation() != Statement.Op.ADD) + throw new IllegalArgumentException("'turn_penalty' statement must have the operation 'add' but was: " + s.operation() + " (not yet implemented)"); + } + + List turnPenaltyStatements = new ArrayList<>(verifyExpressions(new StringBuilder(), + "turn_penalty entry", turnPenaltyVariables, customModel.getTurnPenalty(), lookup)); + boolean needTwoDirections = false; + Function fct = createSimplifiedLookup(lookup); + for (String ttv : turnPenaltyVariables) { + EncodedValue ev = fct.apply(ttv); + if (ev != null && ev.isStoreTwoDirections() || ttv.equals(CHANGE_ANGLE)) { + needTwoDirections = true; + break; + } + } + + String turnPenaltyMethodStartBlock = "double value = 0;\n"; + if (needTwoDirections) { + // Performance optimization: avoid the following two calls if there is no encoded value + // that stores two directions. The call to isAdjNode is slightly faster than calling + // getEdgeIteratorState as it avoids creating a new object and accesses only one node + // but is slightly less safe as it cannot check that at least one node must be + // identical (the case where getEdgeIteratorState returns null) + turnPenaltyMethodStartBlock += "boolean inEdgeReverse = !graph.isAdjNode(inEdge, viaNode);\n" + + "boolean outEdgeReverse = !graph.isAdjNode(outEdge, viaNode);\n"; + } + + for (String arg : turnPenaltyVariables) { + turnPenaltyMethodStartBlock += getTurnPenaltyVariableDeclaration(lookup, arg, needTwoDirections); + } + + // special case for change_angle method call: we need the orientation encoded value + if (turnPenaltyVariables.contains(CHANGE_ANGLE)) { + turnPenaltyVariables.remove(CHANGE_ANGLE); + turnPenaltyVariables.add(Orientation.KEY); + } + + turnPenaltyStatements.addAll(0, new Parser(new org.codehaus.janino.Scanner("getTurnPenalty", new StringReader(turnPenaltyMethodStartBlock))). + parseBlockStatements()); + return turnPenaltyStatements; + } + /** * For the methods getSpeed and getPriority we declare variables that contain the encoded value of the current edge * or if an area contains the current edge. */ private static String getVariableDeclaration(EncodedValueLookup lookup, final String arg) { if (lookup.hasEncodedValue(arg)) { + // parameters in method getPriority or getSpeed are: EdgeIteratorState edge, boolean reverse EncodedValue enc = lookup.getEncodedValue(arg, EncodedValue.class); return getReturnType(enc) + " " + arg + " = (" + getReturnType(enc) + ") (reverse ? " + "edge.getReverse((" + getInterface(enc) + ") this." + arg + "_enc) : " + @@ -232,6 +350,39 @@ private static String getVariableDeclaration(EncodedValueLookup lookup, final St } } + private static String getTurnPenaltyVariableDeclaration(EncodedValueLookup lookup, final String arg, boolean needTwoDirections) { + // parameters in method getTurnPenalty are: int inEdge, int viaNode, int outEdge. + // The variables outEdgeReverse and inEdgeReverse are provided from initial calls if needTwoDirections is true. + if (arg.equals(CHANGE_ANGLE)) { + return "double change_angle = CustomWeightingHelper.calcChangeAngle(edgeIntAccess, this.orientation_enc, inEdge, inEdgeReverse, outEdge, outEdgeReverse);\n"; + } else if (arg.equals(STREET_NAME)) { + return "String street_name = graph.getEdgeIteratorState(outEdge, Integer.MIN_VALUE).getName();\n"; // TODO PERF: get ref into KVStorage without creation of EdgeIteratorState + } else if (arg.equals(PREV_PREFIX + STREET_NAME)) { + return "String prev_street_name = graph.getEdgeIteratorState(inEdge, Integer.MIN_VALUE).getName();\n"; // TODO PERF + } else if (lookup.hasEncodedValue(arg)) { + EncodedValue enc = lookup.getEncodedValue(arg, EncodedValue.class); + if (!(enc instanceof EnumEncodedValue)) + throw new IllegalArgumentException("Currently only EnumEncodedValues are supported: " + arg); + + return getReturnType(enc) + " " + arg + " = (" + getReturnType(enc) + ") " + + "this." + arg + "_enc.getEnum(" + (needTwoDirections ? "outEdgeReverse" : "false") + ", outEdge, edgeIntAccess);\n"; + } else if (arg.startsWith(PREV_PREFIX)) { + final String argSubstr = arg.substring(PREV_PREFIX.length()); + if (lookup.hasEncodedValue(argSubstr)) { + EncodedValue enc = lookup.getEncodedValue(argSubstr, EncodedValue.class); + if (!(enc instanceof EnumEncodedValue)) + throw new IllegalArgumentException("Currently only EnumEncodedValues are supported: " + arg); + + return getReturnType(enc) + " " + arg + " = (" + getReturnType(enc) + ") " + + "this." + argSubstr + "_enc.getEnum(" + (needTwoDirections ? "inEdgeReverse" : "false") + ", inEdge, edgeIntAccess);\n"; + } else { + throw new IllegalArgumentException("Not supported for prev: " + argSubstr); + } + } else { + throw new IllegalArgumentException("Not supported for turn_penalty: " + arg); + } + } + /** * @return the interface as string of the provided EncodedValue, e.g. IntEncodedValue (only interface) or * BooleanEncodedValue (first interface). For StringEncodedValue we return IntEncodedValue to return the index @@ -264,23 +415,27 @@ private static String getReturnType(EncodedValue encodedValue) { * have to inject that parsed and safe user expressions in a later step. */ private static String createClassTemplate(long counter, - Set priorityVariables, Set speedVariables, + Set priorityVariables, + Set speedVariables, + Set turnPenaltyVariables, EncodedValueLookup lookup, Map areas) { final StringBuilder importSourceCode = new StringBuilder("import com.graphhopper.routing.ev.*;\n"); importSourceCode.append("import java.util.Map;\n"); importSourceCode.append("import " + CustomModel.class.getName() + ";\n"); + importSourceCode.append("import " + BaseGraph.class.getName() + ";\n"); + importSourceCode.append("import " + EdgeIntAccess.class.getName() + ";\n"); final StringBuilder classSourceCode = new StringBuilder(100); boolean includedAreaImports = false; - final StringBuilder initSourceCode = new StringBuilder("this.avg_speed_enc = avgSpeedEnc;\n"); - initSourceCode.append("this.priority_enc = priorityEnc;\n"); - initSourceCode.append("this.lookup = lookup;\n"); + final StringBuilder initSourceCode = new StringBuilder("this.lookup = lookup;\n"); initSourceCode.append("this.customModel = customModel;\n"); Set set = new HashSet<>(); for (String prioVar : priorityVariables) set.add(prioVar.startsWith(BACKWARD_PREFIX) ? prioVar.substring(BACKWARD_PREFIX.length()) : prioVar); for (String speedVar : speedVariables) set.add(speedVar.startsWith(BACKWARD_PREFIX) ? speedVar.substring(BACKWARD_PREFIX.length()) : speedVar); + for (String speedVar : turnPenaltyVariables) + set.add(speedVar.startsWith(PREV_PREFIX) ? speedVar.substring(PREV_PREFIX.length()) : speedVar); for (String arg : set) { if (lookup.hasEncodedValue(arg)) { @@ -314,6 +469,8 @@ private static String createClassTemplate(long counter, classSourceCode.append("protected " + Polygon.class.getSimpleName() + " " + arg + ";\n"); initSourceCode.append("JsonFeature feature_" + id + " = (JsonFeature) areas.get(\"" + id + "\");\n"); initSourceCode.append("this." + arg + " = new Polygon(new PreparedPolygon((Polygonal) feature_" + id + ".getGeometry()));\n"); + } else if (arg.equals(STREET_NAME)) { + // street_name is resolved at runtime from graph KV storage, no class field needed } else { if (!arg.startsWith(IN_AREA_PREFIX)) throw new IllegalArgumentException("Variable not supported: " + arg); @@ -329,8 +486,7 @@ private static String createClassTemplate(long counter, + "\npublic class JaninoCustomWeightingHelperSubclass" + counter + " extends " + CustomWeightingHelper.class.getSimpleName() + " {\n" + classSourceCode + " @Override\n" - + " public void init(CustomModel customModel, EncodedValueLookup lookup, " + DecimalEncodedValue.class.getName() + " avgSpeedEnc, " - + DecimalEncodedValue.class.getName() + " priorityEnc, Map areas) {\n" + + " public void init(CustomModel customModel, EncodedValueLookup lookup, Map areas) {\n" + initSourceCode + " }\n\n" // we need these placeholder methods so that the hooks in DeepCopier are invoked @@ -340,7 +496,11 @@ private static String createClassTemplate(long counter, + " }\n" + " @Override\n" + " public double getSpeed(EdgeIteratorState edge, boolean reverse) {\n" - + " return getRawSpeed(edge, reverse); //will be overwritten by code injected in DeepCopier\n" + + " return 1; //will be overwritten by code injected in DeepCopier\n" + + " }\n" + + " @Override\n" + + " public double getTurnPenalty(BaseGraph graph, EdgeIntAccess edgeIntAccess, int inEdge, int viaNode, int outEdge) {\n" + + " return 1; //will be overwritten by code injected in DeepCopier\n" + " }\n" + "}"; } @@ -357,40 +517,77 @@ private static List verifyExpressions(StringBuilder express List list, EncodedValueLookup lookup) throws Exception { // allow variables, all encoded values, constants and special variables like in_xyarea or backward_car_access NameValidator nameInConditionValidator = name -> lookup.hasEncodedValue(name) - || name.toUpperCase(Locale.ROOT).equals(name) || name.startsWith(IN_AREA_PREFIX) - || name.startsWith(BACKWARD_PREFIX) && lookup.hasEncodedValue(name.substring(BACKWARD_PREFIX.length())); - ClassHelper helper = key -> getReturnType(lookup.getEncodedValue(key, EncodedValue.class)); - - parseExpressions(expressions, nameInConditionValidator, info, createObjects, list, helper); + || name.toUpperCase(Locale.ROOT).equals(name) || name.startsWith(IN_AREA_PREFIX) || name.equals(CHANGE_ANGLE) + || name.equals(STREET_NAME) || name.equals(PREV_PREFIX + STREET_NAME) + || name.startsWith(BACKWARD_PREFIX) && lookup.hasEncodedValue(name.substring(BACKWARD_PREFIX.length())) + || name.startsWith(PREV_PREFIX) && lookup.hasEncodedValue(name.substring(PREV_PREFIX.length())); + Function fct = createSimplifiedLookup(lookup); + ClassHelper helper = key -> { + EncodedValue ev = fct.apply(key); + if (ev == null) throw new IllegalArgumentException("Couldn't find class for " + key); + return getReturnType(ev); + }; + + parseExpressions(expressions, nameInConditionValidator, info, createObjects, list, helper, ""); + expressions.append("return value;\n"); return new Parser(new org.codehaus.janino.Scanner(info, new StringReader(expressions.toString()))). parseBlockStatements(); } + private static Function createSimplifiedLookup(EncodedValueLookup lookup) { + return key -> { + if (key.equals(STREET_NAME) || key.equals(PREV_PREFIX + STREET_NAME)) + return null; + else if (key.startsWith(BACKWARD_PREFIX)) + return lookup.getEncodedValue(key.substring(BACKWARD_PREFIX.length()), EncodedValue.class); + else if (key.startsWith(PREV_PREFIX)) + return lookup.getEncodedValue(key.substring(PREV_PREFIX.length()), EncodedValue.class); + else if (lookup.hasEncodedValue(key)) + return lookup.getEncodedValue(key, EncodedValue.class); + else return null; + }; + } + static void parseExpressions(StringBuilder expressions, NameValidator nameInConditionValidator, String exceptionInfo, Set createObjects, List list, - ClassHelper helper) { + ClassHelper classHelper, String indentation) { for (Statement statement : list) { // avoid parsing the RHS value expression again as we just did it to get the maximum values in createClazz - if (statement.getKeyword() == Statement.Keyword.ELSE) { - if (!Helper.isEmpty(statement.getCondition())) - throw new IllegalArgumentException("condition must be empty but was " + statement.getCondition()); - - expressions.append("else {").append(statement.getOperation().build(statement.getValue())).append("; }\n"); - } else if (statement.getKeyword() == Statement.Keyword.ELSEIF || statement.getKeyword() == Statement.Keyword.IF) { - ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.getCondition(), nameInConditionValidator, helper); + if (statement.keyword() == Statement.Keyword.ELSE) { + if (!Helper.isEmpty(statement.condition())) + throw new IllegalArgumentException("condition must be empty but was " + statement.condition()); + + expressions.append(indentation); + if (statement.isBlock()) { + expressions.append("else {"); + parseExpressions(expressions, nameInConditionValidator, exceptionInfo, createObjects, statement.doBlock(), classHelper, indentation + " "); + expressions.append(indentation).append("}\n"); + } else { + expressions.append("else {").append(statement.operation().build(statement.value())).append("; }\n"); + } + } else if (statement.keyword() == Statement.Keyword.ELSEIF || statement.keyword() == Statement.Keyword.IF) { + ParseResult parseResult = ConditionalExpressionVisitor.parse(statement.condition(), nameInConditionValidator, classHelper); if (!parseResult.ok) - throw new IllegalArgumentException(exceptionInfo + " invalid condition \"" + statement.getCondition() + "\"" + + throw new IllegalArgumentException(exceptionInfo + " invalid condition \"" + statement.condition() + "\"" + (parseResult.invalidMessage == null ? "" : ": " + parseResult.invalidMessage)); createObjects.addAll(parseResult.guessedVariables); - if (statement.getKeyword() == Statement.Keyword.ELSEIF) - expressions.append("else "); - expressions.append("if (").append(parseResult.converted).append(") {").append(statement.getOperation().build(statement.getValue())).append(";}\n"); + if (statement.keyword() == Statement.Keyword.ELSEIF) + expressions.append(indentation).append("else "); + + expressions.append(indentation); + if (statement.isBlock()) { + expressions.append("if (").append(parseResult.converted).append(") {\n"); + parseExpressions(expressions, nameInConditionValidator, exceptionInfo, createObjects, statement.doBlock(), classHelper, indentation + " "); + expressions.append(indentation).append("}\n"); + } else { + expressions.append("if (").append(parseResult.converted).append(") {"). + append(statement.operation().build(statement.value())).append(";}\n"); + } } else { throw new IllegalArgumentException("The statement must be either 'if', 'else_if' or 'else'"); } } - expressions.append("return value;\n"); } /** @@ -399,10 +596,12 @@ static void parseExpressions(StringBuilder expressions, NameValidator nameInCond */ private static Java.CompilationUnit injectStatements(List priorityStatements, List speedStatements, + List turnPenaltyStatements, Java.CompilationUnit cu) throws CompileException { cu = new DeepCopier() { boolean speedInjected = false; boolean priorityInjected = false; + boolean turnPenaltyInjected = false; @Override public Java.MethodDeclarator copyMethodDeclarator(Java.MethodDeclarator subject) throws CompileException { @@ -412,6 +611,9 @@ public Java.MethodDeclarator copyMethodDeclarator(Java.MethodDeclarator subject) } else if (subject.name.equals("getPriority") && !priorityStatements.isEmpty() && !priorityInjected) { priorityInjected = true; return injectStatements(subject, this, priorityStatements); + } else if (subject.name.equals("getTurnPenalty") && !turnPenaltyStatements.isEmpty() && !turnPenaltyInjected) { + turnPenaltyInjected = true; + return injectStatements(subject, this, turnPenaltyStatements); } else { return super.copyMethodDeclarator(subject); } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java index 9869db0a5b8..a9f0de021ca 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeighting.java @@ -17,13 +17,16 @@ */ package com.graphhopper.routing.weighting.custom; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.weighting.AbstractWeighting; +import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.CustomModel; import com.graphhopper.util.EdgeIteratorState; +import static com.graphhopper.routing.weighting.Weighting.roundWeight; +import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; + /** * The CustomWeighting allows adjusting the edge weights relative to those we'd obtain for a given base flag encoder. * For example a car flag encoder already provides speeds and access flags for every edge depending on certain edge @@ -69,7 +72,7 @@ * calculated via the speed_factor is simply overwritten. Edges that are not accessible according to the access flags of * the base vehicle always get assigned an infinite weight and this cannot be changed (yet) using this weighting. */ -public final class CustomWeighting extends AbstractWeighting { +public final class CustomWeighting implements Weighting { public static final String NAME = "custom"; /** @@ -81,11 +84,14 @@ public final class CustomWeighting extends AbstractWeighting { private final double headingPenaltySeconds; private final EdgeToDoubleMapping edgeToSpeedMapping; private final EdgeToDoubleMapping edgeToPriorityMapping; + private final TurnCostProvider turnCostProvider; private final MaxCalc maxPrioCalc; private final MaxCalc maxSpeedCalc; - public CustomWeighting(BooleanEncodedValue baseAccessEnc, DecimalEncodedValue baseSpeedEnc, TurnCostProvider turnCostProvider, Parameters parameters) { - super(baseAccessEnc, baseSpeedEnc, turnCostProvider); + public CustomWeighting(TurnCostProvider turnCostProvider, Parameters parameters) { + if (!Weighting.isValidName(getName())) + throw new IllegalStateException("Not a valid name for a Weighting: " + getName()); + this.turnCostProvider = turnCostProvider; this.edgeToSpeedMapping = parameters.getEdgeToSpeedMapping(); this.maxSpeedCalc = parameters.getMaxSpeedCalc(); @@ -103,11 +109,14 @@ public CustomWeighting(BooleanEncodedValue baseAccessEnc, DecimalEncodedValue ba @Override public double calcMinWeightPerDistance() { - return 1d / (maxSpeedCalc.calcMax() / SPEED_CONV) / maxPrioCalc.calcMax() + distanceInfluence; + return 10 * (1d / (maxSpeedCalc.calcMax() / SPEED_CONV) / maxPrioCalc.calcMax() + distanceInfluence); } @Override public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { + double priority = edgeToPriorityMapping.get(edgeState, reverse); + if (priority == 0) return Double.POSITIVE_INFINITY; + final double distance = edgeState.getDistance(); double seconds = calcSeconds(distance, edgeState, reverse); if (Double.isInfinite(seconds)) return Double.POSITIVE_INFINITY; @@ -115,16 +124,10 @@ public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { if (edgeState.get(EdgeIteratorState.UNFAVORED_EDGE)) seconds += headingPenaltySeconds; double distanceCosts = distance * distanceInfluence; if (Double.isInfinite(distanceCosts)) return Double.POSITIVE_INFINITY; - double priority = edgeToPriorityMapping.get(edgeState, reverse); - // special case to avoid NaN for barrier edges (where time is often 0s) - if (priority == 0 && seconds == 0) return Double.POSITIVE_INFINITY; - return seconds / priority + distanceCosts; + return roundWeight(10 * (seconds / priority + distanceCosts)); } double calcSeconds(double distance, EdgeIteratorState edgeState, boolean reverse) { - if (reverse ? !edgeState.getReverse(accessEnc) : !edgeState.get(accessEnc)) - return Double.POSITIVE_INFINITY; - double speed = edgeToSpeedMapping.get(edgeState, reverse); if (speed == 0) return Double.POSITIVE_INFINITY; @@ -139,6 +142,22 @@ public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { return Math.round(calcSeconds(edgeState.getDistance(), edgeState, reverse) * 1000); } + @Override + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + double turnWeight = turnCostProvider.calcTurnWeight(inEdge, viaNode, outEdge); + return roundWeight(10 * turnWeight); + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return turnCostProvider.calcTurnMillis(inEdge, viaNode, outEdge); + } + + @Override + public boolean hasTurnCosts() { + return turnCostProvider != NO_TURN_COST_PROVIDER; + } + @Override public String getName() { return NAME; @@ -149,6 +168,10 @@ public interface EdgeToDoubleMapping { double get(EdgeIteratorState edge, boolean reverse); } + public interface TurnPenaltyMapping { + double get(BaseGraph graph, EdgeIntAccess edgeIntAccess, int inEdge, int viaNode, int outEdge); + } + @FunctionalInterface public interface MaxCalc { double calcMax(); @@ -159,16 +182,19 @@ public static class Parameters { private final EdgeToDoubleMapping edgeToPriorityMapping; private final MaxCalc maxSpeedCalc; private final MaxCalc maxPrioCalc; + private final TurnPenaltyMapping turnPenaltyMapping; private final double distanceInfluence; private final double headingPenaltySeconds; public Parameters(EdgeToDoubleMapping edgeToSpeedMapping, MaxCalc maxSpeedCalc, EdgeToDoubleMapping edgeToPriorityMapping, MaxCalc maxPrioCalc, + TurnPenaltyMapping turnPenaltyMapping, double distanceInfluence, double headingPenaltySeconds) { this.edgeToSpeedMapping = edgeToSpeedMapping; this.maxSpeedCalc = maxSpeedCalc; this.edgeToPriorityMapping = edgeToPriorityMapping; this.maxPrioCalc = maxPrioCalc; + this.turnPenaltyMapping = turnPenaltyMapping; this.distanceInfluence = distanceInfluence; this.headingPenaltySeconds = headingPenaltySeconds; } @@ -189,6 +215,10 @@ public MaxCalc getMaxPrioCalc() { return maxPrioCalc; } + public TurnPenaltyMapping getTurnPenaltyMapping() { + return turnPenaltyMapping; + } + public double getDistanceInfluence() { return distanceInfluence; } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelper.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelper.java index 70f5d1c25a3..a3a69016d16 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelper.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelper.java @@ -18,32 +18,35 @@ package com.graphhopper.routing.weighting.custom; import com.graphhopper.json.MinMax; +import com.graphhopper.json.Statement; import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; import com.graphhopper.routing.ev.EncodedValueLookup; +import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import com.graphhopper.util.shapes.Polygon; +import java.util.List; import java.util.Map; /** - * This class is for internal usage only. It is subclassed by Janino, then special expressions are injected into init, - * getSpeed and getPriority. At the end an instance is created and used in CustomWeighting. + * This class is for internal usage only. It is subclassed by Janino, then special expressions are + * injected into init, getSpeed and getPriority. At the end an instance is created and used in CustomWeighting. */ public class CustomWeightingHelper { - protected DecimalEncodedValue avg_speed_enc; - protected DecimalEncodedValue priority_enc; + static double GLOBAL_MAX_SPEED = 999; + static double GLOBAL_PRIORITY = 1; + protected EncodedValueLookup lookup; protected CustomModel customModel; protected CustomWeightingHelper() { } - public void init(CustomModel customModel, EncodedValueLookup lookup, DecimalEncodedValue avgSpeedEnc, DecimalEncodedValue priorityEnc, Map areas) { + public void init(CustomModel customModel, EncodedValueLookup lookup, Map areas) { this.lookup = lookup; this.customModel = customModel; - this.avg_speed_enc = avgSpeedEnc; - this.priority_enc = priorityEnc; } public double getPriority(EdgeIteratorState edge, boolean reverse) { @@ -51,39 +54,35 @@ public double getPriority(EdgeIteratorState edge, boolean reverse) { } public double getSpeed(EdgeIteratorState edge, boolean reverse) { - return getRawSpeed(edge, reverse); - } - - protected final double getRawSpeed(EdgeIteratorState edge, boolean reverse) { - double speed = reverse ? edge.getReverse(avg_speed_enc) : edge.get(avg_speed_enc); - if (Double.isInfinite(speed) || Double.isNaN(speed) || speed < 0) - throw new IllegalStateException("Invalid estimated speed " + speed); - return speed; + return 1; } - protected final double getRawPriority(EdgeIteratorState edge, boolean reverse) { - if (priority_enc == null) return 1; - double priority = reverse ? edge.getReverse(priority_enc) : edge.get(priority_enc); - if (Double.isInfinite(priority) || Double.isNaN(priority) || priority < 0) - throw new IllegalStateException("Invalid priority " + priority); - return priority; + public double getTurnPenalty(BaseGraph graph, EdgeIntAccess edgeIntAccess, int inEdge, int viaNode, int outEdge) { + return 0; } public final double calcMaxSpeed() { - MinMax minMaxSpeed = new MinMax(1, avg_speed_enc.getMaxOrMaxStorableDecimal()); + MinMax minMaxSpeed = new MinMax(0, GLOBAL_MAX_SPEED); FindMinMax.findMinMax(minMaxSpeed, customModel.getSpeed(), lookup); if (minMaxSpeed.min < 0) throw new IllegalArgumentException("speed has to be >=0 but can be negative (" + minMaxSpeed.min + ")"); if (minMaxSpeed.max <= 0) throw new IllegalArgumentException("maximum speed has to be >0 but was " + minMaxSpeed.max); + if (minMaxSpeed.max == GLOBAL_MAX_SPEED) + throw new IllegalArgumentException("The first statement for 'speed' must be unconditionally to set the speed. But it was " + customModel.getSpeed().get(0)); return minMaxSpeed.max; } public final double calcMaxPriority() { - // initial value of minimum has to be >0 so that multiple_by with a negative value leads to a negative value and not 0 - MinMax minMaxPriority = new MinMax(1, priority_enc == null ? 1 : priority_enc.getMaxOrMaxStorableDecimal()); - FindMinMax.findMinMax(minMaxPriority, customModel.getPriority(), lookup); + MinMax minMaxPriority = new MinMax(0, GLOBAL_PRIORITY); + List statements = customModel.getPriority(); + if (!statements.isEmpty() && "true".equals(statements.get(0).condition())) { + String value = statements.get(0).value(); + if (lookup.hasEncodedValue(value)) + minMaxPriority.max = lookup.getDecimalEncodedValue(value).getMaxOrMaxStorableDecimal(); + } + FindMinMax.findMinMax(minMaxPriority, statements, lookup); if (minMaxPriority.min < 0) throw new IllegalArgumentException("priority has to be >=0 but can be negative (" + minMaxPriority.min + ")"); if (minMaxPriority.max < 0) @@ -100,4 +99,21 @@ public static boolean in(Polygon p, EdgeIteratorState edge) { return true; return p.intersects(edge.fetchWayGeometry(FetchMode.ALL).makeImmutable()); // TODO PERF: cache bbox and edge wayGeometry for multiple area } + + public static double calcChangeAngle(EdgeIntAccess edgeIntAccess, DecimalEncodedValue orientationEnc, + int inEdge, boolean inEdgeReverse, int outEdge, boolean outEdgeReverse) { + double prevAzimuth = orientationEnc.getDecimal(inEdgeReverse, inEdge, edgeIntAccess); + double azimuth = orientationEnc.getDecimal(outEdgeReverse, outEdge, edgeIntAccess); + return calcChangeAngle(prevAzimuth, azimuth); + } + + public static double calcChangeAngle(double prevAzimuth, double azimuth) { + // bring parallel to prevOrientation + azimuth = (azimuth + 180) % 360.0; + + double changeAngle = azimuth - prevAzimuth; + + // keep in [-180, 180] + return (changeAngle + 540.0) % 360.0 - 180.0; + } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java index 1dee5e40af1..304c07a16c6 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java @@ -1,3 +1,20 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.routing.weighting.custom; import com.graphhopper.json.MinMax; @@ -5,7 +22,7 @@ import com.graphhopper.routing.ev.EncodedValueLookup; import com.graphhopper.util.CustomModel; -import java.util.*; +import java.util.List; import static com.graphhopper.json.Statement.Keyword.ELSE; import static com.graphhopper.json.Statement.Keyword.IF; @@ -18,8 +35,6 @@ public class FindMinMax { * is based on baseModel). */ public static void checkLMConstraints(CustomModel baseModel, CustomModel queryModel, EncodedValueLookup lookup) { - if (queryModel.isInternal()) - throw new IllegalArgumentException("CustomModel of query cannot be internal"); if (queryModel.getDistanceInfluence() != null) { double bmDI = baseModel.getDistanceInfluence() == null ? 0 : baseModel.getDistanceInfluence(); if (queryModel.getDistanceInfluence() < bmDI) @@ -33,12 +48,12 @@ public static void checkLMConstraints(CustomModel baseModel, CustomModel queryMo private static void checkMultiplyValue(List list, EncodedValueLookup lookup) { for (Statement statement : list) { - if (statement.getOperation() == Statement.Op.MULTIPLY) { - MinMax minMax = ValueExpressionVisitor.findMinMax(statement.getValue(), lookup); + if (statement.operation() == Statement.Op.MULTIPLY) { + MinMax minMax = ValueExpressionVisitor.findMinMax(statement.value(), lookup); if (minMax.max > 1) - throw new IllegalArgumentException("maximum of value '" + statement.getValue() + "' cannot be larger than 1, but was: " + minMax.max); + throw new IllegalArgumentException("maximum of value '" + statement.value() + "' cannot be larger than 1, but was: " + minMax.max); else if (minMax.min < 0) - throw new IllegalArgumentException("minimum of value '" + statement.getValue() + "' cannot be smaller than 0, but was: " + minMax.min); + throw new IllegalArgumentException("minimum of value '" + statement.value() + "' cannot be smaller than 0, but was: " + minMax.min); } } } @@ -48,37 +63,52 @@ else if (minMax.min < 0) * exceeded by any edge in max. */ static MinMax findMinMax(MinMax minMax, List statements, EncodedValueLookup lookup) { - // 'blocks' of the statements are applied one after the other. A block consists of one (if) or more statements (elseif+else) - List> blocks = ValueExpressionVisitor.splitIntoBlocks(statements); - for (List block : blocks) findMinMaxForBlock(minMax, block, lookup); + List> groups = CustomModelParser.splitIntoGroup(statements); + for (List group : groups) findMinMaxForGroup(minMax, group, lookup); return minMax; } - private static void findMinMaxForBlock(final MinMax minMax, List block, EncodedValueLookup lookup) { - if (block.isEmpty() || !IF.equals(block.get(0).getKeyword())) - throw new IllegalArgumentException("Every block must start with an if-statement"); + private static void findMinMaxForGroup(final MinMax minMax, List group, EncodedValueLookup lookup) { + if (group.isEmpty() || !IF.equals(group.get(0).keyword())) + throw new IllegalArgumentException("Every group must start with an if-statement"); - MinMax minMaxBlock; - if (block.get(0).getCondition().trim().equals("true")) { - minMaxBlock = block.get(0).getOperation().apply(minMax, ValueExpressionVisitor.findMinMax(block.get(0).getValue(), lookup)); + MinMax minMaxGroup; + Statement first = group.get(0); + if (first.condition().trim().equals("true")) { + if(first.isBlock()) { + for (List subGroup : CustomModelParser.splitIntoGroup(first.doBlock())) findMinMaxForGroup(minMax, subGroup, lookup); + return; + } else { + minMaxGroup = first.operation().apply(minMax, ValueExpressionVisitor.findMinMax(first.value(), lookup)); + if (minMaxGroup.max < 0) + throw new IllegalArgumentException("statement resulted in negative value: " + first); + } } else { - minMaxBlock = new MinMax(Double.MAX_VALUE, 0); + minMaxGroup = new MinMax(Double.MAX_VALUE, 0); boolean foundElse = false; - for (Statement s : block) { - if (s.getKeyword() == ELSE) foundElse = true; - MinMax tmp = s.getOperation().apply(minMax, ValueExpressionVisitor.findMinMax(s.getValue(), lookup)); - minMaxBlock.min = Math.min(minMaxBlock.min, tmp.min); - minMaxBlock.max = Math.max(minMaxBlock.max, tmp.max); + for (Statement s : group) { + if (s.keyword() == ELSE) foundElse = true; + MinMax tmp; + if(s.isBlock()) { + tmp = new MinMax(minMax.min, minMax.max); + for (List subGroup : CustomModelParser.splitIntoGroup(s.doBlock())) findMinMaxForGroup(tmp, subGroup, lookup); + } else { + tmp = s.operation().apply(minMax, ValueExpressionVisitor.findMinMax(s.value(), lookup)); + if (tmp.max < 0) + throw new IllegalArgumentException("statement resulted in negative value: " + s); + } + minMaxGroup.min = Math.min(minMaxGroup.min, tmp.min); + minMaxGroup.max = Math.max(minMaxGroup.max, tmp.max); } // if there is no 'else' statement it's like there is a 'neutral' branch that leaves the initial value as is if (!foundElse) { - minMaxBlock.min = Math.min(minMaxBlock.min, minMax.min); - minMaxBlock.max = Math.max(minMaxBlock.max, minMax.max); + minMaxGroup.min = Math.min(minMaxGroup.min, minMax.min); + minMaxGroup.max = Math.max(minMaxGroup.max, minMax.max); } } - minMax.min = minMaxBlock.min; - minMax.max = minMaxBlock.max; + minMax.min = minMaxGroup.min; + minMax.max = minMaxGroup.max; } } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java index 058e7ce8022..4fb4e51a575 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/NameValidator.java @@ -1,5 +1,5 @@ package com.graphhopper.routing.weighting.custom; -interface NameValidator { +public interface NameValidator { boolean isValid(String name); } diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ParseResult.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ParseResult.java index 9adaca5e75e..694f6aa5f27 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/ParseResult.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ParseResult.java @@ -2,7 +2,7 @@ import java.util.Set; -class ParseResult { +public class ParseResult { StringBuilder converted; boolean ok; String invalidMessage; diff --git a/core/src/main/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitor.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitor.java index a9e4f96310b..ef6edb53545 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitor.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitor.java @@ -1,3 +1,20 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.routing.weighting.custom; import com.graphhopper.json.MinMax; @@ -8,11 +25,11 @@ import com.graphhopper.routing.ev.IntEncodedValue; import org.codehaus.commons.compiler.CompileException; import org.codehaus.janino.*; -import org.codehaus.janino.Scanner; import java.io.StringReader; -import java.lang.reflect.InvocationTargetException; -import java.util.*; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; import static com.graphhopper.json.Statement.Keyword.IF; @@ -21,8 +38,9 @@ */ public class ValueExpressionVisitor implements Visitor.AtomVisitor { - private static final Set allowedMethodParents = new HashSet<>(Arrays.asList("Math")); - private static final Set allowedMethods = new HashSet<>(Arrays.asList("sqrt")); + private static final String INFINITY = Double.toString(Double.POSITIVE_INFINITY); + private static final Set allowedMethodParents = Set.of("Math"); + private static final Set allowedMethods = Set.of("sqrt"); private final ParseResult result; private final NameValidator variableValidator; private String invalidMessage; @@ -44,8 +62,7 @@ boolean isValidIdentifier(String identifier) { @Override public Boolean visitRvalue(Java.Rvalue rv) throws Exception { - if (rv instanceof Java.AmbiguousName) { - Java.AmbiguousName n = (Java.AmbiguousName) rv; + if (rv instanceof Java.AmbiguousName n) { if (n.identifiers.length == 1) { String arg = n.identifiers[0]; // e.g. like road_class @@ -58,14 +75,12 @@ public Boolean visitRvalue(Java.Rvalue rv) throws Exception { } if (rv instanceof Java.Literal) { return true; - } else if (rv instanceof Java.UnaryOperation) { - Java.UnaryOperation uop = (Java.UnaryOperation) rv; + } else if (rv instanceof Java.UnaryOperation uop) { result.operators.add(uop.operator); if (uop.operator.equals("-")) return uop.operand.accept(this); return false; - } else if (rv instanceof Java.MethodInvocation) { - Java.MethodInvocation mi = (Java.MethodInvocation) rv; + } else if (rv instanceof Java.MethodInvocation mi) { if (allowedMethods.contains(mi.methodName)) { // skip methods like this.in() if (mi.target != null) { @@ -91,8 +106,7 @@ public Boolean visitRvalue(Java.Rvalue rv) throws Exception { return false; } else if (rv instanceof Java.ParenthesizedExpression) { return ((Java.ParenthesizedExpression) rv).value.accept(this); - } else if (rv instanceof Java.BinaryOperation) { - Java.BinaryOperation binOp = (Java.BinaryOperation) rv; + } else if (rv instanceof Java.BinaryOperation binOp) { String op = binOp.operator; result.operators.add(op); if (op.equals("*") || op.equals("+") || binOp.operator.equals("-")) { @@ -136,47 +150,82 @@ static ParseResult parse(String expression, NameValidator variableValidator) { return result; } - public static Set findVariables(List statements, EncodedValueLookup lookup) { - List> blocks = splitIntoBlocks(statements); + static Set findVariables(List statements, EncodedValueLookup lookup) { + List> groups = CustomModelParser.splitIntoGroup(statements); Set variables = new LinkedHashSet<>(); - for (List block : blocks) findVariablesForBlock(variables, block, lookup); + for (List group : groups) findVariablesForGroup(variables, group, lookup); return variables; } - /** - * Splits the specified list into several list of statements starting with if - */ - static List> splitIntoBlocks(List statements) { - List> result = new ArrayList<>(); - List block = null; - for (Statement st : statements) { - if (IF.equals(st.getKeyword())) result.add(block = new ArrayList<>()); - if (block == null) - throw new IllegalArgumentException("Every block must start with an if-statement"); - block.add(st); - } - return result; - } + private static void findVariablesForGroup(Set createdObjects, List group, EncodedValueLookup lookup) { + if (group.isEmpty() || !IF.equals(group.get(0).keyword())) + throw new IllegalArgumentException("Every group of statements must start with an if-statement"); - private static void findVariablesForBlock(Set createdObjects, List block, EncodedValueLookup lookup) { - if (block.isEmpty() || !IF.equals(block.get(0).getKeyword())) - throw new IllegalArgumentException("Every block must start with an if-statement"); + Statement first = group.get(0); + if (first.condition().trim().equals("true")) { + if (first.isBlock()) { + List> groups = CustomModelParser.splitIntoGroup(first.doBlock()); + for (List subGroup : groups) + findVariablesForGroup(createdObjects, subGroup, lookup); + } else { + createdObjects.addAll(ValueExpressionVisitor.findVariables(first.value(), lookup)); + } - if (block.get(0).getCondition().trim().equals("true")) { - createdObjects.addAll(ValueExpressionVisitor.findVariables(block.get(0).getValue(), lookup)); + if (group.size() > 1) + throw new IllegalArgumentException("Only one statement allowed for an unconditional statement"); } else { - for (Statement s : block) { - createdObjects.addAll(ValueExpressionVisitor.findVariables(s.getValue(), lookup)); + for (Statement st : group) { + if (st.isBlock()) { + List> groups = CustomModelParser.splitIntoGroup(st.doBlock()); + for (List subGroup : groups) + findVariablesForGroup(createdObjects, subGroup, lookup); + } else { + createdObjects.addAll(ValueExpressionVisitor.findVariables(st.value(), lookup)); + } } } } static Set findVariables(String valueExpression, EncodedValueLookup lookup) { - ParseResult result = parse(valueExpression, lookup::hasEncodedValue); + ParseResult result = parse(valueExpression, key -> lookup.hasEncodedValue(key) || key.contains(INFINITY)); if (!result.ok) throw new IllegalArgumentException(result.invalidMessage); if (result.guessedVariables.size() > 1) throw new IllegalArgumentException("Currently only a single EncodedValue is allowed on the right-hand side, but was " + result.guessedVariables.size() + ". Value expression: " + valueExpression); + + // TODO Nearly duplicate code as in findMinMax + double value; + try { + // Speed optimization for numbers only as its over 200x faster than ExpressionEvaluator+cook+evaluate! + // We still call the parse() method before as it is only ~3x slower and might increase security slightly. Because certain + // expressions are accepted from Double.parseDouble but parse() rejects them. With this call order we avoid unexpected security problems. + value = Double.parseDouble(valueExpression); + } catch (NumberFormatException ex) { + try { + if (result.guessedVariables.isEmpty()) { // without encoded values + NoArgEvaluator ee = new ExpressionEvaluator().createFastEvaluator(valueExpression, NoArgEvaluator.class); + value = ee.evaluate(); + } else if (lookup.hasEncodedValue(valueExpression)) { // speed up for common case that complete right-hand side is the encoded value + EncodedValue enc = lookup.getEncodedValue(valueExpression, EncodedValue.class); + value = Math.min(getMin(enc), getMax(enc)); + } else { + // single encoded value + String var = result.guessedVariables.iterator().next(); + SingleArgEvaluator ee = new ExpressionEvaluator().createFastEvaluator(valueExpression, SingleArgEvaluator.class, var); + EncodedValue enc = lookup.getEncodedValue(var, EncodedValue.class); + double max = getMax(enc); + double val1 = ee.evaluate(max); + double min = getMin(enc); + double val2 = ee.evaluate(min); + value = Math.min(val1, val2); + } + } catch (CompileException ex2) { + throw new IllegalArgumentException(ex2); + } + } + if (value < 0) + throw new IllegalArgumentException("illegal expression as it can result in a negative weight: " + valueExpression); + return result.guessedVariables; } @@ -187,6 +236,7 @@ static MinMax findMinMax(String valueExpression, EncodedValueLookup lookup) { if (result.guessedVariables.size() > 1) throw new IllegalArgumentException("Currently only a single EncodedValue is allowed on the right-hand side, but was " + result.guessedVariables.size() + ". Value expression: " + valueExpression); + // TODO Nearly duplicate as in findVariables try { // Speed optimization for numbers only as its over 200x faster than ExpressionEvaluator+cook+evaluate! // We still call the parse() method before as it is only ~3x slower and might increase security slightly. Because certain @@ -198,9 +248,8 @@ static MinMax findMinMax(String valueExpression, EncodedValueLookup lookup) { try { if (result.guessedVariables.isEmpty()) { // without encoded values - ExpressionEvaluator ee = new ExpressionEvaluator(); - ee.cook(valueExpression); - double val = ((Number) ee.evaluate()).doubleValue(); + NoArgEvaluator ee = new ExpressionEvaluator().createFastEvaluator(valueExpression, NoArgEvaluator.class); + double val = ee.evaluate(); return new MinMax(val, val); } @@ -210,29 +259,39 @@ static MinMax findMinMax(String valueExpression, EncodedValueLookup lookup) { return new MinMax(min, max); } - ExpressionEvaluator ee = new ExpressionEvaluator(); String var = result.guessedVariables.iterator().next(); - ee.setParameters(new String[]{var}, new Class[]{double.class}); - ee.cook(valueExpression); - double max = getMax(lookup.getEncodedValue(var, EncodedValue.class)); - Number val1 = (Number) ee.evaluate(max); - double min = getMin(lookup.getEncodedValue(var, EncodedValue.class)); - Number val2 = (Number) ee.evaluate(min); - return new MinMax(Math.min(val1.doubleValue(), val2.doubleValue()), Math.max(val1.doubleValue(), val2.doubleValue())); - } catch (CompileException | InvocationTargetException ex) { + SingleArgEvaluator ee = new ExpressionEvaluator().createFastEvaluator(valueExpression, SingleArgEvaluator.class, var); + EncodedValue enc = lookup.getEncodedValue(var, EncodedValue.class); + double max = getMax(enc); + double val1 = ee.evaluate(max); + double min = getMin(enc); + double val2 = ee.evaluate(min); + return new MinMax(Math.min(val1, val2), Math.max(val1, val2)); + } catch (CompileException ex) { throw new IllegalArgumentException(ex); } } static double getMin(EncodedValue enc) { - if (enc instanceof DecimalEncodedValue) return ((DecimalEncodedValue) enc).getMinStorableDecimal(); + if (enc instanceof DecimalEncodedValue) + return ((DecimalEncodedValue) enc).getMinStorableDecimal(); else if (enc instanceof IntEncodedValue) return ((IntEncodedValue) enc).getMinStorableInt(); throw new IllegalArgumentException("Cannot use non-number data '" + enc.getName() + "' in value expression"); } static double getMax(EncodedValue enc) { - if (enc instanceof DecimalEncodedValue) return ((DecimalEncodedValue) enc).getMaxOrMaxStorableDecimal(); - else if (enc instanceof IntEncodedValue) return ((IntEncodedValue) enc).getMaxOrMaxStorableInt(); + if (enc instanceof DecimalEncodedValue) + return ((DecimalEncodedValue) enc).getMaxOrMaxStorableDecimal(); + else if (enc instanceof IntEncodedValue) + return ((IntEncodedValue) enc).getMaxOrMaxStorableInt(); throw new IllegalArgumentException("Cannot use non-number data '" + enc.getName() + "' in value expression"); } + + protected interface NoArgEvaluator { + double evaluate(); + } + + protected interface SingleArgEvaluator { + double evaluate(double arg); + } } diff --git a/core/src/main/java/com/graphhopper/search/KVStorage.java b/core/src/main/java/com/graphhopper/search/KVStorage.java index a49b51b9f4b..eb51a0687a0 100644 --- a/core/src/main/java/com/graphhopper/search/KVStorage.java +++ b/core/src/main/java/com/graphhopper/search/KVStorage.java @@ -34,7 +34,17 @@ */ public class KVStorage { - private static final long EMPTY_POINTER = 0, START_POINTER = 1; + private static final long EMPTY_POINTER = 0; + // Align entries to 4-byte boundaries. This allows callers to store pointer >> 2 externally, + // giving 4x the addressable space when storing pointers as unsigned int (~16GB instead of ~4GB). + // Callers are expected to shift the pointer themselves: >> 2 when storing, << 2 when retrieving. + private static final int ALIGNMENT = 4; + /** + * The alignment shift for pointers returned by {@link #add}. Callers should use + * {@code pointer >> ALIGNMENT_SHIFT} when storing and {@code pointer << ALIGNMENT_SHIFT} when retrieving. + */ + public static final int ALIGNMENT_SHIFT = 2; + private static final long START_POINTER = ALIGNMENT; // Store the key index in 2 bytes. Use first 2 bits for marking fwd+bwd existence. static final int MAX_UNIQUE_KEYS = (1 << 14); // Store string value as byte array and store the length into 1 byte @@ -81,7 +91,7 @@ public class KVStorage { private final BitUtil bitUtil = BitUtil.LITTLE; private long bytePointer = START_POINTER; private long lastEntryPointer = -1; - private List lastEntries; + private Map lastEntries; /** * Specify a larger cacheSize to reduce disk usage. Note that this increases the memory usage of this object. @@ -109,7 +119,8 @@ public KVStorage create(long initBytes) { public boolean loadExisting() { if (vals.loadExisting()) { - if (!keys.loadExisting()) throw new IllegalStateException("Loaded values but cannot load keys"); + if (!keys.loadExisting()) + throw new IllegalStateException("Loaded values but cannot load keys"); bytePointer = bitUtil.toLong(vals.getHeader(0), vals.getHeader(4)); GHUtility.checkDAVersion(vals.getName(), Constants.VERSION_KV_STORAGE, vals.getHeader(8)); GHUtility.checkDAVersion(keys.getName(), Constants.VERSION_KV_STORAGE, keys.getHeader(0)); @@ -144,57 +155,67 @@ Collection getKeys() { return indexToKey; } - private long setKVList(long currentPointer, final List entries) { + private long setKVList(long currentPointer, final Map entries) { if (currentPointer == EMPTY_POINTER) return currentPointer; currentPointer += 1; // skip stored count - for (KeyValue entry : entries) { - String key = entry.key; - if (key == null) throw new IllegalArgumentException("key cannot be null"); - Object value = entry.value; - if (value == null) throw new IllegalArgumentException("value for key " + key + " cannot be null"); - if (!entry.fwd && !entry.bwd) - throw new IllegalArgumentException("Do not add KeyValue pair where fwd and bwd is false"); - Integer keyIndex = keyToIndex.get(key); - Class clazz; - if (keyIndex == null) { - keyIndex = keyToIndex.size(); - if (keyIndex >= MAX_UNIQUE_KEYS) - throw new IllegalArgumentException("Cannot store more than " + MAX_UNIQUE_KEYS + " unique keys"); - keyToIndex.put(key, keyIndex); - indexToKey.add(key); - indexToClass.add(clazz = value.getClass()); + for (Map.Entry entry : entries.entrySet()) { + if (entry.getValue().fwdBwdEqual) { + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().fwdValue, true, true); } else { - clazz = indexToClass.get(keyIndex); - if (clazz != value.getClass()) - throw new IllegalArgumentException("Class of value for key " + key + " must be " + clazz.getSimpleName() + " but was " + value.getClass().getSimpleName()); + // potentially add two internal values + if (entry.getValue().fwdValue != null) + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().fwdValue, true, false); + if (entry.getValue().bwdValue != null) + currentPointer = add(currentPointer, entry.getKey(), entry.getValue().bwdValue, false, true); } - boolean hasDynLength = hasDynLength(clazz); - if (hasDynLength) { - // optimization for empty string or empty byte array - if (clazz.equals(String.class) && ((String) value).isEmpty() - || clazz.equals(byte[].class) && ((byte[]) value).length == 0) { - vals.ensureCapacity(currentPointer + 3); - vals.setShort(currentPointer, keyIndex.shortValue()); - // ensure that also in case of MMap value is set to 0 - vals.setByte(currentPointer + 2, (byte) 0); - currentPointer += 3; - continue; - } - } + } + return currentPointer; + } + + long add(long currentPointer, String key, Object value, boolean fwd, boolean bwd) { + if (key == null) throw new IllegalArgumentException("key cannot be null"); + if (value == null) + throw new IllegalArgumentException("value for key " + key + " cannot be null"); + + Integer keyIndex = keyToIndex.get(key); + Class clazz; + if (keyIndex == null) { + keyIndex = keyToIndex.size(); + if (keyIndex >= MAX_UNIQUE_KEYS) + throw new IllegalArgumentException("Cannot store more than " + MAX_UNIQUE_KEYS + " unique keys"); + keyToIndex.put(key, keyIndex); + indexToKey.add(key); + indexToClass.add(clazz = value.getClass()); + } else { + clazz = indexToClass.get(keyIndex); + if (clazz != value.getClass()) + throw new IllegalArgumentException("Class of value for key " + key + " must be " + clazz.getSimpleName() + " but was " + value.getClass().getSimpleName()); + } - final byte[] valueBytes = getBytesForValue(clazz, value); - vals.ensureCapacity(currentPointer + 2 + 1 + valueBytes.length); - vals.setShort(currentPointer, (short) (keyIndex << 2 | (entry.fwd ? 2 : 0) | (entry.bwd ? 1 : 0))); - currentPointer += 2; - if (hasDynLength) { - vals.setByte(currentPointer, (byte) valueBytes.length); - currentPointer++; + boolean hasDynLength = hasDynLength(clazz); + if (hasDynLength) { + // optimization for empty string or empty byte array + if (clazz.equals(String.class) && ((String) value).isEmpty() + || clazz.equals(byte[].class) && ((byte[]) value).length == 0) { + vals.ensureCapacity(currentPointer + 3); + vals.setShort(currentPointer, keyIndex.shortValue()); + // ensure that also in case of MMap value is set to 0 + vals.setByte(currentPointer + 2, (byte) 0); + return currentPointer + 3; } - vals.setBytes(currentPointer, valueBytes, valueBytes.length); - currentPointer += valueBytes.length; } - return currentPointer; + + final byte[] valueBytes = getBytesForValue(clazz, value); + vals.ensureCapacity(currentPointer + 2 + 1 + valueBytes.length); + vals.setShort(currentPointer, (short) (keyIndex << 2 | (fwd ? 2 : 0) | (bwd ? 1 : 0))); + currentPointer += 2; + if (hasDynLength) { + vals.setByte(currentPointer, (byte) valueBytes.length); + currentPointer++; + } + vals.setBytes(currentPointer, valueBytes, valueBytes.length); + return currentPointer + valueBytes.length; } /** @@ -205,7 +226,7 @@ private long setKVList(long currentPointer, final List entries) { * * @return entryPointer with which you can later fetch the entryMap via the get or getAll method */ - public long add(final List entries) { + public long add(final Map entries) { if (entries == null) throw new IllegalArgumentException("specified List must not be null"); if (entries.isEmpty()) return EMPTY_POINTER; else if (entries.size() > 200) @@ -213,51 +234,56 @@ else if (entries.size() > 200) // This is a very important "compression" mechanism because one OSM way is split into multiple edges and so we // can often re-use the serialized key-value pairs of the previous edge. - if (isEquals(entries, lastEntries)) return lastEntryPointer; + if (entries.equals(lastEntries)) return lastEntryPointer; + + int entryCount = 0; + for (Map.Entry kv : entries.entrySet()) { + + if (kv.getValue().fwdBwdEqual) { + entryCount++; + } else { + // note, if fwd and bwd are different we create two internal entries! + if (kv.getValue().getFwd() != null) entryCount++; + if (kv.getValue().getBwd() != null) entryCount++; + } - // If the Class of a value is unknown it should already fail here, before we modify internal data. (see #2597#discussion_r896469840) - for (KeyValue kv : entries) - if (keyToIndex.get(kv.key) != null) - getBytesForValue(indexToClass.get(keyToIndex.get(kv.key)), kv.value); + // If the Class of a value is unknown it should already fail here, before we modify internal data. (see #2597#discussion_r896469840) + if (keyToIndex.get(kv.getKey()) != null) { + if (kv.getValue().fwdValue != null) + getBytesForValue(indexToClass.get(keyToIndex.get(kv.getKey())), kv.getValue().fwdValue); + if (kv.getValue().bwdValue != null) + getBytesForValue(indexToClass.get(keyToIndex.get(kv.getKey())), kv.getValue().bwdValue); + } + } lastEntries = entries; lastEntryPointer = bytePointer; vals.ensureCapacity(bytePointer + 1); - vals.setByte(bytePointer, (byte) entries.size()); + vals.setByte(bytePointer, (byte) entryCount); bytePointer = setKVList(bytePointer, entries); if (bytePointer < 0) throw new IllegalStateException("Negative bytePointer in KVStorage"); + // Pad to next alignment boundary + long remainder = bytePointer % ALIGNMENT; + if (remainder != 0) + bytePointer += ALIGNMENT - remainder; return lastEntryPointer; } - // compared to entries.equals(lastEntries) this method avoids a NPE if a value is null and throws an IAE instead - private boolean isEquals(List entries, List lastEntries) { - if (lastEntries != null && entries.size() == lastEntries.size()) { - for (int i = 0; i < entries.size(); i++) { - KeyValue kv = entries.get(i); - if (kv.value == null) - throw new IllegalArgumentException("value for key " + kv.key + " cannot be null"); - if (!kv.equals(lastEntries.get(i))) return false; - } - return true; - } - return false; - } - - public List getAll(final long entryPointer) { + public Map getAll(final long entryPointer) { if (entryPointer < 0) throw new IllegalStateException("Pointer to access KVStorage cannot be negative:" + entryPointer); - if (entryPointer == EMPTY_POINTER) return Collections.emptyList(); + if (entryPointer == EMPTY_POINTER) return Collections.emptyMap(); int keyCount = vals.getByte(entryPointer) & 0xFF; - if (keyCount == 0) return Collections.emptyList(); + if (keyCount == 0) return Collections.emptyMap(); - List list = new ArrayList<>(keyCount); + Map map = new LinkedHashMap<>(); long tmpPointer = entryPointer + 1; AtomicInteger sizeOfObject = new AtomicInteger(); for (int i = 0; i < keyCount; i++) { - int currentKeyIndexRaw = vals.getShort(tmpPointer); + int currentKeyIndexRaw = Short.toUnsignedInt(vals.getShort(tmpPointer)); boolean bwd = (currentKeyIndexRaw & 1) == 1; boolean fwd = (currentKeyIndexRaw & 2) == 2; int currentKeyIndex = currentKeyIndexRaw >>> 2; @@ -266,10 +292,16 @@ public List getAll(final long entryPointer) { Object object = deserializeObj(sizeOfObject, tmpPointer, indexToClass.get(currentKeyIndex)); tmpPointer += sizeOfObject.get(); String key = indexToKey.get(currentKeyIndex); - list.add(new KeyValue(key, object, fwd, bwd)); + KValue oldValue = map.get(key); + if (oldValue != null) + map.put(key, new KValue(fwd ? object : oldValue.fwdValue, bwd ? object : oldValue.bwdValue)); + else if (fwd && bwd) + map.put(key, new KValue(object)); + else + map.put(key, new KValue(fwd ? object : null, bwd ? object : null)); } - return list; + return map; } /** @@ -289,7 +321,7 @@ public Map getMap(final long entryPointer) { long tmpPointer = entryPointer + 1; AtomicInteger sizeOfObject = new AtomicInteger(); for (int i = 0; i < keyCount; i++) { - int currentKeyIndexRaw = vals.getShort(tmpPointer); + int currentKeyIndexRaw = Short.toUnsignedInt(vals.getShort(tmpPointer)); int currentKeyIndex = currentKeyIndexRaw >>> 2; tmpPointer += 2; @@ -404,15 +436,16 @@ public Object get(final long entryPointer, String key, boolean reverse) { long tmpPointer = entryPointer + 1; for (int i = 0; i < keyCount; i++) { - int currentKeyIndexRaw = vals.getShort(tmpPointer); + int currentKeyIndexRaw = Short.toUnsignedInt(vals.getShort(tmpPointer)); boolean bwd = (currentKeyIndexRaw & 1) == 1; boolean fwd = (currentKeyIndexRaw & 2) == 2; int currentKeyIndex = currentKeyIndexRaw >>> 2; assert currentKeyIndex < indexToKey.size() : "invalid key index " + currentKeyIndex + ">=" + indexToKey.size() + ", entryPointer=" + entryPointer + ", max=" + bytePointer; tmpPointer += 2; - if ((!reverse && fwd || reverse && bwd) && currentKeyIndex == keyIndex) + if ((!reverse && fwd || reverse && bwd) && currentKeyIndex == keyIndex) { return deserializeObj(null, tmpPointer, indexToClass.get(keyIndex)); + } // skip to next entry of same edge via skipping the real value Class clazz = indexToClass.get(currentKeyIndex); @@ -473,62 +506,58 @@ public long getCapacity() { return vals.getCapacity() + keys.getCapacity(); } - public static class KeyValue { - public static final String STREET_NAME = "street_name"; - public static final String STREET_REF = "street_ref"; - public static final String STREET_DESTINATION = "street_destination"; - public static final String STREET_DESTINATION_REF = "street_destination_ref"; - - public String key; - public Object value; - public boolean fwd, bwd; - - public KeyValue(String key, Object value) { - this.key = key; - this.value = value; - this.fwd = true; - this.bwd = true; - } + public static class KValue { + private final Object fwdValue; + private final Object bwdValue; + final boolean fwdBwdEqual; - public Object getValue() { - return value; + public KValue(Object obj) { + if (obj == null) + throw new IllegalArgumentException("Object cannot be null if forward and backward is both true"); + fwdValue = bwdValue = obj; + fwdBwdEqual = true; } - public String getKey() { - return key; + public KValue(Object fwd, Object bwd) { + fwdValue = fwd; + bwdValue = bwd; + if (fwdValue != null && bwdValue != null && fwd.getClass() != bwd.getClass()) + throw new IllegalArgumentException("If both values are not null they have to be they same class but was: " + + fwdValue.getClass() + " vs " + bwdValue.getClass()); + if (fwdValue == null && bwdValue == null) + throw new IllegalArgumentException("If both values are null just do not store them"); + fwdBwdEqual = false; } - public KeyValue(String key, Object value, boolean fwd, boolean bwd) { - this.key = key; - this.value = value; - this.fwd = fwd; - this.bwd = bwd; + public Object getFwd() { + return fwdValue; } - public static List createKV(String key, Object value) { - return Collections.singletonList(new KeyValue(key, value)); + public Object getBwd() { + return bwdValue; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - KeyValue keyValue = (KeyValue) o; - return key.equals(keyValue.key) - && fwd == keyValue.fwd - && bwd == keyValue.bwd - && (value instanceof byte[] && keyValue.value instanceof byte[] && - Arrays.equals((byte[]) value, (byte[]) keyValue.value) || value.equals(keyValue.value)); + KValue value = (KValue) o; + // due to check in constructor we can assume that fwdValue and bwdValue are of same type. + // I.e. if one is a byte array the other is too. + if (fwdValue instanceof byte[] || bwdValue instanceof byte[]) + return fwdBwdEqual == value.fwdBwdEqual && (Arrays.equals((byte[]) fwdValue, (byte[]) value.fwdValue) || Arrays.equals((byte[]) bwdValue, (byte[]) value.bwdValue)); + + return fwdBwdEqual == value.fwdBwdEqual && Objects.equals(fwdValue, value.fwdValue) && Objects.equals(bwdValue, value.bwdValue); } @Override public int hashCode() { - return Objects.hash(key, value, fwd, bwd); + return Objects.hash(fwdValue, bwdValue, fwdBwdEqual); } @Override public String toString() { - return key + '=' + value + " (" + fwd + "|" + bwd + ")"; + return fwdBwdEqual ? fwdValue.toString() : fwdValue + " | " + bwdValue; } } diff --git a/core/src/main/java/com/graphhopper/storage/AbstractDataAccess.java b/core/src/main/java/com/graphhopper/storage/AbstractDataAccess.java index 12f6ec6b1b9..f86c8a3a427 100644 --- a/core/src/main/java/com/graphhopper/storage/AbstractDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/AbstractDataAccess.java @@ -32,7 +32,7 @@ public abstract class AbstractDataAccess implements DataAccess { // reserve some space for downstream usage (in classes using/extending this) protected static final int HEADER_OFFSET = 20 * 4 + 20; protected static final byte[] EMPTY = new byte[1024]; - private static final int SEGMENT_SIZE_DEFAULT = 1 << 20; + public static final int SEGMENT_SIZE_DEFAULT = 1 << 20; protected final ByteOrder byteOrder = ByteOrder.LITTLE_ENDIAN; protected final BitUtil bitUtil = BitUtil.LITTLE; private final String location; diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index e1388d0aae4..92673e99007 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -27,9 +27,13 @@ import com.graphhopper.util.shapes.BBox; import java.io.Closeable; -import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.IntConsumer; +import java.util.function.IntUnaryOperator; import static com.graphhopper.util.Helper.nf; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; /** * The base graph handles nodes and edges file format. It can be used with different Directory @@ -43,7 +47,13 @@ * loadExisting, (4) usage, (5) flush, (6) close */ public class BaseGraph implements Graph, Closeable { + /** + * Maximum distance per edge in meters (~2147 km). + */ + public static final double MAX_DIST_METERS = BaseGraphNodesAndEdges.MAX_DIST_MM / 1000.0; final static long MAX_UNSIGNED_INT = 0xFFFF_FFFFL; + private static final int MAX_PILLAR_NODES = 65535; + private static final int GEOMETRY_HEADER_BYTES = 2; final BaseGraphNodesAndEdges store; final NodeAccess nodeAccess; final KVStorage edgeKVStorage; @@ -51,22 +61,26 @@ public class BaseGraph implements Graph, Closeable { final TurnCostStorage turnCostStorage; final BitUtil bitUtil; // length | nodeA | nextNode | ... | nodeB - // as we use integer index in 'edges' area => 'geometry' area is limited to 4GB (we use pos&neg values!) private final DataAccess wayGeometry; private final Directory dir; - private final int segmentSize; private boolean initialized = false; + private long minGeoRef; private long maxGeoRef; + private final int eleBytesPerCoord; - public BaseGraph(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { + public BaseGraph(Directory dir, boolean withElevation, boolean withTurnCosts, int bytesForFlags) { this.dir = dir; this.bitUtil = BitUtil.LITTLE; - this.wayGeometry = dir.create("geometry", segmentSize); + this.wayGeometry = dir.create("geometry"); this.edgeKVStorage = new KVStorage(dir, true); - this.store = new BaseGraphNodesAndEdges(dir, intsForFlags, withElevation, withTurnCosts, segmentSize); + this.store = new BaseGraphNodesAndEdges(dir, withElevation, withTurnCosts, bytesForFlags); this.nodeAccess = new GHNodeAccess(store); - this.segmentSize = segmentSize; - turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true), segmentSize)) : null; + this.turnCostStorage = withTurnCosts ? new TurnCostStorage(this, dir.create("turn_costs", dir.getDefaultType("turn_costs", true))) : null; + this.eleBytesPerCoord = (nodeAccess.getDimension() == 3 ? 3 : 0); + } + + BaseGraphNodesAndEdges getStore() { + return store; } private int getOtherNode(int nodeThis, long edgePointer) { @@ -106,16 +120,22 @@ void checkNotInitialized() { private void loadWayGeometryHeader() { int geometryVersion = wayGeometry.getHeader(0); GHUtility.checkDAVersion(wayGeometry.getName(), Constants.VERSION_GEOMETRY, geometryVersion); - maxGeoRef = bitUtil.toLong( + minGeoRef = bitUtil.toLong( wayGeometry.getHeader(4), wayGeometry.getHeader(8) ); + maxGeoRef = bitUtil.toLong( + wayGeometry.getHeader(12), + wayGeometry.getHeader(16) + ); } private void setWayGeometryHeader() { wayGeometry.setHeader(0, Constants.VERSION_GEOMETRY); - wayGeometry.setHeader(4, bitUtil.getIntLow(maxGeoRef)); - wayGeometry.setHeader(8, bitUtil.getIntHigh(maxGeoRef)); + wayGeometry.setHeader(4, bitUtil.getIntLow(minGeoRef)); + wayGeometry.setHeader(8, bitUtil.getIntHigh(minGeoRef)); + wayGeometry.setHeader(12, bitUtil.getIntLow(maxGeoRef)); + wayGeometry.setHeader(16, bitUtil.getIntHigh(maxGeoRef)); } private void setInitialized() { @@ -136,6 +156,10 @@ public int getEdges() { return store.getEdges(); } + public int getEdgeKeys() { + return 2 * store.getEdges(); + } + @Override public NodeAccess getNodeAccess() { return nodeAccess; @@ -168,19 +192,16 @@ public BaseGraph create(long initSize) { turnCostStorage.create(initSize); } setInitialized(); - // 0 stands for no separate geoRef - maxGeoRef = 4; + // 0 stands for no separate geoRef, <0 stands for no separate geoRef but existing edge copies + minGeoRef = -1; + maxGeoRef = 1; return this; } - public int getIntsForFlags() { - return store.getIntsForFlags(); - } - public String toDetailsString() { return store.toDetailsString() + ", " + "name:(" + edgeKVStorage.getCapacity() / Helper.MB + "MB), " - + "geo:" + nf(maxGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; + + "geo:" + nf(maxGeoRef) + "/" + nf(minGeoRef) + "(" + wayGeometry.getCapacity() / Helper.MB + "MB)"; } /** @@ -262,7 +283,7 @@ EdgeIteratorState copyProperties(EdgeIteratorState from, EdgeIteratorStateImpl t store.writeFlags(edgePointer, from.getFlags()); // copy the rest with higher level API - to.setDistance(from.getDistance()). + to.setDistance_mm(from.getDistance_mm()). setKeyValues(from.getKeyValues()). setWayGeometry(from.fetchWayGeometry(FetchMode.PILLAR_ONLY)); @@ -293,6 +314,90 @@ public EdgeIteratorState edge(int nodeA, int nodeB) { return edge; } + /** + * Creates a copy of a given edge with the same properties. + * + * @param reuseGeometry If true the copy uses the same pointer to the geometry, + * so changing the geometry would alter the geometry for both edges! + */ + public EdgeIteratorState copyEdge(int edge, boolean reuseGeometry) { + EdgeIteratorStateImpl edgeState = (EdgeIteratorStateImpl) getEdgeIteratorState(edge, Integer.MIN_VALUE); + EdgeIteratorStateImpl newEdge = (EdgeIteratorStateImpl) edge(edgeState.getBaseNode(), edgeState.getAdjNode()) + .setFlags(edgeState.getFlags()) + .setDistance_mm(edgeState.getDistance_mm()) + .setKeyValues(edgeState.getKeyValues()); + if (reuseGeometry) { + // We use the same geo ref for the copied edge. This saves memory because we are not duplicating + // the geometry, and it allows to identify the copies of a given edge. + long edgePointer = edgeState.edgePointer; + long geoRef = store.getGeoRef(edgePointer); + if (geoRef == 0) { + // No geometry for this edge, but we need to be able to identify the copied edges later, so + // we use a dedicated negative value for the geo ref. + geoRef = minGeoRef; + store.setGeoRef(edgePointer, geoRef); + minGeoRef--; + } + store.setGeoRef(newEdge.edgePointer, geoRef); + } else { + newEdge.setWayGeometry(edgeState.fetchWayGeometry(FetchMode.PILLAR_ONLY)); + } + return newEdge; + } + + /** + * Runs the given action on the given edge and all its copies that were created with 'reuseGeometry=true'. + */ + public void forEdgeAndCopiesOfEdge(EdgeExplorer explorer, EdgeIteratorState edge, Consumer consumer) { + final long geoRef = store.getGeoRef(((EdgeIteratorStateImpl) edge).edgePointer); + if (geoRef == 0) { + // 0 means there is no geometry (and no copy of this edge), but of course not all edges + // without geometry are copies of each other, so we need to return early + consumer.accept(edge); + return; + } + EdgeIterator iter = explorer.setBaseNode(edge.getBaseNode()); + while (iter.next()) { + long geoRefBefore = store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer); + if (geoRefBefore == geoRef) + consumer.accept(iter); + if (store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer) != geoRefBefore) + throw new IllegalStateException("The consumer must not change the geo ref"); + } + } + + public void forEdgeAndCopiesOfEdge(EdgeExplorer explorer, int node, int edge, IntConsumer consumer) { + final long geoRef = store.getGeoRef(store.toEdgePointer(edge)); + if (geoRef == 0) { + // 0 means there is no geometry (and no copy of this edge), but of course not all edges + // without geometry are copies of each other, so we need to return early + consumer.accept(edge); + return; + } + EdgeIterator iter = explorer.setBaseNode(node); + while (iter.next()) { + long geoRefBefore = store.getGeoRef(((EdgeIteratorStateImpl) iter).edgePointer); + if (geoRefBefore == geoRef) + consumer.accept(iter.getEdge()); + } + } + + public void sortEdges(IntUnaryOperator getNewEdgeForOldEdge) { + if (isFrozen()) + throw new IllegalStateException("Cannot sort edges if graph is already frozen"); + store.sortEdges(getNewEdgeForOldEdge); + if (supportsTurnCosts()) + turnCostStorage.sortEdges(getNewEdgeForOldEdge); + } + + public void relabelNodes(IntUnaryOperator getNewNodeForOldNode) { + if (isFrozen()) + throw new IllegalStateException("Cannot relabel nodes if graph is already frozen"); + store.relabelNodes(getNewNodeForOldNode); + if (supportsTurnCosts()) + turnCostStorage.sortNodes(); + } + @Override public EdgeIteratorState getEdgeIteratorState(int edgeId, int adjNode) { EdgeIteratorStateImpl edge = new EdgeIteratorStateImpl(this); @@ -346,68 +451,70 @@ public boolean isAdjacentToNode(int edge, int node) { return isAdjacentToNode(node, edgePointer); } + /** + * @return true if the specified node is the adjacent node of the specified edge + * (relative to the direction in which the edge is stored). + */ + public boolean isAdjNode(int edge, int node) { + long edgePointer = store.toEdgePointer(edge); + return node == store.getNodeB(edgePointer); + } + private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean reverse) { if (pillarNodes != null && !pillarNodes.isEmpty()) { if (pillarNodes.getDimension() != nodeAccess.getDimension()) throw new IllegalArgumentException("Cannot use pointlist which is " + pillarNodes.getDimension() + "D for graph which is " + nodeAccess.getDimension() + "D"); - long existingGeoRef = Integer.toUnsignedLong(store.getGeoRef(edgePointer)); + long existingGeoRef = store.getGeoRef(edgePointer); + if (existingGeoRef < 0) + // users of this method might not be aware that after changing the geo ref it is no + // longer possible to find the copies corresponding to an edge, so we deny this + throw new IllegalStateException("This edge has already been copied so we can no longer change the geometry, pointer=" + edgePointer); int len = pillarNodes.size(); - int dim = nodeAccess.getDimension(); if (existingGeoRef > 0) { - final int count = wayGeometry.getInt(existingGeoRef * 4L); + final int count = getPillarCount(existingGeoRef); if (len <= count) { setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, existingGeoRef); return; + } else { + throw new IllegalStateException("This edge already has a way geometry so it cannot be changed to a bigger geometry, pointer=" + edgePointer); } } - - long nextGeoRef = nextGeoRef(len * dim); + long nextGeoRef = nextGeoRef(GEOMETRY_HEADER_BYTES + len * (8 + eleBytesPerCoord)); setWayGeometryAtGeoRef(pillarNodes, edgePointer, reverse, nextGeoRef); } else { - store.setGeoRef(edgePointer, 0); + store.setGeoRef(edgePointer, 0L); } } - public EdgeIntAccess createEdgeIntAccess() { - return new EdgeIntAccess() { - @Override - public int getInt(int edgeId, int index) { - long edgePointer = store.toEdgePointer(edgeId); - return store.getFlagInt(edgePointer, index); - } - - @Override - public void setInt(int edgeId, int index, int value) { - long edgePointer = store.toEdgePointer(edgeId); - store.setFlagInt(edgePointer, index, value); - } - }; + public EdgeIntAccess getEdgeAccess() { + return store; } private void setWayGeometryAtGeoRef(PointList pillarNodes, long edgePointer, boolean reverse, long geoRef) { - int len = pillarNodes.size(); - int dim = nodeAccess.getDimension(); - long geoRefPosition = geoRef * 4; - int totalLen = len * dim * 4 + 4; - ensureGeometry(geoRefPosition, totalLen); byte[] wayGeometryBytes = createWayGeometryBytes(pillarNodes, reverse); - wayGeometry.setBytes(geoRefPosition, wayGeometryBytes, wayGeometryBytes.length); - store.setGeoRef(edgePointer, BitUtil.toSignedInt(geoRef)); + wayGeometry.ensureCapacity(geoRef + wayGeometryBytes.length); + wayGeometry.setBytes(geoRef, wayGeometryBytes, wayGeometryBytes.length); + store.setGeoRef(edgePointer, geoRef); } private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { int len = pillarNodes.size(); - int dim = nodeAccess.getDimension(); - int totalLen = len * dim * 4 + 4; + if (len > MAX_PILLAR_NODES) throw new IllegalArgumentException("Too many pillar nodes: " + len); + + // We use 2 bytes to store the node count (unsigned short) + // Per node we need 4 bytes for Latitude and Longitude, and possibly 3 bytes if we have elevation. + int totalLen = GEOMETRY_HEADER_BYTES + len * (8 + eleBytesPerCoord); + byte[] bytes = new byte[totalLen]; - bitUtil.fromInt(bytes, len, 0); + bitUtil.fromShort(bytes, (short) len, 0); + if (reverse) pillarNodes.reverse(); - int tmpOffset = 4; + int tmpOffset = GEOMETRY_HEADER_BYTES; boolean is3D = nodeAccess.is3D(); for (int i = 0; i < len; i++) { double lat = pillarNodes.getLat(i); @@ -417,13 +524,18 @@ private byte[] createWayGeometryBytes(PointList pillarNodes, boolean reverse) { tmpOffset += 4; if (is3D) { - bitUtil.fromInt(bytes, Helper.eleToInt(pillarNodes.getEle(i)), tmpOffset); - tmpOffset += 4; + bitUtil.fromUInt3(bytes, Helper.eleToUInt(pillarNodes.getEle(i)), tmpOffset); + tmpOffset += 3; } } return bytes; } + private int getPillarCount(long geoRef) { + // Read short as unsigned int + return wayGeometry.getShort(geoRef) & 0xFFFF; + } + private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode mode, int baseNode, int adjNode) { if (mode == FetchMode.TOWER_ONLY) { // no reverse handling required as adjNode and baseNode is already properly switched @@ -432,15 +544,13 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode pillarNodes.add(nodeAccess, adjNode); return pillarNodes; } - long geoRef = Integer.toUnsignedLong(store.getGeoRef(edgePointer)); + long geoRef = store.getGeoRef(edgePointer); int count = 0; byte[] bytes = null; if (geoRef > 0) { - geoRef *= 4L; - count = wayGeometry.getInt(geoRef); - - geoRef += 4L; - bytes = new byte[count * nodeAccess.getDimension() * 4]; + count = getPillarCount(geoRef); + geoRef += GEOMETRY_HEADER_BYTES; + bytes = new byte[count * (8 + eleBytesPerCoord)]; wayGeometry.getBytes(geoRef, bytes, bytes.length); } else if (mode == FetchMode.PILLAR_ONLY) return PointList.EMPTY; @@ -459,8 +569,8 @@ private PointList fetchWayGeometry_(long edgePointer, boolean reverse, FetchMode double lon = Helper.intToDegree(bitUtil.toInt(bytes, index)); index += 4; if (nodeAccess.is3D()) { - pillarNodes.add(lat, lon, Helper.intToEle(bitUtil.toInt(bytes, index))); - index += 4; + pillarNodes.add(lat, lon, Helper.uIntToEle(bitUtil.toUInt3(bytes, index))); + index += 3; } else { pillarNodes.add(lat, lon); } @@ -492,16 +602,9 @@ static int getPointListLength(int pillarNodes, FetchMode mode) { throw new IllegalArgumentException("Mode isn't handled " + mode); } - private void ensureGeometry(long bytePos, int byteLength) { - wayGeometry.ensureCapacity(bytePos + byteLength); - } - - private long nextGeoRef(int arrayLength) { + private long nextGeoRef(int bytes) { long tmp = maxGeoRef; - maxGeoRef += arrayLength + 1L; - if (maxGeoRef > MAX_UNSIGNED_INT) - throw new IllegalStateException("Geometry too large, does not fit in 32 bits " + maxGeoRef); - + maxGeoRef += bytes; return tmp; } @@ -513,25 +616,20 @@ public Directory getDirectory() { return dir; } - public int getSegmentSize() { - return segmentSize; - } - public static class Builder { - private final int intsForFlags; - private Directory directory = new RAMDirectory(); + private final int bytesForFlags; + private Directory directory = new GHDirectory("", DAType.RAM); private boolean withElevation = false; private boolean withTurnCosts = false; private long bytes = 100; - private int segmentSize = -1; public Builder(EncodingManager em) { - this(em.getIntsForFlags()); + this(em.getBytesForFlags()); withTurnCosts(em.needsTurnCostsSupport()); } - public Builder(int intsForFlags) { - this.intsForFlags = intsForFlags; + public Builder(int bytesForFlags) { + this.bytesForFlags = bytesForFlags; } // todo: maybe rename later, but for now this makes it easier to replace GraphBuilder @@ -552,18 +650,13 @@ public Builder withTurnCosts(boolean withTurnCosts) { return this; } - public Builder setSegmentSize(int segmentSize) { - this.segmentSize = segmentSize; - return this; - } - public Builder setBytes(long bytes) { this.bytes = bytes; return this; } public BaseGraph build() { - return new BaseGraph(directory, intsForFlags, withElevation, withTurnCosts, segmentSize); + return new BaseGraph(directory, withElevation, withTurnCosts, bytesForFlags); } public BaseGraph create() { @@ -682,7 +775,7 @@ static class EdgeIteratorStateImpl implements EdgeIteratorState { public EdgeIteratorStateImpl(BaseGraph baseGraph) { this.baseGraph = baseGraph; - edgeIntAccess = baseGraph.createEdgeIntAccess(); + edgeIntAccess = baseGraph.getEdgeAccess(); store = baseGraph.store; } @@ -743,18 +836,44 @@ public final int getAdjNode() { @Override public double getDistance() { - return store.getDist(edgePointer); + // never return infinity even if dist_mm is INT MAX, see #435 + return getDistance_mm() / 1000.0; } @Override public EdgeIteratorState setDistance(double dist) { - store.setDist(edgePointer, dist); + if (dist < 0) + throw new IllegalArgumentException("distances must be non-negative, got: " + dist); + // distances above ~2147km are capped + if (dist > MAX_DIST_METERS) + dist = MAX_DIST_METERS; + // distances below 0.5mm are rounded down to zero + long dist_mm = Math.round(dist * 1000); + setDistance_mm(dist_mm); + return this; + } + + /** + * Returns the distance in millimeters + */ + @Override + public long getDistance_mm() { + return store.getDist_mm(edgePointer); + } + + @Override + public EdgeIteratorState setDistance_mm(long distance_mm) { + if (distance_mm < 0) + throw new IllegalArgumentException("distances must be non-negative, got: " + distance_mm); + if (distance_mm > BaseGraphNodesAndEdges.MAX_DIST_MM) + distance_mm = BaseGraphNodesAndEdges.MAX_DIST_MM; + store.setDist_mm(edgePointer, distance_mm); return this; } @Override public IntsRef getFlags() { - IntsRef edgeFlags = new IntsRef(store.getIntsForFlags()); + IntsRef edgeFlags = store.createEdgeFlags(); store.readFlags(edgePointer, edgeFlags); return edgeFlags; } @@ -953,29 +1072,35 @@ public int getReverseEdgeKey() { } @Override - public EdgeIteratorState setKeyValues(List entries) { + public EdgeIteratorState setKeyValues(Map entries) { long pointer = baseGraph.edgeKVStorage.add(entries); - if (pointer > MAX_UNSIGNED_INT) - throw new IllegalStateException("Too many key value pairs are stored, currently limited to " + MAX_UNSIGNED_INT + " was " + pointer); - store.setKeyValuesRef(edgePointer, BitUtil.toSignedInt(pointer)); + // Shift right to use 4x more address space (pointers are 4-byte aligned) + long shiftedPointer = pointer >> KVStorage.ALIGNMENT_SHIFT; + if (shiftedPointer > MAX_UNSIGNED_INT) + throw new IllegalStateException("Too many key value pairs are stored, currently limited to " + (MAX_UNSIGNED_INT << KVStorage.ALIGNMENT_SHIFT) + " was " + pointer); + store.setKeyValuesRef(edgePointer, BitUtil.toSignedInt(shiftedPointer)); return this; } @Override - public List getKeyValues() { - long kvEntryRef = Integer.toUnsignedLong(store.getKeyValuesRef(edgePointer)); + public Map getKeyValues() { + long shiftedRef = Integer.toUnsignedLong(store.getKeyValuesRef(edgePointer)); + // Shift left to restore the actual byte offset + long kvEntryRef = shiftedRef << KVStorage.ALIGNMENT_SHIFT; return baseGraph.edgeKVStorage.getAll(kvEntryRef); } @Override public Object getValue(String key) { - long kvEntryRef = Integer.toUnsignedLong(store.getKeyValuesRef(edgePointer)); + long shiftedRef = Integer.toUnsignedLong(store.getKeyValuesRef(edgePointer)); + // Shift left to restore the actual byte offset + long kvEntryRef = shiftedRef << KVStorage.ALIGNMENT_SHIFT; return baseGraph.edgeKVStorage.get(kvEntryRef, key, reverse); } @Override public String getName() { - String name = (String) getValue(KVStorage.KeyValue.STREET_NAME); + String name = (String) getValue(STREET_NAME); // preserve backward compatibility (returns empty string if name tag missing) return name == null ? "" : name; } @@ -994,6 +1119,11 @@ public EdgeIteratorState detach(boolean reverseArg) { return edge; } + @Override + public boolean isVirtual() { + return false; + } + @Override public final String toString() { return getEdge() + " " + getBaseNode() + "-" + getAdjNode(); diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java index 673eda58753..7c79b405d9f 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraphNodesAndEdges.java @@ -18,13 +18,13 @@ package com.graphhopper.storage; -import com.graphhopper.util.Constants; -import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.GHUtility; -import com.graphhopper.util.Helper; +import com.carrotsearch.hppc.BitSet; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import java.util.Locale; +import java.util.function.IntUnaryOperator; import static com.graphhopper.util.EdgeIterator.NO_EDGE; import static com.graphhopper.util.Helper.nf; @@ -33,12 +33,13 @@ * Underlying storage for nodes and edges of {@link BaseGraph}. Nodes and edges are stored using two {@link DataAccess} * instances. Nodes and edges are simply stored sequentially, see the memory layout in the constructor. */ -class BaseGraphNodesAndEdges { - // Currently distances are stored as 4 byte integers. using a conversion factor of 1000 the minimum distance - // that is not considered zero is 0.0005m (=0.5mm) and the maximum distance per edge is about 2.147.483m=2147km. - // See OSMReader.addEdge and #1871. - private static final double INT_DIST_FACTOR = 1000d; - static double MAX_DIST = Integer.MAX_VALUE / INT_DIST_FACTOR; +class BaseGraphNodesAndEdges implements EdgeIntAccess { + // Distances are stored as 4-byte signed integers representing mm -> max ~2147km + // We could quite easily use unsigned 4-byte integers to raise the max to ~4294km, + // but if we ever wanted to use 4 instead of 8 bytes to represent (accumulated) distances + // downstream, we would either have to lower the limit again, deal with unsigned arithmetic + // everywhere, or increase the precision from 1mm to, say, 10mm + static final long MAX_DIST_MM = Integer.MAX_VALUE; // nodes private final DataAccess nodes; @@ -48,8 +49,8 @@ class BaseGraphNodesAndEdges { // edges private final DataAccess edges; - private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_FLAGS, E_DIST, E_GEO, E_KV; - private final int intsForFlags; + private final int E_NODEA, E_NODEB, E_LINKA, E_LINKB, E_DIST, E_KV, E_FLAGS, E_GEO; + private final int bytesForFlags; private int edgeEntryBytes; private int edgeCount; @@ -62,10 +63,10 @@ class BaseGraphNodesAndEdges { public final BBox bounds; private boolean frozen; - public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withElevation, boolean withTurnCosts, int segmentSize) { - nodes = dir.create("nodes", dir.getDefaultType("nodes", true), segmentSize); - edges = dir.create("edges", dir.getDefaultType("edges", true), segmentSize); - this.intsForFlags = intsForFlags; + public BaseGraphNodesAndEdges(Directory dir, boolean withElevation, boolean withTurnCosts, int bytesForFlags) { + nodes = dir.create("nodes", dir.getDefaultType("nodes", true)); + edges = dir.create("edges", dir.getDefaultType("edges", false)); + this.bytesForFlags = bytesForFlags; this.withTurnCosts = withTurnCosts; this.withElevation = withElevation; bounds = BBox.createInverse(withElevation); @@ -83,11 +84,11 @@ public BaseGraphNodesAndEdges(Directory dir, int intsForFlags, boolean withEleva E_NODEB = 4; E_LINKA = 8; E_LINKB = 12; - E_FLAGS = 16; - E_DIST = E_FLAGS + intsForFlags * 4; - E_GEO = E_DIST + 4; - E_KV = E_GEO + 4; - edgeEntryBytes = E_KV + 4; + E_DIST = 16; + E_KV = 20; + E_FLAGS = 24; + E_GEO = E_FLAGS + bytesForFlags; + edgeEntryBytes = E_GEO + 5; } public void create(long initSize) { @@ -114,8 +115,8 @@ public boolean loadExisting() { throw new IllegalStateException("Configured dimension elevation=" + withElevation + " is not equal " + "to dimension of loaded graph elevation =" + hasElevation); if (withElevation) { - bounds.minEle = Helper.intToEle(nodes.getHeader(8 * 4)); - bounds.maxEle = Helper.intToEle(nodes.getHeader(9 * 4)); + bounds.minEle = Helper.uIntToEle(nodes.getHeader(8 * 4)); + bounds.maxEle = Helper.uIntToEle(nodes.getHeader(9 * 4)); } frozen = nodes.getHeader(10 * 4) == 1; @@ -136,8 +137,8 @@ public void flush() { nodes.setHeader(6 * 4, Helper.degreeToInt(bounds.maxLat)); nodes.setHeader(7 * 4, withElevation ? 1 : 0); if (withElevation) { - nodes.setHeader(8 * 4, Helper.eleToInt(bounds.minEle)); - nodes.setHeader(9 * 4, Helper.eleToInt(bounds.maxEle)); + nodes.setHeader(8 * 4, Helper.eleToUInt(bounds.minEle)); + nodes.setHeader(9 * 4, Helper.eleToUInt(bounds.maxEle)); } nodes.setHeader(10 * 4, frozen ? 1 : 0); @@ -162,8 +163,12 @@ public int getEdges() { return edgeCount; } - public int getIntsForFlags() { - return intsForFlags; + IntsRef createEdgeFlags() { + return new IntsRef((int) Math.ceil((double) getBytesForFlags() / 4)); + } + + public int getBytesForFlags() { + return bytesForFlags; } public boolean withElevation() { @@ -216,6 +221,114 @@ public int edge(int nodeA, int nodeB) { return edge; } + public void sortEdges(IntUnaryOperator getNewEdgeForOldEdge) { + BitSet visited = new BitSet(getEdges()); + for (int edge = 0; edge < getEdges(); edge++) { + if (visited.get(edge)) continue; + int curr = edge; + + long pointer = toEdgePointer(curr); + int nodeA = getNodeA(pointer); + int nodeB = getNodeB(pointer); + int linkA = getLinkA(pointer); + int linkB = getLinkB(pointer); + int dist = edges.getInt(pointer + E_DIST); + int kv = getKeyValuesRef(pointer); + IntsRef flags = createEdgeFlags(); + readFlags(pointer, flags); + long geo = getGeoRef(pointer); + + do { + visited.set(curr); + + int newEdge = getNewEdgeForOldEdge.applyAsInt(curr); + long newPointer = toEdgePointer(newEdge); + int tmpNodeA = getNodeA(newPointer); + int tmpNodeB = getNodeB(newPointer); + int tmpLinkA = getLinkA(newPointer); + int tmpLinkB = getLinkB(newPointer); + int tmpDist = edges.getInt(newPointer + E_DIST); + int tmpKV = getKeyValuesRef(newPointer); + IntsRef tmpFlags = createEdgeFlags(); + readFlags(newPointer, tmpFlags); + long tmpGeo = getGeoRef(newPointer); + + setNodeA(newPointer, nodeA); + setNodeB(newPointer, nodeB); + setLinkA(newPointer, linkA == -1 ? -1 : getNewEdgeForOldEdge.applyAsInt(linkA)); + setLinkB(newPointer, linkB == -1 ? -1 : getNewEdgeForOldEdge.applyAsInt(linkB)); + edges.setInt(newPointer + E_DIST, dist); + setKeyValuesRef(newPointer, kv); + writeFlags(newPointer, flags); + setGeoRef(newPointer, geo); + + nodeA = tmpNodeA; + nodeB = tmpNodeB; + linkA = tmpLinkA; + linkB = tmpLinkB; + dist = tmpDist; + kv = tmpKV; + flags = tmpFlags; + geo = tmpGeo; + + curr = newEdge; + } while (curr != edge); + } + + // update edge references + for (int node = 0; node < getNodes(); node++) { + long pointer = toNodePointer(node); + setEdgeRef(pointer, getNewEdgeForOldEdge.applyAsInt(getEdgeRef(pointer))); + } + } + + public void relabelNodes(IntUnaryOperator getNewNodeForOldNode) { + for (int edge = 0; edge < getEdges(); edge++) { + long pointer = toEdgePointer(edge); + setNodeA(pointer, getNewNodeForOldNode.applyAsInt(getNodeA(pointer))); + setNodeB(pointer, getNewNodeForOldNode.applyAsInt(getNodeB(pointer))); + } + BitSet visited = new BitSet(getNodes()); + for (int node = 0; node < getNodes(); node++) { + if (visited.get(node)) continue; + + int curr = node; + long pointer = toNodePointer(node); + int edgeRef = getEdgeRef(pointer); + double lat = getLat(pointer); + double lon = getLon(pointer); + double ele = withElevation() ? getEle(pointer) : Double.NaN; + int tc = withTurnCosts() ? getTurnCostRef(pointer) : -1; + + do { + visited.set(curr); + int newNode = getNewNodeForOldNode.applyAsInt(curr); + long newPointer = toNodePointer(newNode); + int tmpEdgeRef = getEdgeRef(newPointer); + double tmpLat = getLat(newPointer); + double tmpLon = getLon(newPointer); + double tmpEle = withElevation() ? getEle(newPointer) : Double.NaN; + int tmpTC = withTurnCosts() ? getTurnCostRef(newPointer) : -1; + + setEdgeRef(newPointer, edgeRef); + setLat(newPointer, lat); + setLon(newPointer, lon); + if (withElevation()) + setEle(newPointer, ele); + if (withTurnCosts()) + setTurnCostRef(newPointer, tc); + + edgeRef = tmpEdgeRef; + lat = tmpLat; + lon = tmpLon; + ele = tmpEle; + tc = tmpTC; + + curr = newNode; + } while (curr != node); + } + } + public void ensureNodeCapacity(int node) { if (node < nodeCount) return; @@ -245,21 +358,59 @@ public long toEdgePointer(int edge) { public void readFlags(long edgePointer, IntsRef edgeFlags) { int size = edgeFlags.ints.length; for (int i = 0; i < size; ++i) - edgeFlags.ints[i] = getFlagInt(edgePointer, i); + edgeFlags.ints[i] = getFlagInt(edgePointer, i * 4); } public void writeFlags(long edgePointer, IntsRef edgeFlags) { int size = edgeFlags.ints.length; for (int i = 0; i < size; ++i) - setFlagInt(edgePointer, i, edgeFlags.ints[i]); + setFlagInt(edgePointer, i * 4, edgeFlags.ints[i]); + } + + private int getFlagInt(long edgePointer, int byteOffset) { + if (byteOffset >= bytesForFlags) + throw new IllegalArgumentException("too large byteOffset " + byteOffset + " vs " + bytesForFlags); + edgePointer += byteOffset; + if (byteOffset + 3 == bytesForFlags) { + return (edges.getShort(edgePointer + E_FLAGS) << 8) & 0x00FF_FFFF | edges.getByte(edgePointer + E_FLAGS + 2) & 0xFF; + } else if (byteOffset + 2 == bytesForFlags) { + return edges.getShort(edgePointer + E_FLAGS) & 0xFFFF; + } else if (byteOffset + 1 == bytesForFlags) { + return edges.getByte(edgePointer + E_FLAGS) & 0xFF; + } + return edges.getInt(edgePointer + E_FLAGS); + } + + private void setFlagInt(long edgePointer, int byteOffset, int value) { + if (byteOffset >= bytesForFlags) + throw new IllegalArgumentException("too large byteOffset " + byteOffset + " vs " + bytesForFlags); + edgePointer += byteOffset; + if (byteOffset + 3 == bytesForFlags) { + if ((value & 0xFF00_0000) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the highest byte set but was " + value); + edges.setShort(edgePointer + E_FLAGS, (short) (value >> 8)); + edges.setByte(edgePointer + E_FLAGS + 2, (byte) value); + } else if (byteOffset + 2 == bytesForFlags) { + if ((value & 0xFFFF_0000) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the 2 highest bytes set but was " + value); + edges.setShort(edgePointer + E_FLAGS, (short) value); + } else if (byteOffset + 1 == bytesForFlags) { + if ((value & 0xFFFF_FF00) != 0) + throw new IllegalArgumentException("value at byteOffset " + byteOffset + " must not have the 3 highest bytes set but was " + value); + edges.setByte(edgePointer + E_FLAGS, (byte) value); + } else { + edges.setInt(edgePointer + E_FLAGS, value); + } } - public int getFlagInt(long edgePointer, int index) { - return edges.getInt(edgePointer + E_FLAGS + index * 4); + @Override + public int getInt(int edgeId, int index) { + return getFlagInt(toEdgePointer(edgeId), index * 4); } - public void setFlagInt(long edgePointer, int index, int value) { - edges.setInt(edgePointer + E_FLAGS + index * 4, value); + @Override + public void setInt(int edgeId, int index, int value) { + setFlagInt(toEdgePointer(edgeId), index * 4, value); } public void setNodeA(long edgePointer, int nodeA) { @@ -278,12 +429,26 @@ public void setLinkB(long edgePointer, int linkB) { edges.setInt(edgePointer + E_LINKB, linkB); } - public void setDist(long edgePointer, double distance) { - edges.setInt(edgePointer + E_DIST, distToInt(distance)); + public long getDist_mm(long pointer) { + return edges.getInt(pointer + E_DIST); + } + + public void setDist_mm(long pointer, long distance_mm) { + if (distance_mm < 0) + throw new IllegalArgumentException("distances must be non-negative, got: " + distance_mm); + if (distance_mm > MAX_DIST_MM) + throw new IllegalArgumentException("distances must not exceed " + MAX_DIST_MM + "mm, got: " + distance_mm); + edges.setInt(pointer + E_DIST, (int) distance_mm); } - public void setGeoRef(long edgePointer, int geoRef) { - edges.setInt(edgePointer + E_GEO, geoRef); + public void setGeoRef(long edgePointer, long geoRef) { + int highest25Bits = (int) (geoRef >>> 39); + // Only two cases are allowed for highest bits. If geoRef is positive then all high bits are 0. If negative then all are 1. + if (highest25Bits != 0 && highest25Bits != 0x1_FF_FFFF) + throw new IllegalArgumentException("geoRef is too " + (geoRef > 0 ? "large " : "small ") + geoRef + ", " + Long.toBinaryString(geoRef)); + + edges.setInt(edgePointer + E_GEO, (int) (geoRef)); + edges.setByte(edgePointer + E_GEO + 4, (byte) (geoRef >> 32)); } public void setKeyValuesRef(long edgePointer, int nameRef) { @@ -306,14 +471,11 @@ public int getLinkB(long edgePointer) { return edges.getInt(edgePointer + E_LINKB); } - public double getDist(long pointer) { - int val = edges.getInt(pointer + E_DIST); - // do never return infinity even if INT MAX, see #435 - return val / INT_DIST_FACTOR; - } - - public int getGeoRef(long edgePointer) { - return edges.getInt(edgePointer + E_GEO); + public long getGeoRef(long edgePointer) { + return BitUtil.LITTLE.toLong( + edges.getInt(edgePointer + E_GEO), + // to support negative georefs (#2985) do not mask byte with 0xFF: + edges.getByte(edgePointer + E_GEO + 4)); } public int getKeyValuesRef(long edgePointer) { @@ -333,7 +495,7 @@ public void setLon(long nodePointer, double lon) { } public void setEle(long elePointer, double ele) { - nodes.setInt(elePointer + N_ELE, Helper.eleToInt(ele)); + nodes.setInt(elePointer + N_ELE, Helper.eleToUInt(ele)); } public void setTurnCostRef(long nodePointer, int tcRef) { @@ -353,7 +515,7 @@ public double getLon(long nodePointer) { } public double getEle(long nodePointer) { - return Helper.intToEle(nodes.getInt(nodePointer + N_ELE)); + return Helper.uIntToEle(nodes.getInt(nodePointer + N_ELE)); } public int getTurnCostRef(long nodePointer) { @@ -371,45 +533,35 @@ public boolean getFrozen() { public void debugPrint() { final int printMax = 100; System.out.println("nodes:"); - String formatNodes = "%12s | %12s | %12s | %12s \n"; - System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON"); + String formatNodes = "%12s | %12s | %12s | %12s | %12s | %15s\n"; + System.out.format(Locale.ROOT, formatNodes, "#", "N_EDGE_REF", "N_LAT", "N_LON", "N_ELE", "N_TC"); for (int i = 0; i < Math.min(nodeCount, printMax); ++i) { long nodePointer = toNodePointer(i); - System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(nodePointer), getLat(nodePointer), getLon(nodePointer)); + System.out.format(Locale.ROOT, formatNodes, i, getEdgeRef(nodePointer), getLat(nodePointer), getLon(nodePointer), withElevation ? getEle(nodePointer) : "", withTurnCosts ? getTurnCostRef(nodePointer) : "-"); } if (nodeCount > printMax) { System.out.format(Locale.ROOT, " ... %d more nodes\n", nodeCount - printMax); } System.out.println("edges:"); - String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %12s \n"; + String formatEdges = "%12s | %12s | %12s | %12s | %12s | %12s | %15s \n"; System.out.format(Locale.ROOT, formatEdges, "#", "E_NODEA", "E_NODEB", "E_LINKA", "E_LINKB", "E_FLAGS", "E_DIST"); - IntsRef intsRef = new IntsRef(intsForFlags); + IntsRef edgeFlags = createEdgeFlags(); for (int i = 0; i < Math.min(edgeCount, printMax); ++i) { long edgePointer = toEdgePointer(i); - readFlags(edgePointer, intsRef); + readFlags(edgePointer, edgeFlags); System.out.format(Locale.ROOT, formatEdges, i, getNodeA(edgePointer), getNodeB(edgePointer), getLinkA(edgePointer), getLinkB(edgePointer), - intsRef, - getDist(edgePointer)); + edgeFlags, + getDist_mm(edgePointer)); } if (edgeCount > printMax) { System.out.printf(Locale.ROOT, " ... %d more edges", edgeCount - printMax); } } - private int distToInt(double distance) { - if (distance < 0) - throw new IllegalArgumentException("Distance cannot be negative: " + distance); - if (distance > MAX_DIST) { - distance = MAX_DIST; - } - int intDist = (int) Math.round(distance * INT_DIST_FACTOR); - assert intDist >= 0 : "distance out of range"; - return intDist; - } public String toDetailsString() { return "edges: " + nf(edgeCount) + "(" + edges.getCapacity() / Helper.MB + "MB), " diff --git a/core/src/main/java/com/graphhopper/storage/CHStorage.java b/core/src/main/java/com/graphhopper/storage/CHStorage.java index a54a178048e..d6bd4b7456e 100644 --- a/core/src/main/java/com/graphhopper/storage/CHStorage.java +++ b/core/src/main/java/com/graphhopper/storage/CHStorage.java @@ -42,13 +42,10 @@ */ public class CHStorage { private static final Logger LOGGER = LoggerFactory.getLogger(CHStorage.class); - // we store double weights as integers (rounded to three decimal digits) - private static final double WEIGHT_FACTOR = 1000; // the maximum integer value we can store private static final long MAX_STORED_INTEGER_WEIGHT = ((long) Integer.MAX_VALUE) << 1; // the maximum double weight we can store. if this is exceeded the shortcut will gain infinite weight, potentially yielding connection-not-found errors - private static final double MAX_WEIGHT = MAX_STORED_INTEGER_WEIGHT / WEIGHT_FACTOR; - private static final double MIN_WEIGHT = 1 / WEIGHT_FACTOR; + private static final double MAX_WEIGHT = MAX_STORED_INTEGER_WEIGHT; // shortcuts private final DataAccess shortcuts; @@ -63,25 +60,30 @@ public class CHStorage { private int nodeCount = -1; private boolean edgeBased; - // some shortcuts exceed the maximum storable weight, and we count them here - private int numShortcutsExceedingWeight; + // some shortcut weights are under the minimum storable weight, and we count them here + private int numShortcutsUnderMinWeight; + // some shortcut weights are over the maximum storable weight, and we count them here + private int numShortcutsOverMaxWeight; - // use this to report shortcuts with too small weights - private Consumer lowShortcutWeightConsumer; + // use this to report shortcuts with too large weights + private Consumer highWeightShortcutConsumer; + + private double minValidWeight = Double.POSITIVE_INFINITY; + private double maxValidWeight = Double.NEGATIVE_INFINITY; public static CHStorage fromGraph(BaseGraph baseGraph, CHConfig chConfig) { String name = chConfig.getName(); boolean edgeBased = chConfig.isEdgeBased(); if (!baseGraph.isFrozen()) throw new IllegalStateException("graph must be frozen before we can create ch graphs"); - CHStorage store = new CHStorage(baseGraph.getDirectory(), name, baseGraph.getSegmentSize(), edgeBased); - store.setLowShortcutWeightConsumer(s -> { - // we just log these to find mapping errors + CHStorage store = new CHStorage(baseGraph.getDirectory(), name, edgeBased); + store.setHighWeightShortcutConsumer(s -> { + // we just log these to find potential routing errors NodeAccess nodeAccess = baseGraph.getNodeAccess(); - LOGGER.warn("Setting weights smaller than " + s.minWeight + " is not allowed. " + + LOGGER.warn("Setting weights larger than " + s.maxWeight + " results in infinite-weight shortcuts. " + "You passed: " + s.weight + " for the shortcut " + - " nodeA (" + nodeAccess.getLat(s.nodeA) + "," + nodeAccess.getLon(s.nodeA) + - " nodeB " + nodeAccess.getLat(s.nodeB) + "," + nodeAccess.getLon(s.nodeB)); + " nodeA (" + nodeAccess.getLat(s.nodeA) + "," + nodeAccess.getLon(s.nodeA) + ")" + + " nodeB (" + nodeAccess.getLat(s.nodeB) + "," + nodeAccess.getLon(s.nodeB) + ")"); }); // we use a rather small value here. this might result in more allocations later, but they should // not matter that much. if we expect a too large value the shortcuts DataAccess will end up @@ -91,10 +93,10 @@ public static CHStorage fromGraph(BaseGraph baseGraph, CHConfig chConfig) { return store; } - public CHStorage(Directory dir, String name, int segmentSize, boolean edgeBased) { + public CHStorage(Directory dir, String name, boolean edgeBased) { this.edgeBased = edgeBased; - this.nodesCH = dir.create("nodes_ch_" + name, dir.getDefaultType("nodes_ch_" + name, true), segmentSize); - this.shortcuts = dir.create("shortcuts_" + name, dir.getDefaultType("shortcuts_" + name, true), segmentSize); + this.nodesCH = dir.create("nodes_ch_" + name, dir.getDefaultType("nodes_ch_" + name, true)); + this.shortcuts = dir.create("shortcuts_" + name, dir.getDefaultType("shortcuts_" + name, true)); // shortcuts are stored consecutively using this layout (the last two entries only exist for edge-based): // NODEA | NODEB | WEIGHT | SKIP_EDGE1 | SKIP_EDGE2 | S_ORIG_FIRST | S_ORIG_LAST S_NODEA = 0; @@ -113,11 +115,8 @@ public CHStorage(Directory dir, String name, int segmentSize, boolean edgeBased) nodeCHEntryBytes = N_LAST_SC + 4; } - /** - * Sets a callback called for shortcuts that are below the minimum weight. e.g. used to find/log mapping errors - */ - public void setLowShortcutWeightConsumer(Consumer lowWeightShortcutConsumer) { - this.lowShortcutWeightConsumer = lowWeightShortcutConsumer; + public void setHighWeightShortcutConsumer(Consumer highWeightShortcutConsumer) { + this.highWeightShortcutConsumer = highWeightShortcutConsumer; } /** @@ -150,8 +149,9 @@ public void flush() { shortcuts.setHeader(0, Constants.VERSION_SHORTCUT); shortcuts.setHeader(4, shortcutCount); shortcuts.setHeader(8, shortcutEntryBytes); - shortcuts.setHeader(12, numShortcutsExceedingWeight); - shortcuts.setHeader(16, edgeBased ? 1 : 0); + shortcuts.setHeader(12, numShortcutsUnderMinWeight); + shortcuts.setHeader(16, numShortcutsOverMaxWeight); + shortcuts.setHeader(20, edgeBased ? 1 : 0); shortcuts.flush(); } @@ -170,8 +170,9 @@ public boolean loadExisting() { GHUtility.checkDAVersion(shortcuts.getName(), Constants.VERSION_SHORTCUT, shortcutsVersion); shortcutCount = shortcuts.getHeader(4); shortcutEntryBytes = shortcuts.getHeader(8); - numShortcutsExceedingWeight = shortcuts.getHeader(12); - edgeBased = shortcuts.getHeader(16) == 1; + numShortcutsUnderMinWeight = shortcuts.getHeader(12); + numShortcutsOverMaxWeight = shortcuts.getHeader(16); + edgeBased = shortcuts.getHeader(20) == 1; return true; } @@ -202,8 +203,12 @@ public int shortcutEdgeBased(int nodeA, int nodeB, int accessFlags, double weigh private int shortcut(int nodeA, int nodeB, int accessFlags, double weight, int skip1, int skip2) { if (shortcutCount == Integer.MAX_VALUE) throw new IllegalStateException("Maximum shortcut count exceeded: " + shortcutCount); - if (lowShortcutWeightConsumer != null && weight < MIN_WEIGHT) - lowShortcutWeightConsumer.accept(new LowWeightShortcut(nodeA, nodeB, shortcutCount, weight, MIN_WEIGHT)); + if (highWeightShortcutConsumer != null && weight >= MAX_WEIGHT) + highWeightShortcutConsumer.accept(new HighWeightShortcut(nodeA, nodeB, shortcutCount, weight, MAX_WEIGHT)); + if (weight < MAX_WEIGHT) { + minValidWeight = Math.min(weight, minValidWeight); + maxValidWeight = Math.max(weight, maxValidWeight); + } long shortcutPointer = (long) shortcutCount * shortcutEntryBytes; shortcutCount++; shortcuts.ensureCapacity((long) shortcutCount * shortcutEntryBytes); @@ -385,8 +390,24 @@ public long getCapacity() { return nodesCH.getCapacity() + shortcuts.getCapacity(); } - public int getNumShortcutsExceedingWeight() { - return numShortcutsExceedingWeight; + public int getMB() { + return (int) ((shortcutEntryBytes * (long) shortcutCount + nodeCHEntryBytes * (long) nodeCount) / 1024 / 1024); + } + + public double getMinValidWeight() { + return minValidWeight; + } + + public double getMaxValidWeight() { + return maxValidWeight; + } + + public int getNumShortcutsUnderMinWeight() { + return numShortcutsUnderMinWeight; + } + + public int getNumShortcutsOverMaxWeight() { + return numShortcutsOverMaxWeight; } public String toDetailsString() { @@ -400,15 +421,16 @@ public boolean isClosed() { } private int weightFromDouble(double weight) { + if (Double.isInfinite(weight) || Double.isNaN(weight)) throw new IllegalArgumentException("weight should not be: " + weight); if (weight < 0) throw new IllegalArgumentException("weight cannot be negative but was " + weight); - if (weight < MIN_WEIGHT) - weight = MIN_WEIGHT; + if (weight % 1 != 0) + throw new IllegalArgumentException("weight must be an exact multiple of 1"); if (weight >= MAX_WEIGHT) { - numShortcutsExceedingWeight++; + numShortcutsOverMaxWeight++; return (int) MAX_STORED_INTEGER_WEIGHT; // negative } else - return (int) Math.round(weight * WEIGHT_FACTOR); + return (int) (long) weight; } private double weightToDouble(int intWeight) { @@ -416,27 +438,26 @@ private double weightToDouble(int intWeight) { // high bits with 1's which we remove via "& 0xFFFFFFFFL" to get the unsigned value. (The L is necessary or prepend 8 zeros.) long weightLong = (long) intWeight & 0xFFFFFFFFL; if (weightLong == MAX_STORED_INTEGER_WEIGHT) + // todo: maybe rather just cap to MAX_WEIGHT? return Double.POSITIVE_INFINITY; - double weight = weightLong / WEIGHT_FACTOR; - if (weight >= MAX_WEIGHT) - throw new IllegalArgumentException("too large shortcut weight " + weight + " should get infinity marker bits " - + MAX_STORED_INTEGER_WEIGHT); - return weight; + if (weightLong >= MAX_WEIGHT) + throw new IllegalArgumentException("too large shortcut weight: " + weightLong + ", limit: " + MAX_WEIGHT); + return weightLong; } - public static class LowWeightShortcut { + public static class HighWeightShortcut { int nodeA; int nodeB; int shortcut; double weight; - double minWeight; + double maxWeight; - public LowWeightShortcut(int nodeA, int nodeB, int shortcut, double weight, double minWeight) { + public HighWeightShortcut(int nodeA, int nodeB, int shortcut, double weight, double maxWeight) { this.nodeA = nodeA; this.nodeB = nodeB; this.shortcut = shortcut; this.weight = weight; - this.minWeight = minWeight; + this.maxWeight = maxWeight; } } } diff --git a/core/src/main/java/com/graphhopper/storage/CHStorageBuilder.java b/core/src/main/java/com/graphhopper/storage/CHStorageBuilder.java index c382ef517c0..cb1fc00dc40 100644 --- a/core/src/main/java/com/graphhopper/storage/CHStorageBuilder.java +++ b/core/src/main/java/com/graphhopper/storage/CHStorageBuilder.java @@ -62,8 +62,8 @@ public int addShortcutNodeBased(int a, int b, int accessFlags, double weight, in /** * @param origKeyFirst The first original edge key that is skipped by this shortcut *in the direction of the shortcut*. * This definition assumes that edge-based shortcuts are one-directional, and they are. - * For example for the following shortcut edge from x to y: x->u->v->w->y , - * which skips the shortcuts x->v and v->y the first original edge key would be the one of the edge x->u + * For example for the following shortcut edge from x to y: x->u->v->w->y, + * which skips the shortcuts x->v and v->y, the first original edge key would be the one of the edge x->u * @param origKeyLast like origKeyFirst, but the last orig edge key, i.e. the key of w->y in above example */ public int addShortcutEdgeBased(int a, int b, int accessFlags, double weight, int skippedEdge1, int skippedEdge2, diff --git a/core/src/main/java/com/graphhopper/storage/DAType.java b/core/src/main/java/com/graphhopper/storage/DAType.java index da75daf6b0d..f2d26a30f6d 100644 --- a/core/src/main/java/com/graphhopper/storage/DAType.java +++ b/core/src/main/java/com/graphhopper/storage/DAType.java @@ -80,10 +80,10 @@ else if (dataAccess.contains("MMAP")) type = DAType.MMAP; else if (dataAccess.contains("UNSAFE")) throw new IllegalArgumentException("UNSAFE option is no longer supported, see #1620"); - else if (dataAccess.contains("RAM_STORE")) - type = DAType.RAM_STORE; - else + else if (dataAccess.equals("RAM")) type = DAType.RAM; + else + type = DAType.RAM_STORE; return type; } diff --git a/core/src/main/java/com/graphhopper/storage/DataAccess.java b/core/src/main/java/com/graphhopper/storage/DataAccess.java index 5bb8158ac07..9b80abc3ecb 100644 --- a/core/src/main/java/com/graphhopper/storage/DataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/DataAccess.java @@ -119,6 +119,13 @@ public interface DataAccess extends Closeable { */ boolean ensureCapacity(long bytes); + /** + * Reduces the capacity to the specified number of bytes (rounded up to the next segment + * boundary). The specified capacity must be less than or equal to the current capacity. + * A capacity of zero releases all segments. + */ + void trimTo(long capacity); + /** * @return the size of one segment in bytes */ diff --git a/core/src/main/java/com/graphhopper/storage/GHDirectory.java b/core/src/main/java/com/graphhopper/storage/GHDirectory.java index be26c4e94b0..d92875456d6 100644 --- a/core/src/main/java/com/graphhopper/storage/GHDirectory.java +++ b/core/src/main/java/com/graphhopper/storage/GHDirectory.java @@ -36,8 +36,13 @@ public class GHDirectory implements Directory { private final Map defaultTypes = new LinkedHashMap<>(); private final Map mmapPreloads = new LinkedHashMap<>(); private final Map map = Collections.synchronizedMap(new HashMap<>()); + private final int defaultSegmentSize; public GHDirectory(String _location, DAType defaultType) { + this(_location, defaultType, AbstractDataAccess.SEGMENT_SIZE_DEFAULT); + } + + public GHDirectory(String _location, DAType defaultType, int defaultSegmentSize) { this.typeFallback = defaultType; if (isEmpty(_location)) _location = new File("").getAbsolutePath(); @@ -49,6 +54,8 @@ public GHDirectory(String _location, DAType defaultType) { File dir = new File(location); if (dir.exists() && !dir.isDirectory()) throw new RuntimeException("file '" + dir + "' exists but is not a directory"); + + this.defaultSegmentSize = defaultSegmentSize; } /** @@ -113,7 +120,7 @@ private DAType getDefault(String name, DAType typeFallback) { @Override public DataAccess create(String name, DAType type) { - return create(name, type, -1); + return create(name, type, defaultSegmentSize); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java index ace800d23d7..1db9a6eeda3 100644 --- a/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java +++ b/core/src/main/java/com/graphhopper/storage/GHNodeAccess.java @@ -17,6 +17,8 @@ */ package com.graphhopper.storage; +import com.graphhopper.util.Helper; + /** * @author Peter Karich */ @@ -41,7 +43,9 @@ public final void setNode(int nodeId, double lat, double lon, double ele) { if (store.withElevation()) { // meter precision is sufficient for now store.setEle(store.toNodePointer(nodeId), ele); - store.bounds.update(lat, lon, ele); + // Helper.ELE_UNKNOWN is a marker value for deferred elevation, don't let it poison bounds + if (ele != Helper.ELE_UNKNOWN) + store.bounds.update(lat, lon, ele); } else { store.bounds.update(lat, lon); } diff --git a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java index 666309c08ca..40d00626ab3 100644 --- a/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/MMapDataAccess.java @@ -52,7 +52,7 @@ public final class MMapDataAccess extends AbstractDataAccess { private RandomAccessFile raFile; private final List segments = new ArrayList<>(); - MMapDataAccess(String name, String location, boolean allowWrites, int segmentSize) { + public MMapDataAccess(String name, String location, boolean allowWrites, int segmentSize) { super(name, location, segmentSize); this.allowWrites = allowWrites; } @@ -242,6 +242,28 @@ public void load(int percentage) { } } + @Override + public void trimTo(long capacity) { + if (capacity < 0) + throw new IllegalArgumentException("capacity must not be negative"); + if (capacity > getCapacity()) + throw new IllegalArgumentException("capacity cannot be larger than the current capacity: " + capacity + " > " + getCapacity()); + + int newSegmentCount = (int) (capacity / segmentSizeInBytes); + if (capacity % segmentSizeInBytes != 0) + newSegmentCount++; + + if (newSegmentCount < segments.size()) { + clean(newSegmentCount, segments.size()); + segments.subList(newSegmentCount, segments.size()).clear(); + try { + raFile.setLength(HEADER_OFFSET + getCapacity()); + } catch (IOException ex) { + throw new RuntimeException("Failed to truncate file " + getFullName(), ex); + } + } + } + @Override public void close() { super.close(); @@ -254,20 +276,43 @@ public void close() { public void setInt(long bytePos, int value) { int bufferIndex = (int) (bytePos >> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - ByteBuffer byteBuffer = segments.get(bufferIndex); - byteBuffer.putInt(index, value); + ByteBuffer b1 = segments.get(bufferIndex); + if (index + 3 >= segmentSizeInBytes) { + // seldom and special case if int has to be written into two separate segments + ByteBuffer b2 = segments.get(bufferIndex + 1); + if (index + 1 >= segmentSizeInBytes) { + b2.putShort(1, (short) (value >>> 16)); + b2.put(0, (byte) (value >>> 8)); + b1.put(index, (byte) value); + } else if (index + 2 >= segmentSizeInBytes) { + b2.putShort(0, (short) (value >>> 16)); + b1.putShort(index, (short) value); + } else { + // index + 3 >= segmentSizeInBytes + b2.put(0, (byte) (value >>> 24)); + b1.putShort(index + 1, (short) (value >>> 8)); + b1.put(index, (byte) value); + } + } else { + b1.putInt(index, value); + } } @Override public int getInt(long bytePos) { int bufferIndex = (int) (bytePos >> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - ByteBuffer byteBuffer = segments.get(bufferIndex); - return byteBuffer.getInt(index); + ByteBuffer b1 = segments.get(bufferIndex); + if (index + 3 >= segmentSizeInBytes) { + ByteBuffer b2 = segments.get(bufferIndex + 1); + if (index + 1 >= segmentSizeInBytes) + return (b2.getShort(1) & 0xFFFF) << 16 | (b2.get(0) & 0xFF) << 8 | (b1.get(index) & 0xFF); + if (index + 2 >= segmentSizeInBytes) + return (b2.getShort(0) & 0xFFFF) << 16 | (b1.getShort(index) & 0xFFFF); + // index + 3 >= segmentSizeInBytes + return (b2.get(0) & 0xFF) << 24 | (b1.getShort(index + 1) & 0xFFFF) << 8 | (b1.get(index) & 0xFF); + } + return b1.getInt(index); } @Override @@ -275,9 +320,9 @@ public void setShort(long bytePos, short value) { int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); ByteBuffer byteBuffer = segments.get(bufferIndex); - if (index + 2 > segmentSizeInBytes) { + if (index + 1 >= segmentSizeInBytes) { ByteBuffer byteBufferNext = segments.get(bufferIndex + 1); - // special case if short has to be written into two separate segments + // seldom and special case if short has to be written into two separate segments byteBuffer.put(index, (byte) value); byteBufferNext.put(0, (byte) (value >>> 8)); } else { @@ -290,7 +335,7 @@ public short getShort(long bytePos) { int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); ByteBuffer byteBuffer = segments.get(bufferIndex); - if (index + 2 > segmentSizeInBytes) { + if (index + 1 >= segmentSizeInBytes) { ByteBuffer byteBufferNext = segments.get(bufferIndex + 1); return (short) ((byteBufferNext.get(0) & 0xFF) << 8 | byteBuffer.get(index) & 0xFF); } @@ -352,11 +397,7 @@ public byte getByte(long bytePos) { @Override public long getCapacity() { - long cap = 0; - for (ByteBuffer bb : segments) { - cap += bb.capacity(); - } - return cap; + return (long) getSegments() * segmentSizeInBytes; } @Override diff --git a/core/src/main/java/com/graphhopper/storage/MMapDirectory.java b/core/src/main/java/com/graphhopper/storage/MMapDirectory.java deleted file mode 100644 index 56589053027..00000000000 --- a/core/src/main/java/com/graphhopper/storage/MMapDirectory.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.storage; - -/** - * Manages memory mapped DataAccess objects. - *

    - * - * @author Peter Karich - * @see MMapDataAccess - */ -public class MMapDirectory extends GHDirectory { - // reserve the empty constructor for direct mapped memory - private MMapDirectory() { - this(""); - throw new IllegalStateException("reserved for direct mapped memory"); - } - - public MMapDirectory(String _location) { - super(_location, DAType.MMAP); - } -} diff --git a/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java b/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java index 7fb11d1395c..42c83ae1657 100644 --- a/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/RAMDataAccess.java @@ -20,6 +20,9 @@ import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; +import java.nio.ByteOrder; import java.util.Arrays; /** @@ -32,8 +35,11 @@ public class RAMDataAccess extends AbstractDataAccess { private byte[][] segments = new byte[0][]; private boolean store; + // we could also use UNSAFE but it is not really faster (see #3005) + private static final VarHandle INT = MethodHandles.byteArrayViewVarHandle(int[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior(); + private static final VarHandle SHORT = MethodHandles.byteArrayViewVarHandle(short[].class, ByteOrder.LITTLE_ENDIAN).withInvokeExactBehavior(); - RAMDataAccess(String name, String location, boolean store, int segmentSize) { + public RAMDataAccess(String name, String location, boolean store, int segmentSize) { super(name, location, segmentSize); this.store = store; } @@ -149,6 +155,7 @@ public void flush() { byte[] area = segments[s]; raFile.write(area); } + raFile.setLength(HEADER_OFFSET + len); } } catch (Exception ex) { throw new RuntimeException("Couldn't store bytes to " + toString(), ex); @@ -160,9 +167,23 @@ public final void setInt(long bytePos, int value) { assert segmentSizePower > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - bitUtil.fromInt(segments[bufferIndex], value, index); + if (index + 3 >= segmentSizeInBytes) { + // seldom and special case if int has to be written into two separate segments + byte[] b1 = segments[bufferIndex], b2 = segments[bufferIndex + 1]; + if (index + 1 >= segmentSizeInBytes) { + bitUtil.fromUInt3(b2, value >>> 8, 0); + b1[index] = (byte) value; + } else if (index + 2 >= segmentSizeInBytes) { + bitUtil.fromShort(b2, (short) (value >>> 16), 0); + bitUtil.fromShort(b1, (short) value, index); + } else { + // index + 3 >= segmentSizeInBytes + b2[0] = (byte) (value >>> 24); + bitUtil.fromUInt3(b1, value, index); + } + } else { + INT.set(segments[bufferIndex], index, value); + } } @Override @@ -170,9 +191,16 @@ public final int getInt(long bytePos) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 4 > segmentSizeInBytes) - throw new IllegalStateException("Padding required. Currently an int cannot be distributed over two segments. " + bytePos); - return bitUtil.toInt(segments[bufferIndex], index); + if (index + 3 >= segmentSizeInBytes) { + byte[] b1 = segments[bufferIndex], b2 = segments[bufferIndex + 1]; + if (index + 1 >= segmentSizeInBytes) + return (b2[2] & 0xFF) << 24 | (b2[1] & 0xFF) << 16 | (b2[0] & 0xFF) << 8 | (b1[index] & 0xFF); + if (index + 2 >= segmentSizeInBytes) + return (b2[1] & 0xFF) << 24 | (b2[0] & 0xFF) << 16 | (b1[index + 1] & 0xFF) << 8 | (b1[index] & 0xFF); + // index + 3 >= segmentSizeInBytes + return (b2[0] & 0xFF) << 24 | (b1[index + 2] & 0xFF) << 16 | (b1[index + 1] & 0xFF) << 8 | (b1[index] & 0xFF); + } + return (int) INT.get(segments[bufferIndex], index); } @Override @@ -180,12 +208,12 @@ public final void setShort(long bytePos, short value) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 2 > segmentSizeInBytes) { - // special case if short has to be written into two separate segments + if (index + 1 >= segmentSizeInBytes) { + // seldom and special case if short has to be written into two separate segments segments[bufferIndex][index] = (byte) (value); segments[bufferIndex + 1][0] = (byte) (value >>> 8); } else { - bitUtil.fromShort(segments[bufferIndex], value, index); + SHORT.set(segments[bufferIndex], index, value); } } @@ -194,10 +222,10 @@ public final short getShort(long bytePos) { assert segments.length > 0 : "call create or loadExisting before usage!"; int bufferIndex = (int) (bytePos >>> segmentSizePower); int index = (int) (bytePos & indexDivisor); - if (index + 2 > segmentSizeInBytes) + if (index + 1 >= segmentSizeInBytes) return (short) ((segments[bufferIndex + 1][0] & 0xFF) << 8 | (segments[bufferIndex][index] & 0xFF)); - else - return bitUtil.toShort(segments[bufferIndex], index); + + return (short) SHORT.get(segments[bufferIndex], index); } @Override @@ -252,6 +280,21 @@ public final byte getByte(long bytePos) { return segments[bufferIndex][index]; } + @Override + public void trimTo(long capacity) { + if (capacity < 0) + throw new IllegalArgumentException("capacity must not be negative"); + if (capacity > getCapacity()) + throw new IllegalArgumentException("capacity cannot be larger than the current capacity: " + capacity + " > " + getCapacity()); + + int newSegmentCount = (int) (capacity / segmentSizeInBytes); + if (capacity % segmentSizeInBytes != 0) + newSegmentCount++; + + if (newSegmentCount < segments.length) + segments = Arrays.copyOf(segments, newSegmentCount); + } + @Override public void close() { super.close(); diff --git a/core/src/main/java/com/graphhopper/storage/RAMDirectory.java b/core/src/main/java/com/graphhopper/storage/RAMDirectory.java deleted file mode 100644 index 98638a7c1ee..00000000000 --- a/core/src/main/java/com/graphhopper/storage/RAMDirectory.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.storage; - -/** - * Manages in-memory DataAccess objects. - *

    - * - * @author Peter Karich - * @see RAMDataAccess - * @see RAMIntDataAccess - */ -public class RAMDirectory extends GHDirectory { - public RAMDirectory() { - this("", false); - } - - public RAMDirectory(String location) { - this(location, false); - } - - /** - * @param store true if you want that the RAMDirectory can be loaded or saved on demand, false - * if it should be entirely in RAM - */ - public RAMDirectory(String _location, boolean store) { - super(_location, store ? DAType.RAM_STORE : DAType.RAM); - } -} diff --git a/core/src/main/java/com/graphhopper/storage/RAMIntDataAccess.java b/core/src/main/java/com/graphhopper/storage/RAMIntDataAccess.java index c5cc0ccb39d..10b443c85f0 100644 --- a/core/src/main/java/com/graphhopper/storage/RAMIntDataAccess.java +++ b/core/src/main/java/com/graphhopper/storage/RAMIntDataAccess.java @@ -29,13 +29,13 @@ * * @author Peter Karich */ -class RAMIntDataAccess extends AbstractDataAccess { +public class RAMIntDataAccess extends AbstractDataAccess { private int[][] segments = new int[0][]; private boolean closed = false; private boolean store; private int segmentSizeIntsPower; - RAMIntDataAccess(String name, String location, boolean store, int segmentSize) { + public RAMIntDataAccess(String name, String location, boolean store, int segmentSize) { super(name, location, segmentSize); this.store = store; } @@ -157,6 +157,7 @@ public void flush() { } raFile.write(byteArea); } + raFile.setLength(HEADER_OFFSET + len); } } catch (Exception ex) { throw new RuntimeException("Couldn't store integers to " + toString(), ex); @@ -232,6 +233,21 @@ public void setByte(long bytePos, byte value) { throw new UnsupportedOperationException(toString() + " does not support byte based acccess. Use RAMDataAccess instead"); } + @Override + public void trimTo(long capacity) { + if (capacity < 0) + throw new IllegalArgumentException("capacity must not be negative"); + if (capacity > getCapacity()) + throw new IllegalArgumentException("capacity cannot be larger than the current capacity: " + capacity + " > " + getCapacity()); + + int newSegmentCount = (int) (capacity / segmentSizeInBytes); + if (capacity % segmentSizeInBytes != 0) + newSegmentCount++; + + if (newSegmentCount < segments.length) + segments = Arrays.copyOf(segments, newSegmentCount); + } + @Override public void close() { super.close(); @@ -257,11 +273,6 @@ DataAccess setSegmentSize(int bytes) { return this; } - boolean releaseSegment(int segNumber) { - segments[segNumber] = null; - return true; - } - @Override protected boolean isIntBased() { return true; diff --git a/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java b/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java index bacf54cae75..678b17571b3 100644 --- a/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java +++ b/core/src/main/java/com/graphhopper/storage/RoutingCHGraphImpl.java @@ -107,4 +107,9 @@ public void close() { if (!baseGraph.isClosed()) baseGraph.close(); chStorage.close(); } + + public CHStorage getCHStorage() { + return chStorage; + } + } diff --git a/core/src/main/java/com/graphhopper/storage/StorableProperties.java b/core/src/main/java/com/graphhopper/storage/StorableProperties.java index 73519d33a94..32fd2c975fa 100644 --- a/core/src/main/java/com/graphhopper/storage/StorableProperties.java +++ b/core/src/main/java/com/graphhopper/storage/StorableProperties.java @@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory; import java.io.*; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -38,8 +39,10 @@ public class StorableProperties { private final Map map = new LinkedHashMap<>(); private final DataAccess da; + private final Directory dir; public StorableProperties(Directory dir) { + this.dir = dir; // reduce size int segmentSize = 1 << 15; this.da = dir.create("properties", segmentSize); @@ -49,9 +52,18 @@ public synchronized boolean loadExisting() { if (!da.loadExisting()) return false; + if (da.getCapacity() > Integer.MAX_VALUE) { + throw new IllegalStateException("Properties file is too large: " + da.getCapacity()); + } int len = (int) da.getCapacity(); byte[] bytes = new byte[len]; - da.getBytes(0, bytes, len); + int segmentSize = da.getSegmentSize(); + for (int bytePos = 0; bytePos < len; bytePos += segmentSize) { + int partLen = Math.min(bytes.length - bytePos, segmentSize); + byte[] part = new byte[partLen]; + da.getBytes(bytePos, part, part.length); + System.arraycopy(part, 0, bytes, bytePos, partLen); + } try { loadProperties(map, new StringReader(new String(bytes, UTF_CS))); return true; @@ -61,15 +73,23 @@ public synchronized boolean loadExisting() { } public synchronized void flush() { - try { - StringWriter sw = new StringWriter(); - saveProperties(map, sw); - // TODO at the moment the size is limited to da.segmentSize() ! - byte[] bytes = sw.toString().getBytes(UTF_CS); - da.setBytes(0, bytes, bytes.length); - da.flush(); - } catch (IOException ex) { - throw new RuntimeException(ex); + String props = saveProperties(map); + byte[] bytes = props.getBytes(UTF_CS); + da.ensureCapacity(bytes.length); + int segmentSize = da.getSegmentSize(); + for (int bytePos = 0; bytePos < bytes.length; bytePos += segmentSize) { + int partLen = Math.min(bytes.length - bytePos, segmentSize); + byte[] part = Arrays.copyOfRange(bytes, bytePos, bytePos + partLen); + da.setBytes(bytePos, part, part.length); + } + da.flush(); + // todo: would not be needed if the properties file used a format that is compatible with common text tools + if (dir.getDefaultType().isStoring()) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(dir.getLocation() + "/properties.txt"))) { + writer.write(props); + } catch (IOException e) { + throw new RuntimeException(e); + } } } @@ -122,6 +142,10 @@ public synchronized StorableProperties create(long size) { return this; } + public Directory getDirectory() { + return dir; + } + public synchronized long getCapacity() { return da.getCapacity(); } @@ -139,10 +163,20 @@ public synchronized String toString() { return da.toString(); } + static String saveProperties(Map map) { + StringBuilder builder = new StringBuilder(); + for (Map.Entry e : map.entrySet()) { + builder.append(e.getKey()); + builder.append('='); + builder.append(e.getValue()); + builder.append('\n'); + } + return builder.toString(); + } + static void loadProperties(Map map, Reader tmpReader) throws IOException { - BufferedReader reader = new BufferedReader(tmpReader); - String line; - try { + try (BufferedReader reader = new BufferedReader(tmpReader)) { + String line; while ((line = reader.readLine()) != null) { if (line.startsWith("//") || line.startsWith("#")) { continue; @@ -162,8 +196,6 @@ static void loadProperties(Map map, Reader tmpReader) throws IOE String value = line.substring(index + 1); map.put(field.trim(), value.trim()); } - } finally { - reader.close(); } } } diff --git a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java index 8d694bcf3fd..5be9b03ab5a 100644 --- a/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java +++ b/core/src/main/java/com/graphhopper/storage/TurnCostStorage.java @@ -17,6 +17,7 @@ */ package com.graphhopper.storage; +import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.EdgeIntAccess; @@ -25,6 +26,8 @@ import com.graphhopper.util.EdgeIterator; import com.graphhopper.util.GHUtility; +import java.util.function.IntUnaryOperator; + /** * A key/value store, where the unique keys are triples (fromEdge, viaNode, toEdge) and the values * are integers that can be used to store encoded values. @@ -44,6 +47,7 @@ public class TurnCostStorage { private final BaseGraph baseGraph; private final DataAccess turnCosts; + private final EdgeIntAccess edgeIntAccess = createEdgeIntAccess(); private int turnCostsCount; public TurnCostStorage(BaseGraph baseGraph, DataAccess turnCosts) { @@ -84,67 +88,72 @@ public boolean loadExisting() { } public void set(BooleanEncodedValue bev, int fromEdge, int viaNode, int toEdge, boolean value) { - long pointer = findOrCreateTurnCostEntry(fromEdge, viaNode, toEdge); - if (pointer < 0) - throw new IllegalStateException("Invalid pointer: " + pointer + " at (" + fromEdge + ", " + viaNode + ", " + toEdge + ")"); - bev.setBool(false, -1, createIntAccess(pointer), value); + int index = findOrCreateTurnCostEntry(fromEdge, viaNode, toEdge); + if (index < 0) + throw new IllegalStateException("Invalid index: " + index + " at (" + fromEdge + ", " + viaNode + ", " + toEdge + ")"); + bev.setBool(false, index, edgeIntAccess, value); } /** * Sets the turn cost at the viaNode when going from "fromEdge" to "toEdge" */ public void set(DecimalEncodedValue turnCostEnc, int fromEdge, int viaNode, int toEdge, double cost) { - long pointer = findOrCreateTurnCostEntry(fromEdge, viaNode, toEdge); - if (pointer < 0) - throw new IllegalStateException("Invalid pointer: " + pointer + " at (" + fromEdge + ", " + viaNode + ", " + toEdge + ")"); - turnCostEnc.setDecimal(false, -1, createIntAccess(pointer), cost); + int index = findOrCreateTurnCostEntry(fromEdge, viaNode, toEdge); + if (index < 0) + throw new IllegalStateException("Invalid index: " + index + " at (" + fromEdge + ", " + viaNode + ", " + toEdge + ")"); + turnCostEnc.setDecimal(false, index, edgeIntAccess, cost); } - private long findOrCreateTurnCostEntry(int fromEdge, int viaNode, int toEdge) { - long pointer = findPointer(fromEdge, viaNode, toEdge); - if (pointer < 0) { + private int findOrCreateTurnCostEntry(int fromEdge, int viaNode, int toEdge) { + int index = findIndex(fromEdge, viaNode, toEdge); + if (index < 0) { // create a new entry - ensureTurnCostIndex(turnCostsCount); + index = turnCostsCount; + ensureTurnCostIndex(index); int prevIndex = baseGraph.getNodeAccess().getTurnCostIndex(viaNode); - baseGraph.getNodeAccess().setTurnCostIndex(viaNode, turnCostsCount); - pointer = (long) turnCostsCount * BYTES_PER_ENTRY; + baseGraph.getNodeAccess().setTurnCostIndex(viaNode, index); + long pointer = toPointer(index); turnCosts.setInt(pointer + TC_FROM, fromEdge); turnCosts.setInt(pointer + TC_TO, toEdge); turnCosts.setInt(pointer + TC_NEXT, prevIndex); turnCostsCount++; } - return pointer; + return index; } public double get(DecimalEncodedValue dev, int fromEdge, int viaNode, int toEdge) { - return dev.getDecimal(false, -1, createIntAccess(findPointer(fromEdge, viaNode, toEdge))); + int index = findIndex(fromEdge, viaNode, toEdge); + // todo: should we rather pass 0 to the encoded value so it can decide what this means? + if (index < 0) return 0; + return dev.getDecimal(false, index, edgeIntAccess); } public boolean get(BooleanEncodedValue bev, int fromEdge, int viaNode, int toEdge) { - return bev.getBool(false, -1, createIntAccess(findPointer(fromEdge, viaNode, toEdge))); + int index = findIndex(fromEdge, viaNode, toEdge); + // todo: should we rather pass 0 to the encoded value so it can decide what this means? + if (index < 0) return false; + return bev.getBool(false, index, edgeIntAccess); } - private EdgeIntAccess createIntAccess(long pointer) { + private EdgeIntAccess createEdgeIntAccess() { return new EdgeIntAccess() { @Override - public int getInt(int edgeId, int index) { - return pointer < 0 ? 0 : turnCosts.getInt(pointer + TC_FLAGS); + public int getInt(int entryIndex, int index) { + return turnCosts.getInt(toPointer(entryIndex) + TC_FLAGS); } @Override - public void setInt(int edgeId, int index, int value) { - if (pointer < 0) - throw new IllegalStateException("pointer must not be negative: " + pointer); - turnCosts.setInt(pointer + TC_FLAGS, value); + public void setInt(int entryIndex, int index, int value) { + turnCosts.setInt(toPointer(entryIndex) + TC_FLAGS, value); } }; } - private void ensureTurnCostIndex(int nodeIndex) { - turnCosts.ensureCapacity(((long) nodeIndex + 4) * BYTES_PER_ENTRY); + private void ensureTurnCostIndex(int index) { + turnCosts.ensureCapacity(toPointer(index + 1)); } - private long findPointer(int fromEdge, int viaNode, int toEdge) { + private int findIndex(int fromEdge, int viaNode, int toEdge) { if (!EdgeIterator.Edge.isValid(fromEdge) || !EdgeIterator.Edge.isValid(toEdge)) throw new IllegalArgumentException("from and to edge cannot be NO_EDGE"); if (viaNode < 0) @@ -154,14 +163,41 @@ private long findPointer(int fromEdge, int viaNode, int toEdge) { int index = baseGraph.getNodeAccess().getTurnCostIndex(viaNode); for (int i = 0; i < maxEntries; ++i) { if (index == NO_TURN_ENTRY) return -1; - long pointer = (long) index * BYTES_PER_ENTRY; + long pointer = toPointer(index); if (fromEdge == turnCosts.getInt(pointer + TC_FROM) && toEdge == turnCosts.getInt(pointer + TC_TO)) - return pointer; + return index; index = turnCosts.getInt(pointer + TC_NEXT); } throw new IllegalStateException("Turn cost list for node: " + viaNode + " is longer than expected, max: " + maxEntries); } + public void sortEdges(IntUnaryOperator getNewEdgeForOldEdge) { + for (int i = 0; i < turnCostsCount; i++) { + long pointer = toPointer(i); + turnCosts.setInt(pointer + TC_FROM, getNewEdgeForOldEdge.applyAsInt(turnCosts.getInt(pointer + TC_FROM))); + turnCosts.setInt(pointer + TC_TO, getNewEdgeForOldEdge.applyAsInt(turnCosts.getInt(pointer + TC_TO))); + } + } + + private long toPointer(int index) { + return (long) index * BYTES_PER_ENTRY; + } + + public int getTurnCostsCount() { + return turnCostsCount; + } + + public int getTurnCostsCount(int node) { + int index = baseGraph.getNodeAccess().getTurnCostIndex(node); + int count = 0; + while (index != NO_TURN_ENTRY) { + long pointer = toPointer(index); + index = turnCosts.getInt(pointer + TC_NEXT); + count++; + } + return count; + } + public boolean isClosed() { return turnCosts.isClosed(); } @@ -183,6 +219,44 @@ public Iterator getAllTurnCosts() { return new Itr(); } + public void sortNodes() { + IntArrayList tcFroms = new IntArrayList(); + IntArrayList tcTos = new IntArrayList(); + IntArrayList tcFlags = new IntArrayList(); + IntArrayList tcNexts = new IntArrayList(); + for (int i = 0; i < turnCostsCount; i++) { + long pointer = toPointer(i); + tcFroms.add(turnCosts.getInt(pointer + TC_FROM)); + tcTos.add(turnCosts.getInt(pointer + TC_TO)); + tcFlags.add(turnCosts.getInt(pointer + TC_FLAGS)); + tcNexts.add(turnCosts.getInt(pointer + TC_NEXT)); + } + long turnCostsCountBefore = turnCostsCount; + turnCostsCount = 0; + for (int node = 0; node < baseGraph.getNodes(); node++) { + boolean firstForNode = true; + int turnCostIndex = baseGraph.getNodeAccess().getTurnCostIndex(node); + while (turnCostIndex != NO_TURN_ENTRY) { + if (firstForNode) { + baseGraph.getNodeAccess().setTurnCostIndex(node, turnCostsCount); + } else { + long prevPointer = toPointer(turnCostsCount - 1); + turnCosts.setInt(prevPointer + TC_NEXT, turnCostsCount); + } + long pointer = toPointer(turnCostsCount); + turnCosts.setInt(pointer + TC_FROM, tcFroms.get(turnCostIndex)); + turnCosts.setInt(pointer + TC_TO, tcTos.get(turnCostIndex)); + turnCosts.setInt(pointer + TC_FLAGS, tcFlags.get(turnCostIndex)); + turnCosts.setInt(pointer + TC_NEXT, NO_TURN_ENTRY); + turnCostsCount++; + firstForNode = false; + turnCostIndex = tcNexts.get(turnCostIndex); + } + } + if (turnCostsCountBefore != turnCostsCount) + throw new IllegalStateException("Turn cost count changed unexpectedly: " + turnCostsCountBefore + " -> " + turnCostsCount); + } + public interface Iterator { int getFromEdge(); @@ -204,7 +278,7 @@ private class Itr implements Iterator { private final EdgeIntAccess edgeIntAccess = new IntsRefEdgeIntAccess(intsRef); private long turnCostPtr() { - return (long) turnCostIndex * BYTES_PER_ENTRY; + return toPointer(turnCostIndex); } @Override diff --git a/core/src/main/java/com/graphhopper/storage/index/Snap.java b/core/src/main/java/com/graphhopper/storage/index/Snap.java index 7ef64be1a6a..ad4c1e9b209 100644 --- a/core/src/main/java/com/graphhopper/storage/index/Snap.java +++ b/core/src/main/java/com/graphhopper/storage/index/Snap.java @@ -160,12 +160,35 @@ public void calcSnappedPoint(DistanceCalc distCalc) { if (considerEqual(crossingPoint.lat, crossingPoint.lon, tmpLat, tmpLon)) { snappedPosition = wayIndex == 0 ? Position.TOWER : Position.PILLAR; snappedPoint = new GHPoint3D(tmpLat, tmpLon, tmpEle); + closestNode = wayIndex == 0 ? closestEdge.getBaseNode() : closestNode; } else if (considerEqual(crossingPoint.lat, crossingPoint.lon, adjLat, adjLon)) { wayIndex++; snappedPosition = wayIndex == fullPL.size() - 1 ? Position.TOWER : Position.PILLAR; snappedPoint = new GHPoint3D(adjLat, adjLon, adjEle); + closestNode = wayIndex == fullPL.size() - 1 ? closestEdge.getAdjNode() : closestNode; } else { - snappedPoint = new GHPoint3D(crossingPoint.lat, crossingPoint.lon, (tmpEle + adjEle) / 2); + double delta_lat = adjLat - tmpLat; + double delta_lon = adjLon - tmpLon; + double elevation; + if (delta_lon == 0 && delta_lat == 0) + elevation = tmpEle; + else { + // We can calculate the fraction t directly from the crossing point, without + // calculating the distance to the previous point: + // calcCrossingPointToEdge computes the point on a straight line between A and B + // that is closest to the query point (the "crossing point C") in the shrunk space + // where x_lat' = x_lat and x_lon' = x_lon*s: + // c_lat' = c_lat = a_lat + t*(b_lat - a_lat) + // c_lon' = c_lon*s = a_lon*s + t*(b_lon*s - a_lon*s) + // and returns (c_lat', c_lon'/s), so: + // c_lon = a_lon + t*(b_lon - a_lon) + // => C lies also on a straight line between A and B in lat/lon coordinates + double t = Math.abs(delta_lat) > Math.abs(delta_lon) + ? (crossingPoint.lat - tmpLat) / delta_lat + : (crossingPoint.lon - tmpLon) / delta_lon; + elevation = tmpEle + t * (adjEle - tmpEle); + } + snappedPoint = new GHPoint3D(crossingPoint.lat, crossingPoint.lon, elevation); } } else { // outside of edge segment [wayIndex, wayIndex+1] should not happen for EDGE diff --git a/core/src/main/java/com/graphhopper/util/AngleCalc.java b/core/src/main/java/com/graphhopper/util/AngleCalc.java index 4a0ab5939fe..63b9a743df2 100644 --- a/core/src/main/java/com/graphhopper/util/AngleCalc.java +++ b/core/src/main/java/com/graphhopper/util/AngleCalc.java @@ -59,10 +59,9 @@ public double calcOrientation(double lat1, double lon1, double lat2, double lon2 /** * Return orientation of line relative to east. - *

    * * @param exact If false the atan gets calculated faster, but it might contain small errors - * @return Orientation in interval -pi to +pi where 0 is east + * @return Orientation in interval -pi to +pi where 0 is east and the "bottom" arc is negative */ public double calcOrientation(double lat1, double lon1, double lat2, double lon2, boolean exact) { double shrinkFactor = cos(toRadians((lat1 + lat2) / 2)); diff --git a/core/src/main/java/com/graphhopper/util/ArrayUtil.java b/core/src/main/java/com/graphhopper/util/ArrayUtil.java index 6a0168cdf75..a6748092acd 100644 --- a/core/src/main/java/com/graphhopper/util/ArrayUtil.java +++ b/core/src/main/java/com/graphhopper/util/ArrayUtil.java @@ -21,6 +21,7 @@ import com.carrotsearch.hppc.BitSet; import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.IntIndexedContainer; +import com.carrotsearch.hppc.LongArrayList; import com.carrotsearch.hppc.cursors.IntCursor; import com.carrotsearch.hppc.sorting.IndirectComparator; import com.carrotsearch.hppc.sorting.IndirectSort; @@ -110,6 +111,18 @@ public static IntArrayList reverse(IntArrayList list) { return list; } + public static LongArrayList reverse(LongArrayList list) { + final long[] buffer = list.buffer; + long tmp; + for (int start = 0, end = list.size() - 1; start < end; start++, end--) { + // swap the values + tmp = buffer[start]; + buffer[start] = buffer[end]; + buffer[end] = tmp; + } + return list; + } + /** * Shuffles the elements of the given list in place and returns it */ @@ -130,6 +143,10 @@ public static IntArrayList shuffle(IntArrayList list, Random random) { * @return the size of the new range that contains no duplicates (smaller or equal to end). */ public static int removeConsecutiveDuplicates(int[] arr, int end) { + if (end < 0) + throw new IllegalArgumentException("end less than 0"); + if (end == 0) + return 0; int curr = 0; for (int i = 1; i < end; ++i) { if (arr[i] != arr[curr]) @@ -224,6 +241,13 @@ public static IntArrayList invert(IntArrayList list) { return result; } + public static IntArrayList subList(IntArrayList list, int fromIndex, int toIndex) { + IntArrayList result = new IntArrayList(toIndex - fromIndex); + for (int i = fromIndex; i < toIndex; i++) + result.add(list.get(i)); + return result; + } + /** * @param a sorted array * @param b sorted array @@ -252,4 +276,17 @@ public static int[] merge(int[] a, int[] b) { int sizeWithoutDuplicates = removeConsecutiveDuplicates(result, size); return Arrays.copyOf(result, sizeWithoutDuplicates); } + + public static int getLast(IntArrayList list) { + if (list.isEmpty()) + throw new IllegalArgumentException("Cannot get last element of an empty list"); + return list.get(list.size() - 1); + } + + public static int getLast(int[] array) { + if (array.length == 0) + throw new IllegalArgumentException("Cannot get last element of an empty array"); + return array[array.length - 1]; + } + } diff --git a/core/src/main/java/com/graphhopper/util/BaseGraphVisualizer.java b/core/src/main/java/com/graphhopper/util/BaseGraphVisualizer.java new file mode 100644 index 00000000000..203a3aa0d53 --- /dev/null +++ b/core/src/main/java/com/graphhopper/util/BaseGraphVisualizer.java @@ -0,0 +1,331 @@ +package com.graphhopper.util; + +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.util.AllEdgesIterator; +import com.graphhopper.routing.weighting.SpeedWeighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.util.shapes.BBox; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CountDownLatch; + +public class BaseGraphVisualizer { + + private BaseGraphVisualizer() { + } + + public static void show(BaseGraph graph) { + show(graph, null); + } + + public static void show(BaseGraph graph, DecimalEncodedValue speedEnc) { + if (graph.getNodes() > 1000 || graph.getEdges() > 1000) + throw new IllegalArgumentException("BaseGraphVisualizer only supports up to 1000 nodes/edges, " + + "but graph has " + graph.getNodes() + " nodes and " + graph.getEdges() + " edges"); + var latch = new CountDownLatch(1); + SwingUtilities.invokeLater(() -> { + var frame = new JFrame("BaseGraph"); + frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); + frame.addWindowListener(new WindowAdapter() { + @Override + public void windowClosed(WindowEvent e) { + latch.countDown(); + } + }); + frame.add(new GraphPanel(graph, speedEnc)); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + }); + try { + latch.await(); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } + + private static class GraphPanel extends JPanel { + private static final Locale L = Locale.US; + private static final int PAD = 40; + private static final int W = 800, H = 800; + private static final DistanceCalc DIST = new DistanceCalcEuclidean(); + private static final Font LABEL_FONT = new Font("SansSerif", Font.PLAIN, 10); + private static final Font STATS_FONT = new Font("SansSerif", Font.PLAIN, 12); + + private final BaseGraph graph; + private final NodeAccess na; + private final DecimalEncodedValue speedEnc; + private final BBox bounds; + private final String statsText; + private int hoveredNode = -1; + private int hoveredEdge = -1; + private int hoveredAdjNode = -1; + private int mouseX, mouseY; + private final Set clickedEdges = new HashSet<>(); + private final SpeedWeighting speedWeighting; + private long sumDist; + private long sumFwdTime; + private double sumFwdWeight; + + GraphPanel(BaseGraph graph, DecimalEncodedValue speedEnc) { + this.graph = graph; + this.na = graph.getNodeAccess(); + this.speedEnc = speedEnc; + this.speedWeighting = speedEnc != null ? new SpeedWeighting(speedEnc) : null; + setPreferredSize(new Dimension(W, H)); + setBackground(new Color(20, 20, 30)); + this.bounds = computeBounds(); + this.statsText = computeStats(); + addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + mouseX = e.getX(); + mouseY = e.getY(); + updateHover(mouseX, mouseY); + } + }); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.isShiftDown() && SwingUtilities.isLeftMouseButton(e) && hoveredEdge >= 0) { + if (clickedEdges.add(hoveredEdge)) { + EdgeIteratorState edge = graph.getEdgeIteratorState(hoveredEdge, hoveredAdjNode); + sumDist += edge.getDistance_mm(); + if (speedEnc != null) { + sumFwdTime += speedWeighting.calcEdgeMillis(edge, false); + sumFwdWeight += speedWeighting.calcEdgeWeight(edge, false); + } + repaint(); + } + } + if (SwingUtilities.isRightMouseButton(e)) { + clickedEdges.clear(); + sumDist = 0; + sumFwdTime = 0; + sumFwdWeight = 0; + repaint(); + } + } + }); + } + + private String computeStats() { + String stats = graph.getNodes() + " nodes \u00b7 " + graph.getEdges() + " edges"; + if (graph.getEdges() > 0) { + long minDist = Long.MAX_VALUE, maxDist = Long.MIN_VALUE; + double minSpeed = Double.MAX_VALUE, maxSpeed = Double.MIN_VALUE; + long minTime = Long.MAX_VALUE, maxTime = Long.MIN_VALUE; + double minWeight = Double.MAX_VALUE, maxWeight = Double.MIN_VALUE; + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + long d = iter.getDistance_mm(); + minDist = Math.min(minDist, d); + maxDist = Math.max(maxDist, d); + if (speedEnc != null) { + double speedFwd = iter.get(speedEnc); + double speedBwd = iter.getReverse(speedEnc); + minSpeed = Math.min(minSpeed, Math.min(speedFwd, speedBwd)); + maxSpeed = Math.max(maxSpeed, Math.max(speedFwd, speedBwd)); + for (boolean reverse : new boolean[]{true, false}) { + long t = speedWeighting.calcEdgeMillis(iter, reverse); + if (t < Long.MAX_VALUE) { + minTime = Math.min(minTime, t); + maxTime = Math.max(maxTime, t); + } + double w = speedWeighting.calcEdgeWeight(iter, reverse); + if (Double.isFinite(w)) { + minWeight = Math.min(minWeight, w); + maxWeight = Math.max(maxWeight, w); + } + } + } + } + stats += String.format(L, " \u00b7 dist: %d..%dmm", minDist, maxDist); + if (speedEnc != null) { + stats += String.format(L, " \u00b7 speed: %.0f..%.0fm/s \u00b7 time: %d..%dms \u00b7 weight: %.1f..%.1f", minSpeed, maxSpeed, minTime, maxTime, minWeight, maxWeight); + } + } + return stats; + } + + private BBox computeBounds() { + BBox bounds = BBox.createInverse(false); + for (int i = 0; i < graph.getNodes(); i++) + bounds.update(na.getLat(i), na.getLon(i)); + double padLat = (bounds.maxLat - bounds.minLat) * 0.1; + double padLon = (bounds.maxLon - bounds.minLon) * 0.1; + if (padLat < 1e-9) padLat = 0.001; + if (padLon < 1e-9) padLon = 0.001; + bounds.minLat -= padLat; + bounds.maxLat += padLat; + bounds.minLon -= padLon; + bounds.maxLon += padLon; + return bounds; + } + + private int sx(double lon) { + return PAD + (int) ((lon - bounds.minLon) / (bounds.maxLon - bounds.minLon) * (getWidth() - 2 * PAD)); + } + + private int sy(double lat) { + return getHeight() - PAD - (int) ((lat - bounds.minLat) / (bounds.maxLat - bounds.minLat) * (getHeight() - 2 * PAD)); + } + + private void updateHover(int mx, int my) { + int prevNode = hoveredNode, prevEdge = hoveredEdge; + hoveredNode = -1; + hoveredEdge = -1; + hoveredAdjNode = -1; + + // check nodes first (priority over edges) + double bestD = 64; + for (int i = 0; i < graph.getNodes(); i++) { + double dx = sx(na.getLon(i)) - mx, dy = sy(na.getLat(i)) - my; + double d = dx * dx + dy * dy; + if (d < bestD) { + bestD = d; + hoveredNode = i; + } + } + + // check edges only if no node is hovered + if (hoveredNode == -1) { + double bestED = 36; + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + int base = iter.getBaseNode(), adj = iter.getAdjNode(); + double ax = sx(na.getLon(base)), ay = sy(na.getLat(base)); + double bx = sx(na.getLon(adj)), by = sy(na.getLat(adj)); + if (DIST.validEdgeDistance(my, mx, ay, ax, by, bx)) { + double d = DIST.calcNormalizedEdgeDistance(my, mx, ay, ax, by, bx); + if (d < bestED) { + bestED = d; + hoveredEdge = iter.getEdge(); + hoveredAdjNode = adj; + } + } + } + } + if (hoveredNode != prevNode || hoveredEdge != prevEdge) repaint(); + } + + private void drawTooltip(Graphics2D g, int x, int y, java.util.List lines) { + if (lines.isEmpty()) return; + FontMetrics fm = g.getFontMetrics(); + int lineH = fm.getHeight(); + int pad = 4; + int maxW = 0; + for (String line : lines) maxW = Math.max(maxW, fm.stringWidth(line)); + int boxW = maxW + pad * 2; + int boxH = lineH * lines.size() + pad * 2; + int bx = x - pad, by = y - fm.getAscent() - pad; + g.setColor(new Color(20, 20, 30, 255)); + g.fillRoundRect(bx, by, boxW, boxH, 6, 6); + g.setColor(Color.WHITE); + for (int i = 0; i < lines.size(); i++) { + g.drawString(lines.get(i), x, y + i * lineH); + } + } + + @Override + protected void paintComponent(Graphics g0) { + super.paintComponent(g0); + var g = (Graphics2D) g0; + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + + // draw edges (duplicate edges between the same node pair get a thicker stroke) + var seenPairs = new com.carrotsearch.hppc.LongHashSet(); + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + int base = iter.getBaseNode(), adj = iter.getAdjNode(); + long key = (long) Math.min(base, adj) << 32 | Math.max(base, adj); + boolean isDuplicate = !seenPairs.add(key); + boolean isClicked = clickedEdges.contains(iter.getEdge()); + boolean isHov = iter.getEdge() == hoveredEdge; + if (isClicked) g.setColor(new Color(34, 197, 94)); + else if (isHov) g.setColor(Color.WHITE); + else g.setColor(new Color(59, 130, 246, 130)); + float width = isHov || isClicked ? 2 : isDuplicate ? 4 : 1; + g.setStroke(new BasicStroke(width)); + g.drawLine(sx(na.getLon(base)), sy(na.getLat(base)), sx(na.getLon(adj)), sy(na.getLat(adj))); + } + + // draw nodes + g.setStroke(new BasicStroke(1)); + g.setFont(LABEL_FONT); + for (int i = 0; i < graph.getNodes(); i++) { + boolean isHov = i == hoveredNode; + int r = isHov ? 6 : 3; + int px = sx(na.getLon(i)), py = sy(na.getLat(i)); + g.setColor(isHov ? Color.WHITE : new Color(229, 229, 229)); + g.fillOval(px - r, py - r, r * 2, r * 2); + g.setColor(new Color(160, 160, 180)); + g.drawString(String.valueOf(i), px + 8, py - 6); + } + + // stats + g.setFont(STATS_FONT); + g.setColor(new Color(160, 160, 180)); + g.drawString(statsText, 10, getHeight() - 10); + + // hover info (next to cursor) + g.setFont(LABEL_FONT); + int tx = mouseX + 15, ty = mouseY - 10; + if (hoveredEdge >= 0) { + EdgeIteratorState e = graph.getEdgeIteratorState(hoveredEdge, hoveredAdjNode); + var lines = new ArrayList(); + lines.add(String.format(L, "Edge %d: %d -> %d", e.getEdge(), e.getBaseNode(), e.getAdjNode())); + lines.add(String.format(L, "Distance: %dmm", e.getDistance_mm())); + if (speedEnc != null) { + double fwdSpeed = e.get(speedEnc); + double bwdSpeed = e.getReverse(speedEnc); + lines.add(String.format(L, "Speed-Fwd: %.1f m/s (%.0f km/h)", fwdSpeed, fwdSpeed * 3.6)); + lines.add(String.format(L, "Speed-Bwd: %.1f m/s (%.0f km/h)", bwdSpeed, bwdSpeed * 3.6)); + long fwdTime = speedWeighting.calcEdgeMillis(e, false); + long bwdTime = speedWeighting.calcEdgeMillis(e, true); + lines.add(String.format(L, "Time-Fwd: %dms", fwdTime)); + lines.add(String.format(L, "Time-Bwd: %dms", bwdTime)); + double fwdWeight = speedWeighting.calcEdgeWeight(e, false); + double bwdWeight = speedWeighting.calcEdgeWeight(e, true); + lines.add(String.format(L, "Weight-Fwd: %.3f", fwdWeight)); + lines.add(String.format(L, "Weight-Bwd: %.3f", bwdWeight)); + } + drawTooltip(g, tx, ty, lines); + } + if (hoveredNode >= 0) { + var lines = new ArrayList(); + lines.add(String.format(L, "Node %d", hoveredNode)); + lines.add(String.format(L, "Lat: %.6f", na.getLat(hoveredNode))); + lines.add(String.format(L, "Lon: %.6f", na.getLon(hoveredNode))); + drawTooltip(g, tx, ty, lines); + } + + // sum of clicked edges (top-right) + if (!clickedEdges.isEmpty()) { + g.setFont(LABEL_FONT); + g.setColor(new Color(34, 197, 94)); + String sumInfo = String.format(L, "Sum (%d edges): Distance=%dmm", clickedEdges.size(), sumDist); + if (speedEnc != null) { + sumInfo += String.format(L, ", Time-Fwd=%dms", sumFwdTime); + sumInfo += String.format(L, ", Weight-Fwd=%.3f", sumFwdWeight); + } + FontMetrics fm = g.getFontMetrics(); + g.drawString(sumInfo, getWidth() - fm.stringWidth(sumInfo) - 10, 20); + } + + // instructions (bottom-right) + g.setFont(LABEL_FONT); + g.setColor(new Color(100, 100, 120)); + String instructions = "shift+click: sum edges | right-click: reset"; + FontMetrics fm = g.getFontMetrics(); + g.drawString(instructions, getWidth() - fm.stringWidth(instructions) - 10, getHeight() - 10); + } + } +} diff --git a/core/src/main/java/com/graphhopper/util/BitUtil.java b/core/src/main/java/com/graphhopper/util/BitUtil.java index fadc17f68e7..29257f64232 100644 --- a/core/src/main/java/com/graphhopper/util/BitUtil.java +++ b/core/src/main/java/com/graphhopper/util/BitUtil.java @@ -17,8 +17,6 @@ */ package com.graphhopper.util; -import com.graphhopper.storage.IntsRef; - /** * LITTLE endianness is default for GraphHopper and most microprocessors. * @@ -88,6 +86,10 @@ public final int toInt(byte[] b, int offset) { | (b[offset + 1] & 0xFF) << 8 | (b[offset] & 0xFF); } + public final int toUInt3(byte[] b, int offset) { + return (b[offset + 2] & 0xFF) << 16 | (b[offset + 1] & 0xFF) << 8 | (b[offset] & 0xFF); + } + public final byte[] fromInt(int value) { byte[] bytes = new byte[4]; fromInt(bytes, value, 0); @@ -120,6 +122,15 @@ public final void fromInt(byte[] bytes, int value, int offset) { bytes[offset] = (byte) (value); } + /** + * Note, currently value with higher bits set (like for a negative value) won't throw an exception at this level. + */ + public final void fromUInt3(byte[] bytes, int value, int offset) { + bytes[offset + 2] = (byte) (value >>> 16); + bytes[offset + 1] = (byte) (value >>> 8); + bytes[offset] = (byte) (value); + } + /** * See the counterpart {@link #fromLong(long)} */ @@ -128,11 +139,11 @@ public final long toLong(byte[] b) { } public final long toLong(int intLow, int intHigh) { - return ((long) intHigh << 32) | (intLow & 0xFFFFFFFFL); + return ((long) intHigh << 32) | (intLow & 0xFFFF_FFFFL); } public final long toLong(byte[] b, int offset) { - return ((long) toInt(b, offset + 4) << 32) | (toInt(b, offset) & 0xFFFFFFFFL); + return ((long) toInt(b, offset + 4) << 32) | (toInt(b, offset) & 0xFFFF_FFFFL); } public final byte[] fromLong(long value) { @@ -179,14 +190,6 @@ public byte[] fromBitString(String str) { return bytes; } - public final String toBitString(IntsRef intsRef) { - StringBuilder str = new StringBuilder(); - for (int ints : intsRef.ints) { - str.append(toBitString(ints, 32)); - } - return str.toString(); - } - /** * Similar to Long.toBinaryString */ @@ -249,7 +252,7 @@ public String toBitString(byte[] bytes) { } public final int getIntLow(long longValue) { - return (int) (longValue & 0xFFFFFFFFL); + return (int) (longValue & 0xFFFF_FFFFL); } public final int getIntHigh(long longValue) { diff --git a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IrelandCountryRule.java b/core/src/main/java/com/graphhopper/util/BodyAndStatus.java similarity index 66% rename from core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IrelandCountryRule.java rename to core/src/main/java/com/graphhopper/util/BodyAndStatus.java index 105162ec406..2a804eaca9a 100644 --- a/core/src/main/java/com/graphhopper/routing/util/countryrules/europe/IrelandCountryRule.java +++ b/core/src/main/java/com/graphhopper/util/BodyAndStatus.java @@ -16,10 +16,24 @@ * limitations under the License. */ -package com.graphhopper.routing.util.countryrules.europe; +package com.graphhopper.util; -import com.graphhopper.routing.util.countryrules.CountryRule; +import com.fasterxml.jackson.databind.JsonNode; -public class IrelandCountryRule implements CountryRule { +public class BodyAndStatus { + private final JsonNode body; + private final int status; + public BodyAndStatus(JsonNode body, int status) { + this.body = body; + this.status = status; + } + + public JsonNode getBody() { + return body; + } + + public int getStatus() { + return status; + } } diff --git a/core/src/main/java/com/graphhopper/util/Constants.java b/core/src/main/java/com/graphhopper/util/Constants.java index 498479f8d1a..8e4f70fadeb 100644 --- a/core/src/main/java/com/graphhopper/util/Constants.java +++ b/core/src/main/java/com/graphhopper/util/Constants.java @@ -59,12 +59,12 @@ public class Constants { private static final int JVM_MINOR_VERSION; public static final int VERSION_NODE = 9; - public static final int VERSION_EDGE = 21; + public static final int VERSION_EDGE = 24; // this should be increased whenever the format of the serialized EncodingManager is changed - public static final int VERSION_EM = 3; - public static final int VERSION_SHORTCUT = 9; + public static final int VERSION_EM = 4; + public static final int VERSION_SHORTCUT = 11; public static final int VERSION_NODE_CH = 0; - public static final int VERSION_GEOMETRY = 6; + public static final int VERSION_GEOMETRY = 8; public static final int VERSION_TURN_COSTS = 0; public static final int VERSION_LOCATION_IDX = 5; public static final int VERSION_KV_STORAGE = 2; diff --git a/core/src/main/java/com/graphhopper/util/Downloader.java b/core/src/main/java/com/graphhopper/util/Downloader.java index f28997adf2a..9a6d0e508d0 100644 --- a/core/src/main/java/com/graphhopper/util/Downloader.java +++ b/core/src/main/java/com/graphhopper/util/Downloader.java @@ -20,6 +20,10 @@ import java.io.*; import java.net.HttpURLConnection; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.function.LongConsumer; import java.util.zip.GZIPInputStream; import java.util.zip.Inflater; @@ -29,17 +33,14 @@ * @author Peter Karich */ public class Downloader { - private final String userAgent; + private static final int BUFFER_SIZE = 8 * 1024; + private static final String USER_AGENT = "graphhopper/" + Constants.VERSION; private String referrer = "http://graphhopper.com"; private String acceptEncoding = "gzip, deflate"; private int timeout = 4000; - public Downloader(String userAgent) { - this.userAgent = userAgent; - } - public static void main(String[] args) throws IOException { - new Downloader("GraphHopper Downloader").downloadAndUnzip("http://graphhopper.com/public/maps/0.1/europe_germany_berlin.ghz", "somefolder", + new Downloader().downloadAndUnzip("http://graphhopper.com/public/maps/0.1/europe_germany_berlin.ghz", "somefolder", val -> System.out.println("progress:" + val)); } @@ -76,9 +77,9 @@ public InputStream fetch(HttpURLConnection connection, boolean readErrorStreamNo try { String encoding = connection.getContentEncoding(); if (encoding != null && encoding.equalsIgnoreCase("gzip")) - is = new GZIPInputStream(is); + is = new GZIPInputStream(is, BUFFER_SIZE); else if (encoding != null && encoding.equalsIgnoreCase("deflate")) - is = new InflaterInputStream(is, new Inflater(true)); + is = new InflaterInputStream(is, new Inflater(true), BUFFER_SIZE); } catch (IOException ex) { } @@ -96,7 +97,7 @@ public HttpURLConnection createConnection(String urlStr) throws IOException { conn.setDoInput(true); conn.setUseCaches(true); conn.setRequestProperty("Referrer", referrer); - conn.setRequestProperty("User-Agent", userAgent); + conn.setRequestProperty("User-Agent", USER_AGENT); // suggest respond to be gzipped or deflated (which is just another compression) // http://stackoverflow.com/q/3932117 conn.setRequestProperty("Accept-Encoding", acceptEncoding); @@ -107,21 +108,12 @@ public HttpURLConnection createConnection(String urlStr) throws IOException { public void downloadFile(String url, String toFile) throws IOException { HttpURLConnection conn = createConnection(url); - InputStream iStream = fetch(conn, false); - int size = 8 * 1024; - BufferedOutputStream writer = new BufferedOutputStream(new FileOutputStream(toFile), size); - InputStream in = new BufferedInputStream(iStream, size); - try { - byte[] buffer = new byte[size]; - int numRead; - while ((numRead = in.read(buffer)) != -1) { - writer.write(buffer, 0, numRead); - } - } finally { - Helper.close(iStream); - Helper.close(writer); - Helper.close(in); + Path target = Paths.get(toFile); + Path tmpFile = target.resolveSibling(target.getFileName().toString() + ".part"); + try (var in = fetch(conn, false); var out = Files.newOutputStream(tmpFile)) { + in.transferTo(out); } + Files.move(tmpFile, target, StandardCopyOption.ATOMIC_MOVE); } public void downloadAndUnzip(String url, String toFolder, final LongConsumer progressListener) throws IOException { diff --git a/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java b/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java index 6975bd61ee5..4299009c993 100644 --- a/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java +++ b/core/src/main/java/com/graphhopper/util/EdgeIteratorState.java @@ -22,7 +22,7 @@ import com.graphhopper.storage.Graph; import com.graphhopper.storage.IntsRef; -import java.util.List; +import java.util.Map; /** * This interface represents an edge and is one possible state of an EdgeIterator. @@ -40,14 +40,40 @@ * @see EdgeExplorer */ public interface EdgeIteratorState { - BooleanEncodedValue UNFAVORED_EDGE = new SimpleBooleanEncodedValue("unfavored"); + BooleanEncodedValue UNFAVORED_EDGE = new BooleanEncodedValue() { + @Override + public int init(InitializerConfig init) { + throw new IllegalStateException("Cannot happen for 'unfavored' BooleanEncodedValue"); + } + + @Override + public boolean getBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) { + return false; + } + + @Override + public void setBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess, boolean value) { + throw new IllegalStateException("state of 'unfavored' cannot be modified"); + } + + @Override + public boolean isStoreTwoDirections() { + return false; + } + + @Override + public String getName() { + return "unfavored"; + } + }; + /** * This method can be used to fetch the internal reverse state of an edge. */ BooleanEncodedValue REVERSE_STATE = new BooleanEncodedValue() { @Override public int init(InitializerConfig init) { - throw new IllegalStateException("Cannot happen for this BooleanEncodedValue"); + throw new IllegalStateException("Cannot happen for 'reverse' BooleanEncodedValue"); } @Override @@ -62,7 +88,7 @@ public boolean getBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess) @Override public void setBool(boolean reverse, int edgeId, EdgeIntAccess edgeIntAccess, boolean value) { - throw new IllegalStateException("reverse state cannot be modified"); + throw new IllegalStateException("state of 'reverse' cannot be modified"); } @Override @@ -120,18 +146,32 @@ public boolean isStoreTwoDirections() { /** * @param list is a sorted collection of coordinates between the base node and the current adjacent node. Specify - * the list without the adjacent and base node. This method can be called multiple times, but if the - * distance changes, the setDistance method is not called automatically. + * the list without the adjacent and base node. This method can be called multiple times, unless the + * given point list is longer than the first time the method was called. Also keep in + * mind that if the distance changes the setDistance method is not called automatically. */ EdgeIteratorState setWayGeometry(PointList list); /** * @return the distance of the current edge in meter */ + // todonow: check if we should replace more usages with getDistance_mm. also remove tolerances in tests, but maybe postpone double getDistance(); EdgeIteratorState setDistance(double dist); + /** + * Returns the distance of the current edge in millimeters. This should be used wherever exact + * distance summation is desired. + */ + long getDistance_mm(); + + /** + * Sets the distance in mm. This should be used wherever exact distance summation is desired. + * Distances above the storage limit will be capped! + */ + EdgeIteratorState setDistance_mm(long distance_mm); + /** * Returns edge properties stored in direction of the raw database layout. So do not use it directly, instead * use the appropriate set/get methods with its EncodedValue object. @@ -207,14 +247,14 @@ public boolean isStoreTwoDirections() { * But it might be slow and more inefficient on retrieval. Call this setKeyValues method only once per * EdgeIteratorState as it allocates new space everytime this method is called. */ - EdgeIteratorState setKeyValues(List map); + EdgeIteratorState setKeyValues(Map map); /** * This method returns KeyValue pairs for both directions in contrast to {@link #getValue(String)}. * - * @see #setKeyValues(List) + * @see #setKeyValues(Map) */ - List getKeyValues(); + Map getKeyValues(); /** * This method returns the *first* value for the specified key and only if stored for the direction of this @@ -239,4 +279,7 @@ public boolean isStoreTwoDirections() { * @return the specified edge e */ EdgeIteratorState copyPropertiesFrom(EdgeIteratorState e); + + boolean isVirtual(); + } diff --git a/core/src/main/java/com/graphhopper/util/GHUtility.java b/core/src/main/java/com/graphhopper/util/GHUtility.java index 80212aec3e4..d47a6d2308e 100644 --- a/core/src/main/java/com/graphhopper/util/GHUtility.java +++ b/core/src/main/java/com/graphhopper/util/GHUtility.java @@ -21,8 +21,7 @@ import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.IntIndexedContainer; import com.fasterxml.jackson.databind.ObjectMapper; -import com.graphhopper.coll.GHBitSet; -import com.graphhopper.coll.GHBitSetImpl; +import com.graphhopper.jackson.Jackson; import com.graphhopper.routing.Path; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.Country; @@ -43,21 +42,18 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.UncheckedIOException; +import java.io.*; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ExecutionException; import java.util.concurrent.ForkJoinPool; -import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; import static com.graphhopper.routing.ev.State.ISO_3166_2; import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; +import static com.graphhopper.util.Helper.readJSONFileWithoutComments; /** * A helper class to avoid cluttering the Graph interface with all the common methods. Most of the @@ -382,6 +378,29 @@ public static int getEdgeFromEdgeKey(int edgeKey) { return edgeKey / 2; } + /** + * @return the common node of two edges + * @throws IllegalArgumentException if one of the edges doesn't exist or is a loop or the edges + * aren't connected at exactly one distinct node + */ + public static int getCommonNode(BaseGraph baseGraph, int edge1, int edge2) { + EdgeIteratorState e1 = baseGraph.getEdgeIteratorState(edge1, Integer.MIN_VALUE); + EdgeIteratorState e2 = baseGraph.getEdgeIteratorState(edge2, Integer.MIN_VALUE); + if (e1.getBaseNode() == e1.getAdjNode()) + throw new IllegalArgumentException("edge1: " + edge1 + " is a loop at node " + e1.getBaseNode()); + if (e2.getBaseNode() == e2.getAdjNode()) + throw new IllegalArgumentException("edge2: " + edge2 + " is a loop at node " + e2.getBaseNode()); + + if ((e1.getBaseNode() == e2.getBaseNode() && e1.getAdjNode() == e2.getAdjNode()) || (e1.getBaseNode() == e2.getAdjNode() && e1.getAdjNode() == e2.getBaseNode())) + throw new IllegalArgumentException("edge1: " + edge1 + " and edge2: " + edge2 + " form a circle"); + else if (e1.getBaseNode() == e2.getBaseNode() || e1.getBaseNode() == e2.getAdjNode()) + return e1.getBaseNode(); + else if (e1.getAdjNode() == e2.getAdjNode() || e1.getAdjNode() == e2.getBaseNode()) + return e1.getAdjNode(); + else + throw new IllegalArgumentException("edge1: " + edge1 + " and edge2: " + edge2 + " aren't connected"); + } + public static void setSpeed(double fwdSpeed, double bwdSpeed, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, EdgeIteratorState... edges) { setSpeed(fwdSpeed, bwdSpeed, accessEnc, speedEnc, Arrays.asList(edges)); } @@ -416,13 +435,18 @@ public static EdgeIteratorState setSpeed(double averageSpeed, boolean fwd, boole return edge; } - public static void updateDistancesFor(Graph g, int node, double lat, double lon) { + public static void updateDistancesFor(Graph g, int node, double... latlonele) { NodeAccess na = g.getNodeAccess(); - na.setNode(node, lat, lon); + if (latlonele.length == 3) + na.setNode(node, latlonele[0], latlonele[1], latlonele[2]); + else if (latlonele.length == 2) { + if (na.is3D()) throw new IllegalArgumentException("graph requires elevation"); + na.setNode(node, latlonele[0], latlonele[1]); + } else + throw new IllegalArgumentException("illegal number of arguments " + latlonele.length); EdgeIterator iter = g.createEdgeExplorer().setBaseNode(node); while (iter.next()) { iter.setDistance(DIST_EARTH.calcDistance(iter.fetchWayGeometry(FetchMode.ALL))); - // System.out.println(node + "->" + adj + ": " + iter.getDistance()); } } @@ -549,31 +573,33 @@ public static JsonFeature createRectangle(String id, double minLat, double minLo return result; } - public static List comparePaths(Path refPath, Path path, int source, int target, long seed) { + public static List comparePaths(Path refPath, Path path, int source, int target, boolean checkNodes, long seed) { + if (path.getGraph().getNodes() != refPath.getGraph().getNodes()) + fail("path and refPath graphs have unequal number of nodes"); List strictViolations = new ArrayList<>(); double refWeight = refPath.getWeight(); double weight = path.getWeight(); - if (Math.abs(refWeight - weight) > 1.e-2) { + if (refWeight != weight) { LOGGER.warn("expected: " + refPath.calcNodes()); LOGGER.warn("given: " + path.calcNodes()); LOGGER.warn("seed: " + seed); - fail("wrong weight: " + source + "->" + target + "\nexpected: " + refWeight + "\ngiven: " + weight + "\nseed: " + seed); + fail("wrong weight: " + source + "->" + target + "\nexpected: " + refWeight + "\ngiven: " + weight + "\nseed: " + seed + "L"); } - if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) { - strictViolations.add("wrong distance " + source + "->" + target + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance()); + if (path.getDistance_mm() != refPath.getDistance_mm()) { + strictViolations.add("wrong distance: " + source + "->" + target + "\nexpected: " + refPath.getDistance_mm() + "\ngiven: " + path.getDistance_mm() + "\nseed: " + seed + "L"); } - if (Math.abs(path.getTime() - refPath.getTime()) > 50) { - strictViolations.add("wrong time " + source + "->" + target + ", expected: " + refPath.getTime() + ", given: " + path.getTime()); + if (path.getTime() != refPath.getTime()) { + strictViolations.add("wrong time: " + source + "->" + target + "\nexpected: " + refPath.getTime() + "\ngiven: " + path.getTime() + "\nseed: " + seed + "L"); } - IntIndexedContainer refNodes = refPath.calcNodes(); - IntIndexedContainer pathNodes = path.calcNodes(); - if (!refNodes.equals(pathNodes)) { - // sometimes there are paths including an edge a-c that has the same distance as the two edges a-b-c. in this - // case both options are valid best paths. we only check for this most simple and frequent case here... - if (path.getGraph() != refPath.getGraph()) - fail("path and refPath graphs are different"); - if (!pathsEqualExceptOneEdge(path.getGraph(), refNodes, pathNodes)) - strictViolations.add("wrong nodes " + source + "->" + target + "\nexpected: " + refNodes + "\ngiven: " + pathNodes); + if (checkNodes) { + IntIndexedContainer refNodes = refPath.calcNodes(); + IntIndexedContainer pathNodes = path.calcNodes(); + if (!refNodes.equals(pathNodes)) { + // sometimes there are paths including an edge a-c that has the same distance as the two edges a-b-c. in this + // case both options are valid best paths. we only check for this most simple and frequent case here... + if (!pathsEqualExceptOneEdge(path.getGraph(), refNodes, pathNodes)) + strictViolations.add("wrong nodes " + source + "->" + target + "\nexpected: " + refNodes + "\ngiven: " + pathNodes); + } } return strictViolations; } @@ -633,4 +659,17 @@ private static double getMinDist(Graph graph, int p, int q) { private static void fail(String message) { throw new AssertionError(message); } + + public static CustomModel loadCustomModelFromJar(String name) { + try { + InputStream is = GHUtility.class.getResourceAsStream("/com/graphhopper/custom_models/" + name); + if (is == null) + throw new IllegalArgumentException("There is no built-in custom model '" + name + "'"); + String json = readJSONFileWithoutComments(new InputStreamReader(is)); + ObjectMapper objectMapper = Jackson.newObjectMapper(); + return objectMapper.readValue(json, CustomModel.class); + } catch (IOException e) { + throw new IllegalArgumentException("Could not load built-in custom model '" + name + "'", e); + } + } } diff --git a/core/src/main/java/com/graphhopper/util/PathMerger.java b/core/src/main/java/com/graphhopper/util/PathMerger.java index 1142d749ea3..7bce505ad46 100644 --- a/core/src/main/java/com/graphhopper/util/PathMerger.java +++ b/core/src/main/java/com/graphhopper/util/PathMerger.java @@ -93,7 +93,7 @@ public ResponsePath doWork(PointList waypoints, List paths, EncodedValueLo int origPoints = 0; long fullTimeInMillis = 0; double fullWeight = 0; - double fullDistance = 0; + long fullDistance_mm = 0; boolean allFound = true; InstructionList fullInstructions = new InstructionList(tr); @@ -108,7 +108,7 @@ public ResponsePath doWork(PointList waypoints, List paths, EncodedValueLo } description.addAll(path.getDescription()); fullTimeInMillis += path.getTime(); - fullDistance += path.getDistance(); + fullDistance_mm += path.getDistance_mm(); fullWeight += path.getWeight(); if (enableInstructions) { InstructionList il = InstructionsFromEdges.calcInstructions(path, graph, weighting, evLookup, tr); @@ -170,7 +170,7 @@ public ResponsePath doWork(PointList waypoints, List paths, EncodedValueLo responsePath.setDescription(description). setPoints(fullPoints). setRouteWeight(fullWeight). - setDistance(fullDistance). + setDistance(fullDistance_mm / 1000.0). setTime(fullTimeInMillis). setWaypoints(waypoints). setWaypointIndices(wayPointIndices); @@ -249,4 +249,4 @@ private void calcAscendDescend(final ResponsePath responsePath, final PointList public void setFavoredHeading(double favoredHeading) { this.favoredHeading = favoredHeading; } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/util/RandomGraph.java b/core/src/main/java/com/graphhopper/util/RandomGraph.java new file mode 100644 index 00000000000..4c9e22d4fca --- /dev/null +++ b/core/src/main/java/com/graphhopper/util/RandomGraph.java @@ -0,0 +1,304 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.util; + +import com.carrotsearch.hppc.*; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; + +import java.util.*; + +/** + * Creates random graphs for testing purposes. Nodes are aligned on a grid (+jitter), and connected + * to their KNN + */ +public class RandomGraph { + + private RandomGraph() { + } + + public static Builder start() { + return new Builder(); + } + + public static class Builder { + + private long seed = 42; + private int nodes = 10; + private boolean tree = false; + private boolean chain = false; + private Double speed = null; + private double duplicateEdges = 0.05; + private double curviness = 0.0; + private double speedMean = 16; + private double speedStdDev = 8; + private double pSpeedZero = 0; + + private final double centerLat = 50.0; + private final double centerLon = 10.0; + private final double step = 0.001; + private final double rowFactor = 0.9; + private final double jitter = 0.8; + private final int kMin = 2; + private final int kMax = 3; + + private record TmpGraph(double[] lats, double[] lons, LongArrayList edges) { + } + + public void fill(BaseGraph graph, DecimalEncodedValue speedEnc) { + if (graph.getNodes() > 0 || graph.getEdges() > 0) + throw new IllegalStateException("BaseGraph should be empty"); + if (chain && tree) + throw new IllegalArgumentException("chain and tree are mutually exclusive"); + if (chain) + buildChain(graph, speedEnc); + else if (tree) + buildTree(graph, speedEnc); + else + buildGraph(graph, speedEnc); + } + + private void buildGraph(BaseGraph graph, DecimalEncodedValue speedEnc) { + var rnd = new Random(seed); + TmpGraph g = generateTmpGraph(nodes, rnd); + fillBaseGraph(graph, speedEnc, rnd, g); + } + + private TmpGraph generateTmpGraph(int nodes, Random rnd) { + double[] lats = new double[nodes]; + double[] lons = new double[nodes]; + generateNodePositions(rnd, nodes, lats, lons); + var edges = generateKnnEdges(rnd, nodes, lats, lons); + int duplicates = (int) Math.ceil(edges.size() * duplicateEdges); + for (int i = 0; i < duplicates; i++) + edges.add(edges.get(rnd.nextInt(edges.size()))); + return new TmpGraph(lats, lons, edges); + } + + private void generateNodePositions(Random rnd, int n, double[] lats, double[] lons) { + int cols = Math.max(1, (int) Math.round(Math.sqrt(n / rowFactor))); + int rows = (int) Math.ceil((double) n / cols); + double offsetLat = ((rows - 1) * step) / 2.0; + double offsetLon = ((cols - 1) * step) / 2.0; + for (int i = 0; i < n; i++) { + int r = i / cols, c = i % cols; + lats[i] = centerLat - offsetLat + r * step + (rnd.nextDouble() - 0.5) * jitter * step; + lons[i] = centerLon - offsetLon + c * step + (rnd.nextDouble() - 0.5) * jitter * step; + } + } + + private LongArrayList generateKnnEdges(Random rnd, int n, double[] lats, double[] lons) { + record Pair(int j, double d) { + } + var edges = new LongScatterSet(); + for (int i = 0; i < n; i++) { + int ki = kMin + rnd.nextInt(kMax - kMin + 1); + var list = new ArrayList(); + for (int j = 0; j < n; j++) { + if (j == i) continue; + double dLat = lats[i] - lats[j], dLon = lons[i] - lons[j]; + list.add(new Pair(j, dLat * dLat + dLon * dLon)); + } + list.sort(Comparator.comparingDouble(Pair::d)); + int limit = Math.min(ki, list.size()); + for (int k = 0; k < limit; k++) { + int j = list.get(k).j; + int a = Math.min(i, j), b = Math.max(i, j); + edges.add(BitUtil.LITTLE.toLong(a, b)); + } + } + return new LongArrayList(edges); + } + + private void fillBaseGraph(BaseGraph graph, DecimalEncodedValue speedEnc, Random rnd, TmpGraph g) { + NodeAccess na = graph.getNodeAccess(); + for (int i = 0; i < g.lats.length; i++) { + na.setNode(i, g.lats[i], g.lons[i]); + } + for (var e : g.edges) { + int from = BitUtil.LITTLE.getIntHigh(e.value); + int to = BitUtil.LITTLE.getIntLow(e.value); + EdgeIteratorState edge = graph.edge(from, to); + + double beeline = GHUtility.getDistance(from, to, na); + double distance = Math.max(beeline, beeline * (1 + curviness * rnd.nextDouble())); + if (distance < 0.001) distance = 0.001; + edge.setDistance(distance); + + double fwdSpeed = Math.max(1, Math.min(50, speedMean + speedStdDev * rnd.nextGaussian())); + double bwdSpeed = Math.max(1, Math.min(50, speedMean + speedStdDev * rnd.nextGaussian())); + // if an explicit speed is given we discard the random speeds and use the given one instead + if (speed != null) + fwdSpeed = bwdSpeed = speed; + // zero speeds are possible even if an explicit speed is given + if (rnd.nextDouble() < pSpeedZero) + fwdSpeed = 0; + if (rnd.nextDouble() < pSpeedZero) + bwdSpeed = 0; + if (speedEnc != null) { + edge.set(speedEnc, fwdSpeed); + if (speedEnc.isStoreTwoDirections()) + edge.setReverse(speedEnc, bwdSpeed); + } + } + } + + private void buildTree(BaseGraph graph, DecimalEncodedValue speedEnc) { + for (int attempt = 0; attempt < 1000; attempt++) { + long trySeed = seed + attempt; + Random rnd = new Random(trySeed); + TmpGraph g = generateTmpGraph(nodes, rnd); + LongArrayList treeEdges = findBFSTreeEdgesFromCenter(g); + IntSet nodesInTree = new IntHashSet(); + for (var e : treeEdges) { + nodesInTree.add(BitUtil.LITTLE.getIntHigh(e.value)); + nodesInTree.add(BitUtil.LITTLE.getIntLow(e.value)); + } + // we wait until we find a graph that is fully connected to make sure our tree has the + // desired number of nodes + if (nodesInTree.size() == nodes) { + var tree = new TmpGraph(g.lats, g.lons, treeEdges); + fillBaseGraph(graph, speedEnc, rnd, tree); + return; + } + } + throw new IllegalStateException("Could not generate a spanning tree after 1000 attempts"); + } + + private void buildChain(BaseGraph graph, DecimalEncodedValue speedEnc) { + Random rnd = new Random(seed); + double[] lats = new double[nodes]; + double[] lons = new double[nodes]; + generateNodePositions(rnd, nodes, lats, lons); + + // connect nodes in serpentine order so consecutive chain nodes are grid-neighbors + int cols = Math.max(1, (int) Math.round(Math.sqrt(nodes / rowFactor))); + int rows = (int) Math.ceil((double) nodes / cols); + int[] order = new int[nodes]; + int idx = 0; + for (int r = 0; r < rows; r++) { + int start = r * cols; + int end = Math.min(start + cols, nodes); + if (r % 2 == 0) { + for (int c = start; c < end; c++) + order[idx++] = c; + } else { + for (int c = end - 1; c >= start; c--) + order[idx++] = c; + } + } + + LongArrayList edges = new LongArrayList(); + for (int i = 0; i < nodes - 1; i++) { + int a = Math.min(order[i], order[i + 1]); + int b = Math.max(order[i], order[i + 1]); + edges.add(BitUtil.LITTLE.toLong(a, b)); + } + var g = new TmpGraph(lats, lons, edges); + fillBaseGraph(graph, speedEnc, rnd, g); + } + + private LongArrayList findBFSTreeEdgesFromCenter(TmpGraph g) { + var adjNodes = new HashMap>(); + for (var e : g.edges) { + int a = BitUtil.LITTLE.getIntHigh(e.value), b = BitUtil.LITTLE.getIntLow(e.value); + adjNodes.computeIfAbsent(a, x -> new ArrayList<>()).add(b); + adjNodes.computeIfAbsent(b, x -> new ArrayList<>()).add(a); + } + int center = 0; + double best = Double.MAX_VALUE; + for (int i = 0; i < g.lats.length; i++) { + double d = (g.lats[i] - centerLat) * (g.lats[i] - centerLat) + (g.lons[i] - centerLon) * (g.lons[i] - centerLon); + if (d < best) { + best = d; + center = i; + } + } + var visited = new boolean[g.lats.length]; + visited[center] = true; + var queue = new ArrayDeque(); + queue.add(center); + LongSet treeEdges = new LongScatterSet(); + while (!queue.isEmpty()) { + int cur = queue.poll(); + for (int nb : adjNodes.getOrDefault(cur, List.of())) { + if (!visited[nb]) { + visited[nb] = true; + treeEdges.add(BitUtil.LITTLE.toLong(Math.min(cur, nb), Math.max(cur, nb))); + queue.add(nb); + } + } + } + return new LongArrayList(treeEdges); + } + + public Builder seed(long v) { + seed = v; + return this; + } + + public Builder nodes(int v) { + nodes = v; + return this; + } + + public Builder tree(boolean v) { + tree = v; + return this; + } + + public Builder chain(boolean v) { + chain = v; + return this; + } + + public Builder speed(Double v) { + speed = v; + return this; + } + + public Builder duplicateEdges(double v) { + duplicateEdges = v; + return this; + } + + public Builder curviness(double v) { + curviness = v; + return this; + } + + public Builder speedMean(double v) { + speedMean = v; + return this; + } + + public Builder speedStdDev(double v) { + speedStdDev = v; + return this; + } + + public Builder speedZero(double v) { + pSpeedZero = v; + return this; + } + + } + +} diff --git a/core/src/main/java/com/graphhopper/util/TranslationMap.java b/core/src/main/java/com/graphhopper/util/TranslationMap.java index fd5ca8613be..2127aca272b 100644 --- a/core/src/main/java/com/graphhopper/util/TranslationMap.java +++ b/core/src/main/java/com/graphhopper/util/TranslationMap.java @@ -35,7 +35,7 @@ public class TranslationMap { private static final List LOCALES = Arrays.asList("ar", "ast", "bg", "bn_BN", "ca", "cs_CZ", "da_DK", "de_DE", "el", "eo", "es", "en_US", "fa", "fil", "fi", "fr_FR", "fr_CH", "gl", "he", "hr_HR", "hsb", "hu_HU", "in_ID", "it", "ja", "ko", - "kz", "lt_LT", "nb_NO", "ne", "nl", "pl_PL", "pt_BR", "pt_PT", "ro", "ru", "sk", + "kz", "lt_LT", "mn", "nb_NO", "ne", "nl", "pl_PL", "pt_BR", "pt_PT", "ro", "ru", "sk", "sl_SI", "sr_RS", "sv_SE", "tr", "uk", "uz", "vi_VN", "zh_CN", "zh_HK", "zh_TW"); private final Map translations = new HashMap<>(); diff --git a/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java b/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java index ad8f97ee70a..f8f3a5fee4d 100644 --- a/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java +++ b/core/src/main/java/com/graphhopper/util/details/AbstractPathDetailsBuilder.java @@ -77,7 +77,7 @@ public void endInterval(int lastIndex) { } public Map.Entry> build() { - return new MapEntry(getName(), pathDetails); + return new MapEntry<>(getName(), pathDetails); } @Override @@ -89,4 +89,4 @@ public String getName() { public String toString() { return getName(); } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/util/details/ChangeAngleDetails.java b/core/src/main/java/com/graphhopper/util/details/ChangeAngleDetails.java new file mode 100644 index 00000000000..f7897f4a33f --- /dev/null +++ b/core/src/main/java/com/graphhopper/util/details/ChangeAngleDetails.java @@ -0,0 +1,46 @@ +package com.graphhopper.util.details; + +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.weighting.custom.CustomWeightingHelper; +import com.graphhopper.util.EdgeIteratorState; + +import static com.graphhopper.util.Parameters.Details.CHANGE_ANGLE; + +/** + * This class handles the calculation for the change_angle path detail, i.e. the angle between the + * edges calculated from the 'orientation' of an edge. + */ +public class ChangeAngleDetails extends AbstractPathDetailsBuilder { + + private final DecimalEncodedValue orientationEv; + private Double prevAzimuth; + private Double changeAngle; + + public ChangeAngleDetails(DecimalEncodedValue orientationEv) { + super(CHANGE_ANGLE); + this.orientationEv = orientationEv; + } + + @Override + protected Object getCurrentValue() { + return changeAngle; + } + + @Override + public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { + if (prevAzimuth != null) { + double azimuth = edge.getReverse(orientationEv); + double tmp = CustomWeightingHelper.calcChangeAngle(prevAzimuth, azimuth); + double tmpRound = Math.round(tmp); + + if (changeAngle == null || Math.abs(tmpRound - changeAngle) > 0) { + prevAzimuth = edge.get(orientationEv); + changeAngle = tmpRound; + return true; + } + } + + prevAzimuth = edge.get(orientationEv); + return changeAngle == null; + } +} diff --git a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java index ff3e24ac930..61e976a7288 100644 --- a/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java +++ b/core/src/main/java/com/graphhopper/util/details/ConstantDetailsBuilder.java @@ -21,6 +21,7 @@ import com.graphhopper.coll.MapEntry; import com.graphhopper.util.EdgeIteratorState; +import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -30,6 +31,7 @@ public class ConstantDetailsBuilder extends AbstractPathDetailsBuilder { private final Object value; private boolean firstEdge = true; + private int lastIndex = -1; public ConstantDetailsBuilder(String name, Object value) { super(name); @@ -50,11 +52,22 @@ public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { return false; } + @Override + public void endInterval(int lastIndex) { + this.lastIndex = lastIndex; + super.endInterval(lastIndex); + } + @Override public Map.Entry> build() { - if (firstEdge) + if (firstEdge) { // #2915 if there was no edge at all we need to add a single entry manually here - return new MapEntry<>(getName(), List.of(new PathDetail(value))); + // #3007 we need to set the value but also the (empty) interval (first/last) + PathDetail p = new PathDetail(value); + p.setFirst(lastIndex); + p.setLast(lastIndex); + return new MapEntry<>(getName(), new ArrayList<>(List.of(p))); + } return super.build(); } } diff --git a/core/src/main/java/com/graphhopper/util/details/IntersectionDetails.java b/core/src/main/java/com/graphhopper/util/details/IntersectionDetails.java index a42fe2f949b..447892dfa50 100644 --- a/core/src/main/java/com/graphhopper/util/details/IntersectionDetails.java +++ b/core/src/main/java/com/graphhopper/util/details/IntersectionDetails.java @@ -23,7 +23,11 @@ import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.*; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import static com.graphhopper.util.Parameters.Details.INTERSECTION; @@ -44,7 +48,7 @@ public class IntersectionDetails extends AbstractPathDetailsBuilder { private int fromEdge = -1; - private Map intersectionMap = new HashMap<>(); + private Map intersectionMap = null; private final EdgeExplorer crossingExplorer; private final NodeAccess nodeAccess; @@ -89,25 +93,10 @@ public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { intersectingEdges.add(intersectionValues); } - intersectingEdges.sort(null); - - List bearings = new ArrayList<>(intersectingEdges.size()); - List entries = new ArrayList<>(intersectingEdges.size()); - - for (int i = 0; i < intersectingEdges.size(); i++) { - IntersectionValues intersectionValues = intersectingEdges.get(i); - bearings.add(intersectionValues.bearing); - entries.add(intersectionValues.entry); - if (intersectionValues.in) { - intersectionMap.put("in", i); - } - if (intersectionValues.out) { - intersectionMap.put("out", i); - } - } + intersectingEdges = intersectingEdges.stream(). + sorted((x, y) -> Integer.compare(x.bearing, y.bearing)).collect(Collectors.toList()); - intersectionMap.put("bearings", bearings); - intersectionMap.put("entries", entries); + intersectionMap = IntersectionValues.createIntersection(intersectingEdges); fromEdge = toEdge; return true; @@ -134,20 +123,4 @@ private int edgeId(EdgeIteratorState edge) { public Object getCurrentValue() { return this.intersectionMap; } - - private class IntersectionValues implements Comparable { - - public int bearing; - public boolean entry; - public boolean in; - public boolean out; - - @Override - public int compareTo(Object o) { - if (o instanceof IntersectionValues) { - return Integer.compare(this.bearing, ((IntersectionValues) o).bearing); - } - return 0; - } - } } diff --git a/core/src/main/java/com/graphhopper/util/details/IntersectionValues.java b/core/src/main/java/com/graphhopper/util/details/IntersectionValues.java new file mode 100644 index 00000000000..3340d3b21b5 --- /dev/null +++ b/core/src/main/java/com/graphhopper/util/details/IntersectionValues.java @@ -0,0 +1,82 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.util.details; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class IntersectionValues { + public int bearing; + public boolean entry; + public boolean in; + public boolean out; + + /** + * create a List of IntersectionValues from a PathDetail + */ + public static List createList(Map intersectionMap) { + List list = new ArrayList<>(); + + List bearings = (List) intersectionMap.get("bearings"); + Integer in = (Integer) intersectionMap.getOrDefault("in", -1); + Integer out = (Integer) intersectionMap.getOrDefault("out", -1); + List entry = (List) intersectionMap.get("entries"); + + if (bearings.size() != entry.size()) { + throw new IllegalStateException("Bearings and entry array sizes different"); + } + int numEntries = bearings.size(); + + for (int i = 0; i < numEntries; i++) { + IntersectionValues iv = new IntersectionValues(); + iv.bearing = bearings.get(i); + iv.entry = entry.get(i); + iv.in = (in == i); + iv.out = (out == i); + + list.add(iv); + } + return list; + } + + /** + * create a PathDetail from a List of IntersectionValues + */ + public static Map createIntersection(List list) { + Map intersection = new HashMap<>(); + + intersection.put("bearings", + list.stream().map(x -> x.bearing).collect(Collectors.toList())); + intersection.put("entries", + list.stream().map(x -> x.entry).collect(Collectors.toList())); + + for (int m = 0; m < list.size(); m++) { + IntersectionValues intersectionValues = list.get(m); + if (intersectionValues.in) { + intersection.put("in", m); + } + if (intersectionValues.out) { + intersection.put("out", m); + } + } + return intersection; + } +} diff --git a/core/src/main/java/com/graphhopper/util/details/KVStringDetails.java b/core/src/main/java/com/graphhopper/util/details/KVStringDetails.java index a599df1fde4..d930b40ba1b 100644 --- a/core/src/main/java/com/graphhopper/util/details/KVStringDetails.java +++ b/core/src/main/java/com/graphhopper/util/details/KVStringDetails.java @@ -27,6 +27,7 @@ public class KVStringDetails extends AbstractPathDetailsBuilder { private String curString; + private boolean initial = true; public KVStringDetails(String name) { super(name); @@ -34,13 +35,17 @@ public KVStringDetails(String name) { @Override public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { - if (curString == null) { - curString = (String) edge.getValue(getName()); + String value = (String) edge.getValue(getName()); + if (initial) { + curString = value; + initial = false; return true; - } - String val = (String) edge.getValue(getName()); - if (!curString.equals(val)) { - curString = val; + } else if (curString == null) { + curString = value; + // do not create separate details if value stays null + return value != null; + } else if (!curString.equals(value)) { + curString = value; return true; } return false; diff --git a/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java index 6e456a72876..b14cfb77225 100644 --- a/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java +++ b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java @@ -43,7 +43,16 @@ public List createPathDetailsBuilders(List requested builders.add(new ConstantDetailsBuilder(LEG_DISTANCE, path.getDistance())); if (requestedPathDetails.contains(LEG_WEIGHT)) builders.add(new ConstantDetailsBuilder(LEG_WEIGHT, path.getWeight())); + if (requestedPathDetails.contains(CHANGE_ANGLE)) + builders.add(new ChangeAngleDetails(evl.getDecimalEncodedValue(Orientation.KEY))); + for (String key : requestedPathDetails) { + if (key.endsWith("_conditional")) + builders.add(new KVStringDetails(key)); + } + + if (requestedPathDetails.contains(MOTORWAY_JUNCTION)) + builders.add(new KVStringDetails(MOTORWAY_JUNCTION)); if (requestedPathDetails.contains(STREET_NAME)) builders.add(new KVStringDetails(STREET_NAME)); if (requestedPathDetails.contains(STREET_REF)) @@ -73,7 +82,8 @@ public List createPathDetailsBuilders(List requested builders.add(new IntersectionDetails(graph, weighting)); for (String pathDetail : requestedPathDetails) { - if (!evl.hasEncodedValue(pathDetail)) continue; // path details like "time" won't be found + if (!evl.hasEncodedValue(pathDetail)) + continue; // path details like "time" won't be found EncodedValue ev = evl.getEncodedValue(pathDetail, EncodedValue.class); if (ev instanceof DecimalEncodedValue) @@ -86,7 +96,8 @@ else if (ev instanceof StringEncodedValue) builders.add(new StringDetails(pathDetail, (StringEncodedValue) ev)); else if (ev instanceof IntEncodedValue) builders.add(new IntDetails(pathDetail, (IntEncodedValue) ev)); - else throw new IllegalArgumentException("unknown EncodedValue class " + ev.getClass().getName()); + else + throw new IllegalArgumentException("unknown EncodedValue class " + ev.getClass().getName()); } if (requestedPathDetails.size() > builders.size()) { diff --git a/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java b/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java index 8873b7ad927..4b927383cb6 100644 --- a/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java +++ b/core/src/main/java/com/graphhopper/util/details/PathDetailsFromEdges.java @@ -25,7 +25,6 @@ import com.graphhopper.util.FetchMode; import java.util.*; -import java.util.stream.Collectors; /** * This class calculates a PathDetail list in a similar fashion to the instruction calculation, @@ -59,8 +58,9 @@ public static Map> calcDetails(Path path, EncodedValueL if (!path.isFound() || requestedPathDetails.isEmpty()) return Collections.emptyMap(); HashSet uniquePD = new HashSet<>(requestedPathDetails.size()); - Collection res = requestedPathDetails.stream().filter(pd -> !uniquePD.add(pd)).collect(Collectors.toList()); - if (!res.isEmpty()) throw new IllegalArgumentException("Do not use duplicate path details: " + res); + Collection res = requestedPathDetails.stream().filter(pd -> !uniquePD.add(pd)).toList(); + if (!res.isEmpty()) + throw new IllegalArgumentException("Do not use duplicate path details: " + res); List pathBuilders = pathBuilderFactory.createPathDetailsBuilders(requestedPathDetails, path, evLookup, weighting, graph); if (pathBuilders.isEmpty()) @@ -96,4 +96,4 @@ public void finish() { calc.endInterval(lastIndex); } } -} \ No newline at end of file +} diff --git a/core/src/main/java/com/graphhopper/util/details/WeightDetails.java b/core/src/main/java/com/graphhopper/util/details/WeightDetails.java index 70d33a5ea6c..7af768592dd 100644 --- a/core/src/main/java/com/graphhopper/util/details/WeightDetails.java +++ b/core/src/main/java/com/graphhopper/util/details/WeightDetails.java @@ -32,7 +32,7 @@ public class WeightDetails extends AbstractPathDetailsBuilder { private final Weighting weighting; - private int edgeId = EdgeIterator.NO_EDGE; + private int prevEdgeId = EdgeIterator.NO_EDGE; private Double weight; public WeightDetails(Weighting weighting) { @@ -42,9 +42,9 @@ public WeightDetails(Weighting weighting) { @Override public boolean isEdgeDifferentToLastEdge(EdgeIteratorState edge) { - if (edge.getEdge() != edgeId) { - edgeId = edge.getEdge(); - weight = GHUtility.calcWeightWithTurnWeight(weighting, edge, false, edgeId); + if (edge.getEdge() != prevEdgeId) { + weight = GHUtility.calcWeightWithTurnWeight(weighting, edge, false, prevEdgeId); + prevEdgeId = edge.getEdge(); return true; } return false; diff --git a/core/src/main/java/com/graphhopper/util/shapes/Polygon.java b/core/src/main/java/com/graphhopper/util/shapes/Polygon.java index e58e1137db7..d190394a5b9 100644 --- a/core/src/main/java/com/graphhopper/util/shapes/Polygon.java +++ b/core/src/main/java/com/graphhopper/util/shapes/Polygon.java @@ -25,8 +25,6 @@ import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedPolygon; -import java.util.Arrays; - /** * This class represents a polygon that is defined by a set of points. * Every point i is connected to point i-1 and i+1. diff --git a/core/src/main/resources/com/graphhopper/countries/countries.geojson b/core/src/main/resources/com/graphhopper/countries/countries.geojson index bd2cc5fbfba..a37cd976885 100644 --- a/core/src/main/resources/com/graphhopper/countries/countries.geojson +++ b/core/src/main/resources/com/graphhopper/countries/countries.geojson @@ -1 +1 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"IN-AS"},"geometry":{"type":"Polygon","coordinates":[[[89.71298,26.26755],[89.73581,26.15818],[89.77865,26.08387],[89.77728,26.04254],[89.86592,25.93115],[89.80585,25.82489],[89.84388,25.70042],[89.86129,25.61714],[89.81208,25.37244],[89.89562,25.5635],[90.01922,25.60314],[89.89906,25.73867],[90.10642,25.96113],[90.48065,26.0031],[90.61729,25.87714],[90.95443,25.95001],[91.24969,25.71887],[91.84432,26.10982],[91.94732,25.99755],[92.30438,26.06788],[92.13958,25.69846],[92.6477,25.57465],[92.79464,25.21612],[92.39147,25.01471],[92.49887,24.88796],[92.38626,24.86055],[92.25854,24.9191],[92.15796,24.54435],[92.22953,24.50245],[92.27931,24.39213],[92.21786,24.25259],[92.29819,24.25406],[92.42317,24.25635],[92.44239,24.14299],[92.52685,24.16617],[92.76168,24.52088],[92.8379,24.39588],[93.02329,24.40026],[93.10569,24.81883],[93.17985,24.80169],[93.47236,25.3074],[93.31924,25.53872],[93.51905,25.67433],[93.69964,25.92717],[93.78341,25.97594],[93.79371,25.81472],[93.96537,25.90185],[93.99902,26.16468],[94.27093,26.5658],[94.30801,26.45459],[94.39659,26.52772],[94.40551,26.61922],[94.78248,26.79771],[94.88548,26.92451],[95.19515,27.02915],[95.46295,27.12209],[95.49041,27.2473],[95.89279,27.27294],[95.99647,27.37481],[95.86189,27.43333],[95.88523,27.52836],[95.76335,27.73519],[95.97518,27.9665],[95.63117,27.96105],[95.51994,27.88157],[95.316,27.87125],[94.86968,27.74036],[94.46937,27.5728],[94.216,27.62331],[94.26269,27.52471],[94.15489,27.4839],[93.80882,27.15142],[93.83354,27.07746],[93.68385,26.97838],[93.50257,26.93859],[93.38035,26.96308],[93.03291,26.919],[92.90313,27.00591],[92.64289,27.03894],[92.6441,26.98495],[92.11863,26.893],[92.05523,26.8692],[91.83181,26.87318],[91.50067,26.79223],[90.67715,26.77215],[90.48504,26.8594],[90.39271,26.90704],[90.30402,26.85098],[90.04535,26.72422],[89.86124,26.73307],[89.86885,26.46258],[89.84653,26.40078],[89.71298,26.26755]]]}},{"type":"Feature","properties":{"id":"IN-LD"},"geometry":{"type":"Polygon","coordinates":[[[71.96044,11.88348],[72.15131,7.6285],[74.03744,7.70688],[74.10827,11.89423],[71.96044,11.88348]]]}},{"type":"Feature","properties":{"id":"CN-HE"},"geometry":{"type":"Polygon","coordinates":[[[113.47297,36.6976],[113.59554,36.4616],[113.72909,36.36103],[114.02435,36.33172],[114.61418,36.12345],[114.91287,36.13066],[114.91493,36.04354],[115.19714,36.22544],[115.31936,36.07518],[115.48072,36.15506],[115.27336,36.47927],[115.47866,36.75759],[115.62629,36.79828],[115.83366,37.0475],[115.96755,37.33304],[116.23466,37.35978],[116.21887,37.46177],[116.31912,37.58485],[116.42143,37.46995],[116.79153,37.84015],[117.40745,37.83636],[117.55439,38.06323],[117.69378,38.07296],[117.84484,38.26514],[118.33648,38.49229],[117.59559,38.61472],[117.42736,38.61204],[117.24128,38.56373],[117.21931,38.63189],[117.09297,38.58682],[117.02567,38.69837],[116.87187,38.68658],[116.85539,38.74658],[116.75514,38.74176],[116.70364,38.93751],[116.75926,39.04372],[116.85882,39.05598],[116.91581,39.13112],[116.85539,39.16467],[116.87942,39.34385],[116.81899,39.34491],[116.87049,39.43354],[116.77848,39.46005],[116.80732,39.61415],[116.52065,39.59193],[116.46366,39.52946],[116.43722,39.44494],[116.32942,39.4566],[116.23809,39.51649],[116.22058,39.5787],[115.91365,39.57552],[115.81031,39.50933],[115.74851,39.51251],[115.66268,39.60833],[115.51574,39.6041],[115.47042,39.74177],[115.41755,39.77925],[115.56518,39.81011],[115.42098,39.96449],[115.50338,40.07649],[115.76808,40.15736],[115.8467,40.14633],[115.95382,40.27481],[115.90541,40.35544],[115.857,40.36145],[115.76568,40.44668],[115.77632,40.46196],[115.73272,40.51145],[115.77907,40.56128],[115.81615,40.5575],[115.82267,40.5871],[116.11278,40.62646],[116.24565,40.79067],[116.46537,40.7652],[116.31465,40.93037],[116.47602,40.89586],[116.44306,40.98145],[116.6209,40.97989],[116.62605,41.06485],[116.98379,40.68896],[117.21794,40.69938],[117.30789,40.65199],[117.43972,40.68532],[117.51663,40.65069],[117.47749,40.63167],[117.44419,40.65042],[117.42977,40.61851],[117.41432,40.63701],[117.40882,40.56206],[117.3072,40.57719],[117.24781,40.54902],[117.25227,40.50857],[117.20626,40.50126],[117.25913,40.43701],[117.22824,40.41245],[117.21811,40.36616],[117.29587,40.27612],[117.32814,40.2879],[117.34291,40.23131],[117.5537,40.22607],[117.75558,40.05389],[117.78648,39.96659],[117.53517,39.98711],[117.50427,39.88971],[117.54753,39.7584],[117.58872,39.73993],[117.65945,39.63848],[117.71026,39.52787],[117.76245,39.59828],[117.92861,39.57499],[117.85274,39.38685],[117.79197,39.36668],[117.84656,39.35421],[117.84072,39.32712],[117.96363,39.31331],[118.06079,39.25166],[118.02577,39.21789],[118.35983,38.73266],[119.98931,39.77661],[119.73586,40.11431],[119.58137,40.36799],[119.55253,40.54876],[119.26277,40.53154],[119.12406,40.67647],[118.90296,40.75297],[118.88992,40.9576],[119.01695,40.97523],[118.92631,41.06382],[119.07325,41.08608],[119.24972,41.27264],[119.23562,41.3149],[119.1481,41.29638],[118.79585,41.3598],[118.38729,41.31082],[118.20396,41.62314],[118.11538,41.76567],[118.34335,41.85728],[118.26232,42.08446],[118.18267,42.02838],[117.98973,42.25952],[118.01376,42.39962],[117.7851,42.61678],[117.44453,42.59151],[117.39921,42.46449],[117.00782,42.45994],[116.88697,42.37985],[116.90757,42.18477],[116.78054,42.19902],[116.86981,42.00236],[116.70158,41.93037],[116.30058,41.99675],[116.10763,41.84399],[116.06574,41.77131],[115.91194,41.93804],[115.81787,41.93191],[115.31112,41.67291],[115.33927,41.59541],[114.8545,41.59593],[114.93484,41.85882],[114.84626,42.14456],[114.50637,42.11095],[114.43702,41.9457],[114.23309,41.69547],[114.21455,41.50703],[114.01336,41.52503],[113.85612,41.41338],[113.99208,41.19054],[113.83621,41.08452],[114.06829,40.85485],[114.05834,40.79795],[114.1246,40.74569],[114.28527,40.51327],[114.30519,40.36695],[114.54482,40.3366],[113.89114,40.0192],[114.41093,39.83121],[114.39376,39.60568],[114.5613,39.55276],[114.3402,39.07997],[113.94676,39.09489],[113.75518,38.94499],[113.84101,38.76854],[113.53683,38.50787],[113.55194,38.23871],[113.82797,38.16263],[114.02984,37.72972],[114.127,37.69387],[114.09851,37.58594],[113.74488,37.0738],[113.78917,36.88071],[113.47297,36.6976]]]}},{"type":"Feature","properties":{"id":"IN-AN"},"geometry":{"type":"Polygon","coordinates":[[[91.66991,10.83331],[93.82619,5.95573],[94.98735,6.60903],[94.6395,14.00732],[93.69443,13.6468],[92.61282,13.95915],[91.66991,10.83331]]]}},{"type":"Feature","properties":{"id":"CN-SD"},"geometry":{"type":"Polygon","coordinates":[[[114.82429,34.994],[115.13122,35.00187],[115.21465,34.94814],[115.19439,34.90817],[115.42545,34.8014],[115.45394,34.63659],[115.67916,34.55577],[116.09596,34.60665],[116.19792,34.51759],[116.37405,34.64676],[116.43859,34.89944],[116.96456,34.88367],[117.15957,34.52692],[117.78991,34.51447],[117.93411,34.68404],[118.14559,34.54389],[118.17718,34.36837],[118.42849,34.44655],[118.51226,34.69194],[118.76495,34.73822],[118.88442,35.04349],[119.30259,35.07833],[123.85601,37.49093],[123.90497,38.79949],[120.97044,38.45359],[117.84484,38.26514],[117.69378,38.07296],[117.55439,38.06323],[117.40745,37.83636],[116.79153,37.84015],[116.42143,37.46995],[116.31912,37.58485],[116.21887,37.46177],[116.23466,37.35978],[115.96755,37.33304],[115.83366,37.0475],[115.62629,36.79828],[115.47866,36.75759],[115.27336,36.47927],[115.48072,36.15506],[115.43197,36.01078],[115.35026,35.95021],[115.35644,35.77771],[115.49171,35.92297],[115.64414,35.91936],[116.0939,36.11069],[116.03073,35.963],[115.87005,35.91185],[115.67985,35.76211],[115.41206,35.64111],[115.32966,35.47744],[115.07903,35.40416],[114.91218,35.19962],[114.82429,34.994]]]}},{"type":"Feature","properties":{"id":"IN-CT"},"geometry":{"type":"Polygon","coordinates":[[[80.24825,18.95565],[80.36636,18.83091],[80.27503,18.72104],[80.34782,18.59158],[80.51055,18.62802],[81.05026,17.77615],[81.39221,17.81014],[81.40165,17.88727],[81.44988,17.89707],[81.52679,18.16248],[81.50722,18.19217],[81.54275,18.26864],[81.61468,18.31052],[81.706,18.39916],[81.69158,18.42196],[81.79664,18.477],[81.84402,18.5714],[81.90101,18.64266],[82.16468,18.79094],[82.17945,18.90401],[82.2512,18.91473],[82.06375,19.78221],[81.82617,19.92558],[81.93809,20.09527],[82.34939,19.83776],[82.45513,19.91461],[82.58628,19.86263],[82.58285,19.76444],[82.72258,19.85036],[82.70439,20.00141],[82.39437,20.05302],[82.34252,20.85688],[82.4517,20.82608],[82.67555,21.16008],[83.1792,21.11044],[83.27499,21.3722],[83.40236,21.33958],[83.3385,21.50226],[83.46828,21.72569],[83.48167,21.81242],[83.58844,21.84078],[83.54347,22.05159],[83.65436,22.22967],[84.00421,22.37261],[84.04129,22.46005],[83.99425,22.53602],[84.40177,22.89325],[84.3695,22.97704],[84.14909,22.97546],[84.02961,23.16592],[84.06978,23.33059],[83.97039,23.37424],[84.02652,23.63068],[83.78929,23.58853],[83.71564,23.68587],[83.69487,23.82209],[83.55342,23.8783],[83.4185,24.08596],[83.32134,24.10131],[83.15963,23.90467],[82.95226,23.87642],[82.80876,23.96492],[82.65666,23.90216],[82.49599,23.7844],[81.90685,23.85569],[81.79218,23.80764],[81.72214,23.83999],[81.66274,23.92821],[81.59614,23.88834],[81.68712,23.71809],[81.57091,23.58113],[81.62567,23.48875],[81.75338,23.56619],[81.94942,23.49347],[82.03886,23.38433],[82.19361,23.32066],[82.13687,23.16608],[82.16245,23.15077],[82.1149,23.10247],[81.93886,23.07823],[81.91543,22.94701],[81.75991,22.85086],[81.76849,22.65108],[81.39633,22.45291],[81.35684,22.52175],[81.0918,22.43102],[80.98571,22.03441],[80.90606,22.13112],[80.80203,21.74036],[80.65784,21.32967],[80.64308,21.25418],[80.42129,21.09923],[80.46935,20.92681],[80.54214,20.92681],[80.5799,20.6694],[80.60806,20.32273],[80.38284,20.23256],[80.54488,20.07528],[80.51467,19.92687],[80.8937,19.47306],[80.72341,19.27355],[80.55587,19.40313],[80.24825,18.95565]]]}},{"type":"Feature","properties":{"id":"CN-XZ"},"geometry":{"type":"Polygon","coordinates":[[[78.38897,32.53938],[78.4645,32.45367],[78.49609,32.2762],[78.68754,32.10256],[78.74404,32.00384],[78.78036,31.99478],[78.69933,31.78723],[78.84516,31.60631],[78.71032,31.50197],[78.77898,31.31209],[79.01931,31.42817],[79.14016,31.43403],[79.22805,31.34963],[79.59884,30.93943],[79.93255,30.88288],[80.20721,30.58541],[80.54504,30.44936],[80.83343,30.32023],[81.03953,30.20059],[81.12842,30.01395],[81.24362,30.0126],[81.29032,30.08806],[81.2623,30.14596],[81.33355,30.15303],[81.39928,30.21862],[81.41018,30.42153],[81.62033,30.44703],[81.99082,30.33423],[82.10135,30.35439],[82.10757,30.23745],[82.19475,30.16884],[82.16984,30.0692],[82.38622,30.02608],[82.5341,29.9735],[82.73024,29.81695],[83.07116,29.61957],[83.28131,29.56813],[83.44787,29.30513],[83.63156,29.16249],[83.82303,29.30513],[83.97559,29.33091],[84.18107,29.23451],[84.24801,29.02783],[84.2231,28.89571],[84.47528,28.74023],[84.62317,28.73887],[84.85511,28.58041],[85.06059,28.68562],[85.19135,28.62825],[85.18668,28.54076],[85.10729,28.34092],[85.38127,28.28336],[85.4233,28.32996],[85.59765,28.30529],[85.60854,28.25045],[85.69105,28.38475],[85.71907,28.38064],[85.74864,28.23126],[85.84672,28.18187],[85.90743,28.05144],[85.97813,27.99023],[85.94946,27.9401],[86.06309,27.90021],[86.12069,27.93047],[86.08333,28.02121],[86.088,28.09264],[86.18607,28.17364],[86.22966,27.9786],[86.42736,27.91122],[86.51609,27.96623],[86.56265,28.09569],[86.74181,28.10638],[86.75582,28.04182],[87.03757,27.94835],[87.11696,27.84104],[87.56996,27.84517],[87.72718,27.80938],[87.82681,27.95248],[88.13378,27.88015],[88.1278,27.95417],[88.25332,27.9478],[88.54858,28.06057],[88.63235,28.12356],[88.83559,28.01936],[88.88091,27.85192],[88.77517,27.45415],[88.82981,27.38814],[88.91901,27.32483],[88.93678,27.33777],[88.96947,27.30319],[89.00216,27.32532],[88.95355,27.4106],[88.97213,27.51671],[89.0582,27.60985],[89.12825,27.62502],[89.59525,28.16433],[89.79762,28.23979],[90.13387,28.19178],[90.58842,28.02838],[90.69894,28.07784],[91.20019,27.98715],[91.25779,28.07509],[91.46327,28.0064],[91.48973,27.93903],[91.5629,27.84823],[91.6469,27.76358],[91.84722,27.76325],[91.87057,27.7195],[92.27432,27.89077],[92.32101,27.79363],[92.42538,27.80092],[92.7275,27.98662],[92.73025,28.05814],[92.65472,28.07632],[92.67486,28.15018],[92.93075,28.25671],[93.14635,28.37035],[93.18069,28.50319],[93.44621,28.67189],[93.72797,28.68821],[94.35897,29.01965],[94.2752,29.11687],[94.69318,29.31739],[94.81353,29.17804],[95.0978,29.14446],[95.11291,29.09527],[95.2214,29.10727],[95.26122,29.07727],[95.3038,29.13847],[95.41091,29.13007],[95.50842,29.13487],[95.72086,29.20797],[95.75149,29.32063],[95.84899,29.31464],[96.05361,29.38167],[96.31316,29.18643],[96.18682,29.11087],[96.20467,29.02325],[96.3626,29.10607],[96.61391,28.72742],[96.40929,28.51526],[96.48895,28.42955],[96.6455,28.61657],[96.85561,28.4875],[96.88445,28.39452],[96.98882,28.32564],[97.1289,28.3619],[97.34547,28.21385],[97.41729,28.29783],[97.47085,28.2688],[97.50518,28.49716],[97.56835,28.55628],[97.70705,28.5056],[97.79632,28.33168],[97.90069,28.3776],[98.15337,28.12114],[98.16696,28.21002],[98.26789,28.24421],[98.20129,28.35666],[98.28987,28.39804],[98.36952,28.26084],[98.39355,28.10953],[98.60881,28.1725],[98.69361,28.21789],[98.75884,28.33218],[98.63697,28.49072],[98.5968,28.68622],[98.63113,28.69103],[98.68125,28.73319],[98.66254,28.79549],[98.65447,28.8585],[98.62495,28.9721],[98.78974,29.01054],[98.83197,28.80406],[98.97548,28.82933],[98.97376,28.87564],[98.91815,28.88796],[98.92501,28.98111],[99.0184,29.03425],[98.96003,29.18663],[99.11487,29.22679],[99.05479,29.30586],[99.06234,29.45051],[98.98956,29.66359],[99.01119,29.8189],[99.05651,29.93708],[99.0438,30.07979],[98.99642,30.15344],[98.90613,30.68457],[98.95523,30.74862],[98.78528,30.92431],[98.80725,30.98496],[98.75404,31.03293],[98.71009,31.11997],[98.60469,31.18725],[98.63559,31.33839],[98.69052,31.33751],[98.77567,31.24949],[98.88896,31.37767],[98.84433,31.42954],[98.71971,31.50626],[98.56246,31.67705],[98.409,31.83089],[98.43475,32.00458],[98.30497,32.12619],[98.21639,32.33936],[97.99255,32.46748],[97.72544,32.52886],[97.66399,32.47704],[97.37663,32.5306],[97.30865,32.07501],[97.22557,32.10962],[97.16583,32.03049],[97.00309,32.06628],[96.9564,31.99351],[96.725,32.02612],[96.8431,31.7083],[96.5657,31.7194],[96.37069,31.85539],[96.33636,31.95682],[96.24435,31.9341],[96.13929,31.82623],[96.21963,31.76145],[96.25053,31.55396],[96.20109,31.53816],[96.14822,31.69078],[95.7891,31.75327],[95.61881,31.77896],[95.51032,31.74685],[95.22571,32.3872],[94.91981,32.413],[94.61254,32.67001],[94.14733,32.43561],[93.72161,32.57343],[93.48953,32.4947],[93.02398,32.73646],[92.22335,32.72144],[92.20275,32.88766],[91.97891,32.86113],[91.51405,33.11339],[90.70175,33.13985],[90.51567,33.2651],[90.38177,33.2605],[90.24993,33.42857],[89.99656,33.55913],[89.93751,33.80083],[89.63882,34.04583],[89.87708,34.2277],[89.73358,34.65862],[89.81941,34.90395],[89.57908,34.90113],[89.45274,35.22991],[89.68482,35.42207],[89.80224,35.859],[89.42802,35.91602],[89.40811,36.01522],[89.691,36.0935],[88.94119,36.35716],[88.76438,36.29077],[88.53332,36.48755],[86.2619,36.19995],[86.09298,35.8679],[85.57662,35.64055],[85.26489,35.80333],[84.19784,35.35881],[83.1253,35.39688],[82.966,35.62716],[82.45238,35.7309],[82.01568,35.34201],[81.66961,35.24337],[80.45287,35.45172],[79.83283,34.48958],[79.05418,34.4154],[79.05364,34.32482],[78.99802,34.3027],[78.91769,34.15452],[78.66225,34.08858],[78.65657,34.03195],[78.73367,34.01121],[78.77349,33.73871],[78.67599,33.66445],[78.73636,33.56521],[79.15252,33.17156],[79.14016,33.02545],[79.46562,32.69668],[79.26768,32.53277],[79.13174,32.47766],[79.0979,32.38051],[78.99322,32.37948],[78.96713,32.33655],[78.7831,32.46873],[78.73916,32.69438],[78.38897,32.53938]]]}},{"type":"Feature","properties":{"id":"CN-HI"},"geometry":{"type":"Polygon","coordinates":[[[107.44022,18.66249],[110.2534,15.19951],[112.88221,15.61902],[111.04979,20.2622],[108.26073,20.07614],[107.44022,18.66249]]]}},{"type":"Feature","properties":{"id":"IN-DN"},"geometry":{"type":"Polygon","coordinates":[[[72.92346,20.26348],[72.9808,20.21323],[72.98492,20.11784],[73.19675,20.05625],[73.23383,20.14266],[73.171,20.19744],[73.06354,20.18487],[73.06766,20.21806],[73.18508,20.29407],[73.12156,20.36909],[72.94578,20.35331],[72.92346,20.26348]]]}},{"type":"Feature","properties":{"id":"CN-NX"},"geometry":{"type":"Polygon","coordinates":[[[104.29321,37.4367],[105.18997,36.95208],[105.51544,36.0996],[105.32592,35.99911],[105.48454,35.72756],[106.05857,35.48639],[106.36344,35.23889],[106.4994,35.35993],[106.44241,35.69466],[106.921,35.76824],[106.9313,36.12456],[106.49047,36.30848],[106.66076,37.19751],[107.30346,37.0979],[107.2705,37.47921],[107.66139,37.87999],[107.1627,38.16155],[106.47743,38.31903],[106.95396,38.94819],[106.78161,39.37518],[106.59484,39.36934],[106.13479,39.15615],[105.85121,38.62116],[105.8258,38.34219],[105.80039,37.94149],[105.76606,37.79513],[105.10619,37.63707],[104.99084,37.5424],[104.29321,37.4367]]]}},{"type":"Feature","properties":{"id":"IN-MP"},"geometry":{"type":"Polygon","coordinates":[[[74.05677,22.48115],[74.27787,22.38373],[74.08149,22.35896],[74.07909,22.24652],[74.17282,22.06846],[74.14947,21.95323],[74.43717,22.03282],[74.52609,21.90769],[74.51408,21.72314],[74.64317,21.65583],[74.91783,21.63062],[75.07575,21.55209],[75.11455,21.45306],[75.22304,21.40928],[75.96324,21.39618],[76.15653,21.2625],[76.17301,21.08386],[76.38656,21.07809],[76.65847,21.28201],[76.80198,21.59519],[76.90429,21.60349],[77.07733,21.72474],[77.23422,21.7158],[77.49893,21.76332],[77.56484,21.38019],[78.00842,21.41887],[78.37989,21.62296],[78.43654,21.50099],[78.9347,21.49268],[78.91136,21.59295],[79.14585,21.62583],[79.33021,21.70719],[79.4914,21.67306],[79.53861,21.54634],[79.75662,21.60062],[79.91489,21.52207],[79.95025,21.5572],[80.06732,21.55528],[80.13427,21.61115],[80.20946,21.6354],[80.28499,21.59742],[80.38696,21.49619],[80.40824,21.3738],[80.56377,21.36037],[80.65784,21.32967],[80.80203,21.74036],[80.90606,22.13112],[80.98571,22.03441],[81.0918,22.43102],[81.35684,22.52175],[81.39633,22.45291],[81.76849,22.65108],[81.75991,22.85086],[81.91543,22.94701],[81.93886,23.07823],[82.1149,23.10247],[82.16245,23.15077],[82.13687,23.16608],[82.19361,23.32066],[82.03886,23.38433],[81.94942,23.49347],[81.75338,23.56619],[81.62567,23.48875],[81.57091,23.58113],[81.68712,23.71809],[81.59614,23.88834],[81.66274,23.92821],[81.72214,23.83999],[81.79218,23.80764],[81.90685,23.85569],[82.49599,23.7844],[82.65666,23.90216],[82.80876,23.96492],[82.79708,24.00319],[82.70267,24.09693],[82.66216,24.12513],[82.6752,24.16695],[82.71915,24.13876],[82.77854,24.29312],[82.76618,24.3754],[82.71606,24.37305],[82.71846,24.52713],[82.80361,24.55274],[82.69374,24.69755],[82.55195,24.65575],[82.41634,24.70504],[82.42561,24.59458],[82.30854,24.60831],[82.20691,24.78392],[81.95869,24.83301],[81.90101,24.88768],[81.90376,24.99943],[81.79389,25.00783],[81.73587,25.05574],[81.69605,25.03863],[81.56112,25.19748],[81.4904,25.07938],[81.27822,25.16703],[81.22741,24.95431],[80.80375,24.94154],[80.84426,24.99788],[80.88958,25.19375],[80.71723,25.14342],[80.77972,25.05823],[80.61561,25.10425],[80.25649,25.02401],[80.41648,25.1633],[80.29014,25.42281],[79.86854,25.23786],[79.86442,25.1086],[79.39269,25.11731],[79.47303,25.27512],[79.2794,25.35395],[79.31373,25.14404],[79.04182,25.1459],[78.92681,25.56753],[78.73146,25.47086],[78.81214,25.43056],[78.75789,25.33968],[78.66588,25.38807],[78.71051,25.45102],[78.56975,25.39676],[78.57868,25.34961],[78.52306,25.36419],[78.5464,25.3133],[78.42109,25.28164],[78.55464,25.26984],[78.63395,25.0887],[78.65558,24.91197],[78.77471,24.86027],[78.75686,24.60519],[78.87634,24.64483],[78.98483,24.44527],[78.9759,24.35397],[78.80767,24.15677],[78.51516,24.39025],[78.38882,24.26511],[78.2666,24.45277],[78.28033,24.5671],[78.16635,24.87647],[78.30642,24.97454],[78.34178,25.08155],[78.44306,25.12787],[78.30642,25.3707],[78.44066,25.56133],[78.52752,25.57031],[78.82003,25.6375],[78.74914,25.73589],[78.88458,25.90864],[79.12902,26.32665],[79.01779,26.63518],[78.74999,26.77994],[78.21029,26.82407],[76.91493,26.09533],[76.79408,25.94353],[76.59187,25.87745],[76.48544,25.71795],[76.52046,25.5319],[76.59942,25.39304],[76.68079,25.34309],[76.83734,25.32944],[76.93691,25.28071],[77.0763,25.33813],[77.19646,25.30647],[77.27508,25.42746],[77.35645,25.42932],[77.35507,25.2776],[77.41069,25.22109],[77.39559,25.11979],[77.31044,25.07938],[77.27439,25.11482],[77.00248,25.07316],[76.85554,25.03646],[76.94274,24.86401],[76.78481,24.83098],[76.85211,24.74745],[76.95304,24.75805],[77.06737,24.64639],[77.05879,24.52838],[76.94789,24.4509],[76.91802,24.54181],[76.82224,24.544],[76.83494,24.3718],[76.94343,24.20751],[76.91768,24.13735],[76.35875,24.25103],[76.21215,24.21283],[76.18846,24.33364],[76.0889,24.08564],[75.96324,24.02357],[75.98384,23.93574],[75.87879,23.88489],[75.73596,23.89651],[75.6879,23.7602],[75.45993,23.9144],[75.53203,24.0659],[75.70953,23.96617],[75.84377,24.10257],[75.7418,24.13766],[75.82076,24.24727],[75.73081,24.38118],[75.78987,24.47183],[75.88737,24.42401],[75.92101,24.51838],[75.78712,24.7699],[75.62919,24.68632],[75.58593,24.72562],[75.46989,24.68944],[75.18527,24.75057],[75.42629,24.8855],[75.30715,24.90138],[75.35659,25.03397],[75.17291,25.05356],[75.11352,24.88986],[75.03078,24.84563],[74.83543,24.98294],[74.84607,24.81135],[75.03318,24.75431],[74.90066,24.65107],[74.80522,24.79483],[74.80659,24.67322],[74.74616,24.539],[74.86907,24.48996],[74.90375,24.46058],[74.81002,24.41714],[74.75269,24.27576],[74.87182,24.27607],[74.91371,24.24226],[74.88143,24.21816],[74.98958,24.03235],[74.90924,23.86166],[74.93877,23.6376],[74.78702,23.54321],[74.61124,23.45694],[74.53605,23.30095],[74.69707,23.27572],[74.75063,23.22841],[74.53502,23.09868],[74.39769,23.11004],[74.3201,23.0573],[74.47769,22.86004],[74.36714,22.62858],[74.26929,22.64633],[74.05677,22.48115]]]}},{"type":"Feature","properties":{"id":"CN-JX"},"geometry":{"type":"Polygon","coordinates":[[[113.5976,27.42114],[113.62232,27.41017],[113.62232,27.34859],[113.875,27.38548],[113.77715,27.11781],[113.91517,26.94716],[113.83895,26.79342],[113.86402,26.65513],[113.91208,26.61277],[114.104,26.57624],[114.07482,26.40847],[113.94058,26.18193],[114.23789,26.20042],[114.02606,26.021],[114.00787,25.89258],[113.91448,25.69908],[113.98315,25.40916],[114.00924,25.28319],[114.19464,25.29685],[114.5565,25.42281],[114.74739,25.13036],[114.57092,25.08746],[114.17541,24.6545],[114.35462,24.58865],[114.4178,24.48464],[114.72576,24.6055],[114.8442,24.58178],[114.85897,24.55805],[114.9266,24.67478],[114.93415,24.6467],[115.06599,24.70753],[115.10822,24.66792],[115.37738,24.76803],[115.46699,24.76584],[115.56381,24.62954],[115.65444,24.61799],[115.69358,24.54056],[115.84327,24.5671],[115.78559,24.63703],[115.80379,24.69131],[115.76499,24.71221],[115.76362,24.79109],[115.82405,24.91539],[115.86696,24.86743],[115.89889,24.87833],[115.88859,24.94123],[115.8625,25.22544],[116.00601,25.32789],[116.14265,25.87899],[116.36444,25.971],[116.50039,26.15728],[116.38572,26.23368],[116.64321,26.49024],[116.51412,26.70145],[116.60476,26.92513],[117.05451,27.1062],[117.16918,27.2943],[117.01469,27.66163],[117.31269,27.76801],[117.28523,27.87671],[117.56332,27.96408],[117.67249,27.82268],[117.7518,27.83027],[117.85274,27.94891],[118.08792,27.99167],[118.16413,28.05743],[118.35296,28.09409],[118.36875,28.19369],[118.31245,28.22878],[118.42121,28.29239],[118.48308,28.32856],[118.43364,28.41193],[118.47381,28.47925],[118.40343,28.57457],[118.4254,28.68486],[118.3509,28.8164],[118.25134,28.92523],[118.0323,29.10057],[118.07281,29.2852],[118.20052,29.38178],[118.1195,29.42853],[118.12705,29.53523],[117.69584,29.55852],[117.45483,29.69222],[117.24952,29.89899],[117.06893,29.82634],[117.09091,29.69759],[116.70913,29.58301],[116.64939,29.70117],[116.91993,29.94541],[116.74278,30.06077],[116.57936,30.04769],[116.52992,29.89899],[116.12823,29.82754],[115.93803,29.71906],[115.65994,29.85851],[115.50064,29.8323],[115.40107,29.67492],[115.26786,29.63674],[115.11199,29.68029],[115.16693,29.50176],[114.95063,29.55852],[114.88128,29.38397],[114.25231,29.31993],[114.23034,29.2193],[114.0525,29.20761],[113.9447,29.05196],[114.01817,28.89758],[114.14932,28.77187],[114.07653,28.55919],[114.25094,28.38233],[113.99963,28.15253],[114.03945,28.06289],[113.74351,27.9477],[113.72085,27.8755],[113.75656,27.81782],[113.59863,27.63365],[113.5976,27.42114]]]}},{"type":"Feature","properties":{"id":"IN-SK"},"geometry":{"type":"Polygon","coordinates":[[[88.01646,27.21612],[88.04932,27.21631],[88.08331,27.16501],[88.08563,27.14072],[88.22656,27.11842],[88.30226,27.12682],[88.33505,27.10176],[88.45161,27.07655],[88.52045,27.17753],[88.62842,27.1731],[88.74219,27.144],[88.91901,27.32483],[88.82981,27.38814],[88.77517,27.45415],[88.88091,27.85192],[88.83559,28.01936],[88.63235,28.12356],[88.54858,28.06057],[88.25332,27.9478],[88.1278,27.95417],[88.13378,27.88015],[88.1973,27.85067],[88.19107,27.79285],[88.04008,27.49223],[88.07277,27.43007],[88.01646,27.21612]]]}},{"type":"Feature","properties":{"id":"IN-CH"},"geometry":{"type":"Polygon","coordinates":[[[76.69014,30.74699],[76.71306,30.7419],[76.71967,30.73202],[76.73014,30.72479],[76.73894,30.70136],[76.7513,30.68512],[76.76095,30.68619],[76.78945,30.67054],[76.8025,30.67527],[76.81468,30.68693],[76.82825,30.76411],[76.79254,30.76647],[76.80413,30.779],[76.79632,30.78623],[76.78104,30.77392],[76.76911,30.77683],[76.777,30.78483],[76.75975,30.79928],[76.73864,30.7908],[76.72285,30.77067],[76.70808,30.77045],[76.69066,30.75968],[76.69014,30.74699]]]}},{"type":"Feature","properties":{"id":"IN-NL"},"geometry":{"type":"Polygon","coordinates":[[[93.31924,25.53872],[93.47236,25.3074],[93.61175,25.19748],[93.86993,25.55854],[94.33685,25.50588],[94.58953,25.69289],[94.55932,25.51084],[94.68032,25.47003],[94.80117,25.49359],[95.18556,26.07338],[95.11428,26.1019],[95.12801,26.38397],[95.05798,26.45408],[95.23513,26.68499],[95.19515,27.02915],[94.88548,26.92451],[94.78248,26.79771],[94.40551,26.61922],[94.39659,26.52772],[94.30801,26.45459],[94.27093,26.5658],[93.99902,26.16468],[93.96537,25.90185],[93.79371,25.81472],[93.78341,25.97594],[93.69964,25.92717],[93.51905,25.67433],[93.31924,25.53872]]]}},{"type":"Feature","properties":{"id":"TT"},"geometry":{"type":"Polygon","coordinates":[[[-62.08693,10.04435],[-60.89962,9.81445],[-60.07172,11.77667],[-61.62505,11.18974],[-62.08693,10.04435]]]}},{"type":"Feature","properties":{"id":"AI"},"geometry":{"type":"Polygon","coordinates":[[[-63.95092,18.07976],[-63.35989,18.06012],[-62.86666,18.19278],[-62.75637,18.13489],[-62.64209,18.3662],[-63.3414,18.89029],[-63.90607,18.93262],[-63.95092,18.07976]]]}},{"type":"Feature","properties":{"id":"SX"},"geometry":{"type":"Polygon","coordinates":[[[-63.33064,17.9615],[-63.29212,17.90532],[-63.07669,17.79659],[-62.93924,18.02904],[-63.02323,18.05757],[-63.04039,18.05619],[-63.0579,18.06614],[-63.07759,18.04943],[-63.09686,18.04608],[-63.11096,18.05368],[-63.13584,18.0541],[-63.33064,17.9615]]]}},{"type":"Feature","properties":{"id":"VC"},"geometry":{"type":"Polygon","coordinates":[[[-61.73897,12.61191],[-61.38256,12.52991],[-61.13395,12.51526],[-60.70539,13.41452],[-61.43129,13.68336],[-61.73897,12.61191]]]}},{"type":"Feature","properties":{"id":"CW"},"geometry":{"type":"Polygon","coordinates":[[[-69.5195,12.75292],[-69.4514,12.18025],[-68.99639,11.79035],[-68.33524,11.78151],[-68.90012,12.62309],[-69.5195,12.75292]]]}},{"type":"Feature","properties":{"id":"CN-HL"},"geometry":{"type":"Polygon","coordinates":[[[121.20391,52.57468],[121.87133,52.27152],[122.19817,52.50786],[122.7365,52.20255],[122.96447,51.31173],[124.27871,51.304],[125.31005,51.62824],[126.06674,50.96621],[125.78521,50.7408],[125.78933,50.53263],[125.51467,50.39626],[125.18989,49.93442],[125.25649,49.32691],[125.2153,49.23777],[125.10681,49.12377],[124.86099,49.17945],[124.67834,48.83037],[124.61036,48.74894],[124.53552,48.46928],[124.50118,48.12485],[124.26086,48.52933],[123.55361,48.03493],[122.84637,47.67648],[122.3973,47.34626],[122.69393,47.08041],[123.00842,46.72385],[123.46778,46.96291],[123.59275,46.69042],[123.00498,46.56877],[123.17046,46.2283],[123.90655,46.28812],[124.13452,45.61692],[124.55612,45.41002],[125.68771,45.50346],[126.00768,45.12296],[126.65313,45.23621],[127.08846,44.93369],[127.03765,44.59242],[127.53753,44.55525],[127.71331,44.03429],[128.43429,44.50825],[128.88198,43.50374],[129.94903,44.06193],[130.07537,43.8048],[130.38574,44.03824],[130.44616,43.63905],[131.29402,43.46695],[131.19492,43.53047],[131.21105,43.82383],[131.26176,43.94011],[131.23583,43.96085],[131.25484,44.03131],[131.30365,44.04262],[131.1108,44.70266],[130.95639,44.85154],[131.48415,44.99513],[131.68466,45.12374],[131.66852,45.2196],[131.76532,45.22609],[131.86903,45.33636],[131.99417,45.2567],[132.83978,45.05916],[132.96373,45.0212],[133.12293,45.1332],[133.09279,45.25693],[133.19419,45.51913],[133.41083,45.57723],[133.48457,45.86203],[133.60442,45.90053],[133.67569,45.9759],[133.72695,46.05576],[133.68047,46.14697],[133.88097,46.25066],[133.91496,46.4274],[133.84104,46.46681],[134.03538,46.75668],[134.20016,47.33458],[134.50898,47.4812],[134.7671,47.72051],[134.55508,47.98651],[134.67098,48.1564],[134.75328,48.36763],[134.49516,48.42884],[132.66989,47.96491],[132.57309,47.71741],[131.90448,47.68011],[131.2635,47.73325],[131.09871,47.6852],[130.95985,47.6957],[130.90915,47.90623],[130.65103,48.10052],[130.84462,48.30942],[130.52147,48.61745],[130.66946,48.88251],[130.43232,48.90844],[130.2355,48.86741],[129.85416,49.11067],[129.67598,49.29596],[129.50685,49.42398],[129.40398,49.44194],[129.35317,49.3481],[129.23232,49.40353],[129.11153,49.36813],[128.72896,49.58676],[127.83476,49.5748],[127.53516,49.84306],[127.49299,50.01251],[127.60515,50.23503],[127.37384,50.28393],[127.36009,50.43787],[127.28765,50.46585],[127.36335,50.58306],[127.28165,50.72075],[127.14586,50.91152],[126.93135,51.0841],[126.90369,51.3238],[126.68349,51.70607],[126.44606,51.98254],[126.558,52.13738],[125.6131,53.07229],[125.17522,53.20225],[124.46078,53.21881],[123.86158,53.49391],[123.26989,53.54843],[122.85966,53.47395],[122.35063,53.49565],[121.39213,53.31888],[121.82738,53.03956],[121.20391,52.57468]]]}},{"type":"Feature","properties":{"id":"HT"},"geometry":{"type":"Polygon","coordinates":[[[-74.76465,18.06252],[-72.29523,17.48026],[-71.75671,18.03456],[-71.73783,18.07177],[-71.74994,18.11115],[-71.75465,18.14405],[-71.78271,18.18302],[-71.69952,18.34101],[-71.90875,18.45821],[-71.88102,18.50125],[-72.00201,18.62312],[-71.95412,18.64939],[-71.82556,18.62551],[-71.71885,18.78423],[-71.72624,18.87802],[-71.77766,18.95007],[-71.88102,18.95007],[-71.74088,19.0437],[-71.71088,19.08353],[-71.69938,19.10916],[-71.65337,19.11759],[-71.62642,19.21212],[-71.73229,19.26686],[-71.77766,19.33823],[-71.69448,19.37866],[-71.6802,19.45008],[-71.71268,19.53374],[-71.71449,19.55364],[-71.7429,19.58445],[-71.75865,19.70231],[-71.77419,19.73128],[-72.17094,20.08703],[-72.94479,20.79216],[-73.62304,20.6935],[-73.98196,19.51903],[-74.7289,18.71009],[-74.76465,18.06252]]]}},{"type":"Feature","properties":{"id":"KY"},"geometry":{"type":"Polygon","coordinates":[[[-81.81969,19.51758],[-81.52417,18.7521],[-79.33932,19.50496],[-79.63484,20.26689],[-81.81969,19.51758]]]}},{"type":"Feature","properties":{"id":"CU"},"geometry":{"type":"Polygon","coordinates":[[[-85.9092,21.8218],[-85.29304,20.76169],[-80.28428,20.93037],[-78.19353,19.33462],[-73.98196,19.51903],[-73.62304,20.6935],[-80.16442,23.44484],[-82.02215,24.23074],[-85.9092,21.8218]]]}},{"type":"Feature","properties":{"id":"TC"},"geometry":{"type":"Polygon","coordinates":[[[-72.94479,20.79216],[-71.13087,20.98822],[-70.63262,21.53631],[-72.41726,22.40371],[-72.94479,20.79216]]]}},{"type":"Feature","properties":{"id":"MT"},"geometry":{"type":"Polygon","coordinates":[[[13.4634,35.88474],[14.74801,35.36688],[15.10171,36.26215],[14.02721,36.53141],[13.4634,35.88474]]]}},{"type":"Feature","properties":{"id":"GR"},"geometry":{"type":"Polygon","coordinates":[[[19.0384,40.35325],[19.20409,39.7532],[22.5213,33.45682],[29.73302,35.92555],[29.69611,36.10365],[29.61805,36.14179],[29.61002,36.1731],[29.48192,36.18377],[29.30783,36.01033],[28.23708,36.56812],[27.95037,36.46155],[27.89482,36.69898],[27.46117,36.53789],[27.24613,36.71622],[27.45627,36.9008],[27.20312,36.94571],[27.14757,37.32],[26.95583,37.64989],[26.99377,37.69034],[27.16428,37.72343],[27.05537,37.9131],[26.21136,38.17558],[26.24183,38.44695],[26.32173,38.48731],[26.21136,38.65436],[26.61814,38.81372],[26.70773,39.0312],[26.43357,39.43096],[25.94257,39.39358],[25.61285,40.17161],[26.04292,40.3958],[25.94795,40.72797],[26.03489,40.73051],[26.0754,40.72772],[26.08638,40.73214],[26.12495,40.74283],[26.12854,40.77339],[26.15685,40.80709],[26.21351,40.83298],[26.20856,40.86048],[26.26169,40.9168],[26.29441,40.89119],[26.28623,40.93005],[26.32259,40.94042],[26.35894,40.94292],[26.33297,40.98388],[26.3606,41.02027],[26.31928,41.07386],[26.32259,41.24929],[26.39861,41.25053],[26.5209,41.33993],[26.5837,41.32131],[26.62997,41.34613],[26.61767,41.42281],[26.59742,41.48058],[26.59196,41.60491],[26.5209,41.62592],[26.47958,41.67037],[26.35957,41.71149],[26.30255,41.70925],[26.2654,41.71544],[26.22888,41.74139],[26.21325,41.73223],[26.16841,41.74858],[26.06148,41.70345],[26.07083,41.64584],[26.15146,41.60828],[26.14328,41.55496],[26.17951,41.55409],[26.176,41.50072],[26.14796,41.47533],[26.20288,41.43943],[26.16548,41.42278],[26.12926,41.35878],[25.87919,41.30526],[25.8266,41.34563],[25.70507,41.29209],[25.66183,41.31316],[25.61042,41.30614],[25.55082,41.31667],[25.52394,41.2798],[25.48187,41.28506],[25.28322,41.23411],[25.11611,41.34212],[24.942,41.38685],[24.90928,41.40876],[24.86136,41.39298],[24.82514,41.4035],[24.8041,41.34913],[24.71529,41.41928],[24.61129,41.42278],[24.52599,41.56808],[24.30513,41.51297],[24.27124,41.57682],[24.18126,41.51735],[24.10063,41.54796],[24.06323,41.53222],[24.06908,41.46132],[23.96975,41.44118],[23.91483,41.47971],[23.89613,41.45257],[23.80148,41.43943],[23.76525,41.40175],[23.67644,41.41139],[23.63203,41.37632],[23.52453,41.40262],[23.40416,41.39999],[23.33639,41.36317],[23.31301,41.40525],[23.22771,41.37106],[23.21953,41.33773],[23.1833,41.31755],[22.93334,41.34104],[22.81199,41.3398],[22.76408,41.32225],[22.74538,41.16321],[22.71266,41.13945],[22.65306,41.18168],[22.62852,41.14385],[22.58295,41.11568],[22.5549,41.13065],[22.42285,41.11921],[22.26744,41.16409],[22.17629,41.15969],[22.1424,41.12449],[22.06527,41.15617],[21.90869,41.09191],[21.91102,41.04786],[21.7556,40.92525],[21.69601,40.9429],[21.57448,40.86076],[21.53007,40.90759],[21.41555,40.9173],[21.35595,40.87578],[21.25779,40.86165],[21.21105,40.8855],[21.15262,40.85546],[20.97887,40.85475],[20.98396,40.79109],[20.95752,40.76982],[20.98134,40.76046],[21.05833,40.66586],[21.03932,40.56299],[20.96908,40.51526],[20.94925,40.46625],[20.83688,40.47882],[20.7906,40.42726],[20.78234,40.35803],[20.71789,40.27739],[20.67162,40.09433],[20.62566,40.0897],[20.61081,40.07866],[20.55593,40.06524],[20.51297,40.08168],[20.48487,40.06271],[20.42373,40.06777],[20.37911,39.99058],[20.31135,39.99438],[20.41546,39.82832],[20.41475,39.81437],[20.38572,39.78516],[20.30804,39.81563],[20.29152,39.80421],[20.31961,39.72799],[20.27412,39.69884],[20.22707,39.67459],[20.22376,39.64532],[20.15988,39.652],[20.12956,39.65805],[20.05189,39.69112],[20.00957,39.69227],[19.98042,39.6504],[19.92466,39.69533],[19.97622,39.78684],[19.95905,39.82857],[19.0384,40.35325]]]}},{"type":"Feature","properties":{"id":"PN"},"geometry":{"type":"Polygon","coordinates":[[[-133.61511,-21.93325],[-133.59543,-28.4709],[-122.0366,-24.55017],[-133.61511,-21.93325]]]}},{"type":"Feature","properties":{"id":"TO"},"geometry":{"type":"Polygon","coordinates":[[[-180,-24.21376],[-173.10761,-24.19665],[-173.11048,-23.23027],[-173.13438,-14.94228],[-174.17905,-14.94502],[-176.76826,-14.95183],[-176.74538,-22.89767],[-180,-22.90585],[-180,-24.21376]]]}},{"type":"Feature","properties":{"id":"WS"},"geometry":{"type":"Polygon","coordinates":[[[-174.18596,-12.48057],[-174.17905,-14.94502],[-173.13438,-14.94228],[-171.14262,-14.93704],[-171.14953,-12.4725],[-174.18596,-12.48057]]]}},{"type":"Feature","properties":{"id":"IN-HR"},"geometry":{"type":"Polygon","coordinates":[[[74.46464,29.78106],[74.50035,29.74053],[74.61158,29.76199],[74.57244,29.56449],[74.62188,29.52985],[74.57382,29.45992],[74.59785,29.32472],[74.80453,29.39563],[75.05207,29.281],[75.08159,29.22889],[75.33462,29.28909],[75.41084,29.19982],[75.36294,29.14301],[75.44448,29.01609],[75.51315,29.01504],[75.51744,28.9733],[75.4656,28.92689],[75.56877,28.61225],[75.80223,28.40227],[75.94333,28.36663],[76.09336,28.1498],[76.01886,28.16948],[75.93097,28.0959],[76.04032,28.08561],[75.91964,27.93329],[75.99449,27.85789],[76.22177,27.81296],[76.17267,28.01849],[76.20614,28.02486],[76.17216,28.05546],[76.22451,28.04986],[76.24958,28.07031],[76.31635,28.01925],[76.34519,28.02804],[76.35618,28.07591],[76.30262,28.09999],[76.36184,28.12543],[76.27,28.17538],[76.49333,28.15404],[76.54792,27.97393],[76.66225,28.01774],[76.65607,28.09954],[76.79958,28.16796],[76.80747,28.21562],[76.86635,28.22681],[76.96317,28.14269],[76.88575,27.67075],[76.96884,27.65555],[77.03235,27.78623],[77.28126,27.80689],[77.52468,27.9204],[77.53635,27.99167],[77.47283,28.08409],[77.54699,28.17613],[77.48794,28.19792],[77.53532,28.23725],[77.46528,28.30831],[77.48828,28.35515],[77.4979,28.40891],[77.46811,28.3997],[77.47361,28.41918],[77.45704,28.43586],[77.43086,28.4259],[77.41584,28.44039],[77.42477,28.46122],[77.41447,28.47465],[77.38752,28.46506],[77.37447,28.47035],[77.34615,28.51651],[77.32563,28.49004],[77.31413,28.4837],[77.30855,28.48823],[77.30053,28.49007],[77.30053,28.49419],[77.28832,28.49673],[77.27527,28.49358],[77.26991,28.48791],[77.26238,28.48762],[77.24298,28.47917],[77.23483,28.46982],[77.23156,28.45609],[77.24101,28.45616],[77.24628,28.4496],[77.24169,28.42763],[77.22058,28.41344],[77.17818,28.40921],[77.17389,28.40446],[77.17208,28.40559],[77.16161,28.42922],[77.14651,28.43692],[77.14043,28.43848],[77.13076,28.43984],[77.11277,28.4731],[77.11973,28.4952],[77.09775,28.50493],[77.09575,28.50713],[77.09863,28.51146],[77.09265,28.51434],[77.08003,28.51815],[77.07505,28.51877],[77.07235,28.52025],[77.07153,28.51787],[77.06703,28.51199],[77.06428,28.51285],[77.06093,28.51225],[77.05746,28.51297],[77.05226,28.51512],[77.0463,28.5167],[77.04862,28.52107],[77.0434,28.52409],[77.04344,28.52513],[77.03209,28.53118],[77.02424,28.5328],[77.0139,28.54068],[77.00544,28.53947],[77.00098,28.53114],[77.01476,28.52398],[77.01703,28.52104],[77.00982,28.51466],[76.9969,28.51949],[76.99111,28.51365],[76.97845,28.5213],[76.95334,28.50509],[76.92034,28.50678],[76.90635,28.51395],[76.90206,28.50671],[76.89657,28.50686],[76.89116,28.50037],[76.88043,28.50543],[76.88738,28.51998],[76.87391,28.5282],[76.86249,28.54487],[76.84584,28.55037],[76.83915,28.58301],[76.86429,28.58602],[76.8903,28.63274],[76.90738,28.62393],[76.91725,28.63395],[76.93528,28.61865],[76.94601,28.63289],[76.92378,28.64999],[76.93751,28.66942],[76.95502,28.66988],[76.95776,28.68448],[76.96781,28.69917],[76.94832,28.71264],[76.96068,28.73161],[76.95811,28.7432],[76.94403,28.75419],[76.95579,28.76841],[76.94807,28.78097],[76.95433,28.79045],[76.94292,28.79955],[76.95116,28.81798],[76.96146,28.81474],[76.97064,28.82783],[76.98077,28.82113],[76.99398,28.84068],[77.04282,28.8355],[77.06171,28.86963],[77.07939,28.87135],[77.08316,28.88315],[77.09166,28.87098],[77.11063,28.86918],[77.12265,28.8573],[77.13406,28.86301],[77.14616,28.85572],[77.14282,28.83858],[77.15681,28.8367],[77.17457,28.85865],[77.21157,28.8573],[77.22358,28.89939],[77.14187,29.09577],[77.10411,29.50415],[77.12882,29.75305],[77.34443,30.06315],[77.58613,30.31006],[77.5542,30.40278],[77.22083,30.49246],[76.99802,30.80024],[76.92386,30.83415],[76.90017,30.89751],[76.7704,30.9087],[76.82825,30.76411],[76.81468,30.68693],[76.81949,30.66139],[76.83794,30.65755],[76.84902,30.66693],[76.85923,30.65762],[76.85846,30.6416],[76.87133,30.63997],[76.91193,30.60504],[76.89931,30.55206],[76.89571,30.53579],[76.92026,30.52618],[76.88798,30.44038],[76.91579,30.40145],[76.92987,30.39686],[76.88833,30.35569],[76.877,30.36265],[76.88438,30.38516],[76.84885,30.40604],[76.83151,30.43328],[76.80747,30.41196],[76.75769,30.43727],[76.69864,30.39257],[76.73375,30.36458],[76.56045,30.26381],[76.63873,30.20493],[76.6238,30.14497],[76.60903,30.07978],[76.50089,30.0783],[76.44664,30.10385],[76.43394,30.15195],[76.2276,30.12656],[76.18057,29.88947],[76.2355,29.88649],[76.24168,29.85761],[76.11602,29.80222],[76.08169,29.80877],[76.04049,29.74798],[75.9423,29.73069],[75.86162,29.75424],[75.83003,29.81354],[75.70232,29.81145],[75.69339,29.75573],[75.65288,29.77987],[75.60173,29.7456],[75.44448,29.80788],[75.39813,29.76378],[75.37891,29.71161],[75.33908,29.68984],[75.34046,29.66747],[75.27934,29.60779],[75.31162,29.58122],[75.22716,29.54598],[75.21858,29.61495],[75.15747,29.66747],[75.1966,29.69073],[75.22991,29.75603],[75.20072,29.832],[75.17806,29.83826],[75.14099,29.77063],[75.1015,29.81294],[75.09155,29.91774],[74.98821,29.86357],[74.87285,29.96296],[74.80144,29.99092],[74.69467,29.9695],[74.63115,29.90494],[74.52094,29.94303],[74.55116,29.85553],[74.46464,29.78106]]]}},{"type":"Feature","properties":{"id":"WF"},"geometry":{"type":"Polygon","coordinates":[[[-178.60852,-12.49232],[-178.60161,-14.95666],[-176.76826,-14.95183],[-174.17905,-14.94502],[-174.18596,-12.48057],[-178.60852,-12.49232]]]}},{"type":"Feature","properties":{"id":"IN-MN"},"geometry":{"type":"Polygon","coordinates":[[[92.98416,24.11354],[93.32351,24.04468],[93.34735,24.10151],[93.41415,24.07854],[93.46633,23.97067],[93.50616,23.94432],[93.62871,24.00922],[93.75952,24.0003],[93.80279,23.92549],[93.92089,23.95812],[94.14081,23.83333],[94.30215,24.23752],[94.32362,24.27692],[94.45279,24.56656],[94.50729,24.59281],[94.5526,24.70764],[94.60204,24.70889],[94.73937,25.00545],[94.74212,25.13606],[94.57458,25.20318],[94.68032,25.47003],[94.55932,25.51084],[94.58953,25.69289],[94.33685,25.50588],[93.86993,25.55854],[93.61175,25.19748],[93.47236,25.3074],[93.17985,24.80169],[93.10569,24.81883],[93.02329,24.40026],[92.98416,24.11354]]]}},{"type":"Feature","properties":{"id":"PF"},"geometry":{"type":"Polygon","coordinates":[[[-156.4957,-12.32002],[-156.46451,-23.21255],[-156.44843,-28.52556],[-133.59543,-28.4709],[-133.61511,-21.93325],[-133.65593,-7.46952],[-149.6249,-7.51261],[-149.61166,-12.30171],[-156.4957,-12.32002]]]}},{"type":"Feature","properties":{"id":"AS"},"geometry":{"type":"Polygon","coordinates":[[[-174.18596,-12.48057],[-171.14953,-12.4725],[-171.14262,-14.93704],[-167.73854,-14.92809],[-167.75195,-10.12005],[-174.17993,-10.13616],[-174.18596,-12.48057]]]}},{"type":"Feature","properties":{"id":"US-HI"},"geometry":{"type":"Polygon","coordinates":[[[-179.2458,29.18869],[-179.23433,18.15359],[-154.13058,18.17333],[-154.14205,29.20682],[-176.83456,29.19028],[-176.81808,27.68129],[-177.84531,27.68616],[-177.8563,29.18961],[-179.2458,29.18869]]]}},{"type":"Feature","properties":{"id":"CN-AH"},"geometry":{"type":"Polygon","coordinates":[[[114.88128,32.97295],[115.20675,32.84844],[115.1889,32.59946],[115.44982,32.53813],[115.56312,32.38634],[115.91812,32.57922],[115.86181,32.45705],[115.93872,32.06861],[115.87005,31.77195],[115.64002,31.76145],[115.4718,31.65045],[115.36165,31.40228],[115.57823,31.14465],[115.69667,31.20604],[115.77461,31.10527],[115.87692,31.1423],[116.07776,30.96966],[115.84156,30.8312],[115.85082,30.75865],[115.7698,30.6701],[115.90061,30.50992],[115.91949,30.30472],[116.08222,30.12434],[116.12823,29.82754],[116.52992,29.89899],[116.57936,30.04769],[116.74278,30.06077],[116.91993,29.94541],[116.64939,29.70117],[116.70913,29.58301],[117.09091,29.69759],[117.06893,29.82634],[117.24952,29.89899],[117.45483,29.69222],[117.69584,29.55852],[118.12705,29.53523],[118.1195,29.42853],[118.20052,29.38178],[118.33717,29.48503],[118.48548,29.52447],[118.75808,29.76258],[118.89816,30.02332],[118.85627,30.16709],[118.90914,30.20508],[118.89404,30.35391],[119.22912,30.28871],[119.38705,30.37761],[119.31152,30.53032],[119.23393,30.53062],[119.24663,30.61575],[119.38671,30.69018],[119.43134,30.64145],[119.57382,30.85743],[119.58412,30.97407],[119.63149,31.1329],[119.57656,31.11174],[119.50378,31.1611],[119.36782,31.19107],[119.35134,31.30143],[119.27238,31.25272],[119.17968,31.29908],[119.07257,31.23394],[118.78761,31.23394],[118.71482,31.29967],[118.86314,31.43745],[118.8652,31.62415],[118.73954,31.67851],[118.71894,31.62415],[118.64204,31.64812],[118.69354,31.7194],[118.47587,31.78246],[118.49716,31.84373],[118.35605,31.93788],[118.39004,32.02612],[118.50128,32.14393],[118.50299,32.19479],[118.64444,32.21657],[118.6956,32.36198],[118.68049,32.45647],[118.54659,32.58442],[118.89747,32.58905],[119.08218,32.45531],[119.22225,32.60756],[119.17762,32.83286],[118.85559,32.95855],[118.7059,32.71624],[118.37425,32.72144],[118.22456,32.9332],[118.20121,33.2203],[118.03024,33.12777],[117.96775,33.33052],[118.18405,33.74546],[117.71026,33.74718],[117.74322,33.89264],[117.50907,34.06176],[117.02567,34.15556],[116.95014,34.39841],[116.37405,34.64676],[116.19792,34.51759],[116.2429,34.37177],[116.57867,34.27537],[116.53266,34.09588],[116.64974,33.96585],[116.63806,33.8858],[116.22951,33.72148],[116.14986,33.71148],[116.03176,33.83563],[116.05545,33.85787],[115.96034,33.90376],[115.99845,33.97439],[115.76431,34.07598],[115.59059,34.02563],[115.54801,33.8821],[115.62286,33.57115],[115.34957,33.5185],[115.30769,33.20709],[115.13465,33.08348],[114.91287,33.14387],[114.88128,32.97295]]]}},{"type":"Feature","properties":{"id":"CV"},"geometry":{"type":"Polygon","coordinates":[[[-25.86,17.60587],[-25.82546,14.43014],[-22.19117,14.46693],[-22.22571,17.64208],[-25.86,17.60587]]]}},{"type":"Feature","properties":{"id":"SH"},"geometry":{"type":"Polygon","coordinates":[[[-14.91926,-6.63386],[-14.82771,-8.70814],[-13.48367,-36.6746],[-13.41694,-37.88844],[-13.29655,-40.02846],[-9.34669,-41.00353],[-4.97086,-15.55882],[-13.33271,-8.07391],[-14.91926,-6.63386]]]}},{"type":"Feature","properties":{"id":"CN-NM"},"geometry":{"type":"Polygon","coordinates":[[[97.1777,42.7964],[98.3139,40.56806],[99.76958,40.97575],[100.28045,40.67439],[99.70229,39.98659],[100.84075,39.18224],[101.7279,38.64154],[101.8872,39.06931],[102.45712,39.23757],[102.96798,39.10981],[103.9389,39.46482],[104.18884,39.12047],[103.54476,38.15615],[103.36074,38.08809],[103.39507,37.88352],[103.83865,37.65773],[104.29321,37.4367],[104.99084,37.5424],[105.10619,37.63707],[105.76606,37.79513],[105.80039,37.94149],[105.8258,38.34219],[105.85121,38.62116],[106.13479,39.15615],[106.59484,39.36934],[106.78161,39.37518],[106.95396,38.94819],[106.47743,38.31903],[107.1627,38.16155],[107.66139,37.87999],[107.96607,37.79269],[108.01174,37.66561],[108.78936,37.68436],[108.83193,38.0662],[108.93836,37.9182],[109.02831,38.01618],[109.18521,38.03592],[108.93356,38.17883],[108.9624,38.35673],[109.55428,38.80119],[109.70809,39.05011],[109.89795,39.14683],[110.20866,39.28488],[110.10944,39.42876],[110.23063,39.4566],[110.38135,39.30826],[110.43662,39.38261],[110.52005,39.3834],[110.59661,39.27744],[110.68759,39.26522],[110.88363,39.50854],[111.15074,39.58478],[111.04808,39.43022],[111.09289,39.35859],[111.11881,39.36403],[111.12945,39.4025],[111.21288,39.42638],[111.33235,39.42081],[111.36188,39.47489],[111.4199,39.50245],[111.43295,39.64006],[111.53217,39.65698],[111.67121,39.62525],[111.72615,39.59537],[111.91909,39.61468],[111.97059,39.78822],[112.10758,39.97527],[112.30293,40.25463],[112.45399,40.29995],[112.61947,40.23891],[112.73963,40.1626],[112.84572,40.20169],[112.88761,40.32822],[113.24809,40.41349],[113.31504,40.31304],[113.53443,40.3345],[113.6721,40.4425],[113.85234,40.44511],[113.93783,40.50544],[114.07001,40.54093],[114.0652,40.67634],[114.1246,40.74569],[114.05834,40.79795],[114.06829,40.85485],[113.83621,41.08452],[113.99208,41.19054],[113.85612,41.41338],[114.01336,41.52503],[114.21455,41.50703],[114.23309,41.69547],[114.43702,41.9457],[114.50637,42.11095],[114.84626,42.14456],[114.93484,41.85882],[114.8545,41.59593],[115.33927,41.59541],[115.31112,41.67291],[115.81787,41.93191],[115.91194,41.93804],[116.06574,41.77131],[116.10763,41.84399],[116.30058,41.99675],[116.70158,41.93037],[116.86981,42.00236],[116.78054,42.19902],[116.90757,42.18477],[116.88697,42.37985],[117.00782,42.45994],[117.39921,42.46449],[117.44453,42.59151],[117.7851,42.61678],[118.01376,42.39962],[117.98973,42.25952],[118.18267,42.02838],[118.26232,42.08446],[118.34335,41.85728],[118.11538,41.76567],[118.20396,41.62314],[118.38729,41.31082],[118.79585,41.3598],[119.1481,41.29638],[119.23562,41.3149],[119.29435,41.32732],[119.36576,41.43191],[119.39392,41.58566],[119.32456,41.62519],[119.28268,41.78155],[119.3795,42.08803],[119.29916,42.12522],[119.23736,42.19596],[119.27032,42.25901],[119.50584,42.39709],[119.54498,42.29356],[119.83474,42.21428],[120.03662,41.81277],[120.02975,41.71341],[120.09498,41.68932],[120.18424,41.84245],[120.40878,41.98297],[120.5722,42.16747],[121.03774,42.27019],[121.28494,42.43359],[121.56646,42.51968],[121.66534,42.43967],[121.86309,42.53588],[121.92077,42.67032],[122.41241,42.8659],[122.80517,42.73087],[123.24737,42.98255],[123.55224,42.99862],[123.68682,43.3756],[123.32153,43.48979],[123.53301,43.64203],[123.3229,44.06045],[123.37715,44.15215],[123.1327,44.34938],[123.143,44.52294],[122.33001,44.22256],[122.11715,44.57237],[122.08213,44.91327],[122.1453,45.2971],[122.16522,45.41484],[122.01965,45.48324],[121.99836,45.6366],[121.95098,45.71097],[121.74568,45.68267],[121.64337,45.73877],[121.82121,45.8728],[121.75735,45.99505],[121.8727,46.04083],[122.2586,45.79434],[122.39868,45.9139],[122.52433,45.7771],[122.72003,45.70474],[122.79418,45.94064],[123.17046,46.2283],[123.00498,46.56877],[123.59275,46.69042],[123.46778,46.96291],[123.00842,46.72385],[122.69393,47.08041],[122.3973,47.34626],[122.84637,47.67648],[123.55361,48.03493],[124.26086,48.52933],[124.50118,48.12485],[124.53552,48.46928],[124.61036,48.74894],[124.67834,48.83037],[124.86099,49.17945],[125.10681,49.12377],[125.2153,49.23777],[125.25649,49.32691],[125.18989,49.93442],[125.51467,50.39626],[125.78933,50.53263],[125.78521,50.7408],[126.06674,50.96621],[125.31005,51.62824],[124.27871,51.304],[122.96447,51.31173],[122.7365,52.20255],[122.19817,52.50786],[121.87133,52.27152],[121.20391,52.57468],[121.82738,53.03956],[121.39213,53.31888],[120.85633,53.28499],[120.0451,52.7359],[120.04049,52.58773],[120.46454,52.63811],[120.71673,52.54099],[120.61346,52.32447],[120.77337,52.20805],[120.65907,51.93544],[120.10963,51.671],[119.13553,50.37412],[119.38598,50.35162],[119.27996,50.13348],[119.11003,50.00276],[118.61623,49.93809],[117.82343,49.52696],[117.48208,49.62324],[117.27597,49.62544],[117.07142,49.68482],[116.71193,49.83813],[116.03781,48.87014],[116.06565,48.81716],[115.78876,48.51781],[115.811,48.25699],[115.52082,48.15367],[115.57128,47.91988],[115.94296,47.67741],[116.08431,47.80693],[116.2527,47.87766],[116.4465,47.83662],[116.67405,47.89039],[116.87527,47.88836],[117.08918,47.82242],[117.37875,47.63627],[117.50181,47.77216],[117.80196,48.01661],[118.03676,48.00982],[118.11009,48.04],[118.22677,48.03853],[118.29654,48.00246],[118.55766,47.99277],[118.7564,47.76947],[119.12343,47.66458],[119.13995,47.53997],[119.35892,47.48104],[119.31964,47.42617],[119.54918,47.29505],[119.56019,47.24874],[119.62403,47.24575],[119.71209,47.19192],[119.85518,46.92196],[119.91242,46.90091],[119.89261,46.66423],[119.80455,46.67631],[119.77373,46.62947],[119.68127,46.59015],[119.65265,46.62342],[119.42827,46.63783],[119.37306,46.61132],[119.30261,46.6083],[119.24978,46.64761],[119.10448,46.65516],[119.00541,46.74273],[118.92616,46.72765],[118.89974,46.77139],[118.8337,46.77742],[118.78747,46.68689],[118.30534,46.73519],[117.69554,46.50991],[117.60748,46.59771],[117.41782,46.57862],[117.36609,46.36335],[117.07252,46.35818],[116.83166,46.38637],[116.75551,46.33083],[116.58612,46.30211],[116.26678,45.96479],[116.24012,45.8778],[116.27366,45.78637],[116.16989,45.68603],[115.91898,45.6227],[115.69688,45.45761],[115.35757,45.39106],[114.94546,45.37377],[114.74612,45.43585],[114.54801,45.38337],[114.5166,45.27189],[114.08071,44.92847],[113.909,44.91444],[113.63821,44.74326],[112.74662,44.86297],[112.4164,45.06858],[111.98695,45.09074],[111.76275,44.98032],[111.40498,44.3461],[111.96289,43.81596],[111.93776,43.68709],[111.79758,43.6637],[111.59087,43.51207],[111.0149,43.3289],[110.4327,42.78293],[110.08401,42.6411],[109.89402,42.63111],[109.452,42.44842],[109.00679,42.45302],[108.84489,42.40246],[108.23156,42.45532],[107.57258,42.40898],[107.49681,42.46221],[107.29755,42.41395],[107.24774,42.36107],[106.76517,42.28741],[105.24708,41.7442],[105.01119,41.58382],[104.91272,41.64619],[104.51667,41.66113],[104.52258,41.8706],[103.92804,41.78246],[103.3685,41.89696],[102.72403,42.14675],[102.42826,42.15137],[102.07645,42.22519],[101.80515,42.50074],[101.28833,42.58524],[100.84979,42.67087],[100.33297,42.68231],[99.50671,42.56535],[97.1777,42.7964]]]}},{"type":"Feature","properties":{"id":"CP"},"geometry":{"type":"Polygon","coordinates":[[[-109.6462,9.84394],[-108.755,9.84085],[-108.75183,10.72712],[-109.64303,10.7302],[-109.6462,9.84394]]]}},{"type":"Feature","properties":{"id":"MX"},"geometry":{"type":"Polygon","coordinates":[[[-120.12904,18.41089],[-92.37213,14.39277],[-92.2261,14.53423],[-92.1454,14.6804],[-92.18161,14.84147],[-92.1423,14.88647],[-92.1454,14.98143],[-92.0621,15.07406],[-92.20983,15.26077],[-91.73182,16.07371],[-90.44567,16.07573],[-90.40499,16.40524],[-90.61212,16.49832],[-90.69064,16.70697],[-91.04436,16.92175],[-91.43809,17.25373],[-90.99199,17.25192],[-90.98678,17.81655],[-89.14985,17.81563],[-89.15105,17.95104],[-89.03839,18.0067],[-88.8716,17.89535],[-88.71505,18.0707],[-88.48242,18.49164],[-88.3268,18.49048],[-88.29909,18.47591],[-88.26593,18.47617],[-88.03238,18.41778],[-88.03165,18.16657],[-87.90671,18.15213],[-87.87604,18.18313],[-87.86657,18.19971],[-87.85693,18.18266],[-87.84815,18.18511],[-87.24084,17.80373],[-85.9092,21.8218],[-96.92418,25.97377],[-97.13927,25.96583],[-97.35946,25.92189],[-97.37332,25.83854],[-97.42511,25.83969],[-97.45669,25.86874],[-97.49828,25.89877],[-97.52025,25.88518],[-97.66511,26.01708],[-97.95155,26.0625],[-97.97017,26.05232],[-98.24603,26.07191],[-98.27075,26.09457],[-98.30491,26.10475],[-98.35126,26.15129],[-99.00546,26.3925],[-99.03053,26.41249],[-99.08477,26.39849],[-99.53573,27.30926],[-99.49744,27.43746],[-99.482,27.47128],[-99.48045,27.49016],[-99.50208,27.50021],[-99.52955,27.49747],[-99.51478,27.55836],[-99.55409,27.61314],[-100.50029,28.66117],[-100.51222,28.70679],[-100.5075,28.74066],[-100.52313,28.75598],[-100.59809,28.88197],[-100.63689,28.90812],[-100.67294,29.09744],[-100.79696,29.24688],[-100.87982,29.296],[-100.94056,29.33371],[-100.94579,29.34523],[-100.96725,29.3477],[-101.01128,29.36947],[-101.05686,29.44738],[-101.47277,29.7744],[-102.60596,29.8192],[-103.15787,28.93865],[-104.37752,29.54255],[-104.39363,29.55396],[-104.3969,29.57105],[-104.5171,29.64671],[-104.77674,30.4236],[-106.00363,31.39181],[-106.09025,31.40569],[-106.20346,31.46305],[-106.23711,31.51262],[-106.24612,31.54193],[-106.28084,31.56173],[-106.30305,31.62154],[-106.33419,31.66303],[-106.34864,31.69663],[-106.3718,31.71165],[-106.38003,31.73151],[-106.41773,31.75196],[-106.43419,31.75478],[-106.45244,31.76523],[-106.46726,31.75998],[-106.47298,31.75054],[-106.48815,31.74769],[-106.50111,31.75714],[-106.50962,31.76155],[-106.51251,31.76922],[-106.52266,31.77509],[-106.529,31.784],[-108.20899,31.78534],[-108.20979,31.33316],[-109.05235,31.3333],[-111.07523,31.33232],[-112.34553,31.7357],[-114.82011,32.49609],[-114.79524,32.55731],[-114.81141,32.55543],[-114.80584,32.62028],[-114.76736,32.64094],[-114.71871,32.71894],[-115.88053,32.63624],[-117.1243,32.53427],[-118.48109,32.5991],[-120.12904,18.41089]]]}},{"type":"Feature","properties":{"id":"IE"},"geometry":{"type":"Polygon","coordinates":[[[-13.3292,54.24486],[-10.17712,50.85267],[-5.79914,52.03902],[-5.37267,53.63269],[-5.83481,53.87749],[-6.26218,54.09785],[-6.29003,54.11278],[-6.32694,54.09337],[-6.36279,54.11248],[-6.36605,54.07234],[-6.47849,54.06947],[-6.62842,54.03503],[-6.66264,54.0666],[-6.6382,54.17071],[-6.70175,54.20218],[-6.74575,54.18788],[-6.81583,54.22791],[-6.85179,54.29176],[-6.87775,54.34682],[-7.02034,54.4212],[-7.19145,54.31296],[-7.14908,54.22732],[-7.25012,54.20063],[-7.26316,54.13863],[-7.29493,54.12013],[-7.29687,54.1354],[-7.28017,54.16714],[-7.29157,54.17191],[-7.34005,54.14698],[-7.30553,54.11869],[-7.32834,54.11475],[-7.44567,54.1539],[-7.4799,54.12239],[-7.55812,54.12239],[-7.69501,54.20731],[-7.81397,54.20159],[-7.8596,54.21779],[-7.87101,54.29299],[-8.04555,54.36292],[-8.179,54.46763],[-8.04538,54.48941],[-7.99812,54.54427],[-7.8596,54.53671],[-7.70315,54.62077],[-7.93293,54.66603],[-7.83352,54.73854],[-7.75041,54.7103],[-7.64449,54.75265],[-7.54671,54.74606],[-7.54508,54.79401],[-7.47626,54.83084],[-7.4473,54.87003],[-7.44404,54.9403],[-7.40004,54.94498],[-7.4033,55.00391],[-7.34464,55.04688],[-7.2471,55.06933],[-6.9734,55.19878],[-6.71944,55.27952],[-6.79943,55.54107],[-7.93366,55.84142],[-13.3292,54.24486]]]}},{"type":"Feature","properties":{"id":"ST"},"geometry":{"type":"Polygon","coordinates":[[[5.9107,-0.09539],[6.69416,-0.53945],[8.0168,1.79377],[7.23334,2.23756],[5.9107,-0.09539]]]}},{"type":"Feature","properties":{"id":"GD"},"geometry":{"type":"Polygon","coordinates":[[[-62.14806,11.87638],[-61.57265,11.65795],[-61.13395,12.51526],[-61.38256,12.52991],[-61.73897,12.61191],[-62.14806,11.87638]]]}},{"type":"Feature","properties":{"id":"AG"},"geometry":{"type":"Polygon","coordinates":[[[-62.62949,16.82364],[-62.52079,16.69392],[-62.14123,17.02632],[-61.83929,16.66647],[-61.44461,16.81958],[-61.45764,17.9187],[-62.12601,17.9235],[-62.27053,17.22145],[-62.62949,16.82364]]]}},{"type":"Feature","properties":{"id":"AF"},"geometry":{"type":"Polygon","coordinates":[[[60.50209,34.13992],[60.5838,33.80793],[60.5485,33.73422],[60.57762,33.59772],[60.69573,33.56054],[60.91133,33.55596],[60.88908,33.50219],[60.56485,33.12944],[60.86191,32.22565],[60.84541,31.49561],[61.70929,31.37391],[61.80569,31.16167],[61.80957,31.12576],[61.83257,31.0452],[61.8335,30.97669],[61.78268,30.92724],[61.80829,30.84224],[60.87231,29.86514],[62.47751,29.40782],[63.5876,29.50456],[64.12966,29.39157],[64.19796,29.50407],[64.62116,29.58903],[65.04005,29.53957],[66.24175,29.85181],[66.36042,29.9583],[66.23609,30.06321],[66.34869,30.404],[66.28413,30.57001],[66.39194,30.9408],[66.42645,30.95309],[66.58175,30.97532],[66.68166,31.07597],[66.72561,31.20526],[66.83273,31.26867],[67.04147,31.31561],[67.03323,31.24519],[67.29964,31.19586],[67.78854,31.33203],[67.7748,31.4188],[67.62374,31.40473],[67.58323,31.52772],[67.72056,31.52304],[67.86887,31.63536],[68.00071,31.6564],[68.1655,31.82691],[68.25614,31.80357],[68.27605,31.75863],[68.44222,31.76446],[68.57475,31.83158],[68.6956,31.75687],[68.79997,31.61665],[68.91078,31.59687],[68.95995,31.64822],[69.00939,31.62249],[69.11514,31.70782],[69.20577,31.85957],[69.3225,31.93186],[69.27032,32.14141],[69.27932,32.29119],[69.23599,32.45946],[69.2868,32.53938],[69.38155,32.56601],[69.44747,32.6678],[69.43649,32.7302],[69.38018,32.76601],[69.47082,32.85834],[69.5436,32.8768],[69.49854,32.88843],[69.49004,33.01509],[69.57656,33.09911],[69.71526,33.09911],[69.79766,33.13247],[69.85259,33.09451],[70.02563,33.14282],[70.07369,33.22557],[70.13686,33.21064],[70.32775,33.34496],[70.17062,33.53535],[70.20141,33.64387],[70.14785,33.6553],[70.14236,33.71701],[70.00503,33.73528],[69.85671,33.93719],[69.87307,33.9689],[69.90203,34.04194],[70.54336,33.9463],[70.88119,33.97933],[71.07345,34.06242],[71.06933,34.10564],[71.09307,34.11961],[71.09453,34.13524],[71.13078,34.16503],[71.12815,34.26619],[71.17662,34.36769],[71.02401,34.44835],[71.0089,34.54568],[71.11602,34.63047],[71.08718,34.69034],[71.28356,34.80882],[71.29472,34.87728],[71.50329,34.97328],[71.49917,35.00478],[71.55273,35.02615],[71.52938,35.09023],[71.67495,35.21262],[71.5541,35.28776],[71.54294,35.31037],[71.65435,35.4479],[71.49917,35.6267],[71.55273,35.71483],[71.37969,35.95865],[71.19505,36.04134],[71.60491,36.39429],[71.80267,36.49924],[72.18135,36.71838],[72.6323,36.84601],[73.82685,36.91421],[74.04856,36.82648],[74.43389,37.00977],[74.53739,36.96224],[74.56453,37.03023],[74.49981,37.24518],[74.80605,37.21565],[74.88887,37.23275],[74.8294,37.3435],[74.68383,37.3948],[74.56161,37.37734],[74.41055,37.3948],[74.23339,37.41116],[74.20308,37.34208],[73.8564,37.26158],[73.82552,37.22659],[73.64974,37.23643],[73.61129,37.27469],[73.76647,37.33913],[73.77197,37.4417],[73.29633,37.46495],[73.06884,37.31729],[72.79693,37.22222],[72.66381,37.02014],[72.54095,37.00007],[72.31676,36.98115],[71.83229,36.68084],[71.67083,36.67346],[71.57195,36.74943],[71.51502,36.89128],[71.48481,36.93218],[71.46923,36.99925],[71.45578,37.03094],[71.43097,37.05855],[71.44127,37.11856],[71.4494,37.18137],[71.4555,37.21418],[71.47386,37.2269],[71.48339,37.23937],[71.4824,37.24921],[71.48536,37.26017],[71.50674,37.31502],[71.49821,37.31975],[71.4862,37.33405],[71.47685,37.40281],[71.49612,37.4279],[71.5256,37.47971],[71.50616,37.50733],[71.49693,37.53527],[71.5065,37.60912],[71.51972,37.61945],[71.54186,37.69691],[71.55234,37.73209],[71.53053,37.76534],[71.54324,37.77104],[71.55752,37.78677],[71.59255,37.79956],[71.58843,37.92425],[71.51565,37.95349],[71.32871,37.88564],[71.296,37.93403],[71.2809,37.91995],[71.24969,37.93031],[71.27278,37.96496],[71.27622,37.99946],[71.28922,38.01272],[71.29878,38.04429],[71.36444,38.15358],[71.37803,38.25641],[71.33869,38.27335],[71.33114,38.30339],[71.21291,38.32797],[71.1451,38.40106],[71.10957,38.40671],[71.10592,38.42077],[71.09542,38.42517],[71.0556,38.40176],[71.03545,38.44779],[70.98693,38.48862],[70.92728,38.43021],[70.88719,38.46826],[70.84376,38.44688],[70.82538,38.45394],[70.81697,38.44507],[70.80521,38.44447],[70.79766,38.44944],[70.78702,38.45031],[70.78581,38.45502],[70.77132,38.45548],[70.75455,38.4252],[70.72485,38.4131],[70.69807,38.41861],[70.67438,38.40597],[70.6761,38.39144],[70.69189,38.37031],[70.64966,38.34999],[70.61526,38.34774],[70.60407,38.28046],[70.54673,38.24541],[70.4898,38.12546],[70.17206,37.93276],[70.1863,37.84296],[70.27694,37.81258],[70.28243,37.66706],[70.15015,37.52519],[69.95971,37.5659],[69.93362,37.61378],[69.84435,37.60616],[69.80041,37.5746],[69.51888,37.5844],[69.44954,37.4869],[69.36645,37.40462],[69.45022,37.23315],[69.39529,37.16752],[69.25152,37.09426],[69.03274,37.25174],[68.96407,37.32603],[68.88168,37.33368],[68.91189,37.26704],[68.80889,37.32494],[68.81438,37.23862],[68.6798,37.27906],[68.61851,37.19815],[68.41888,37.13906],[68.41201,37.10402],[68.29253,37.10621],[68.27605,37.00977],[68.18542,37.02074],[68.02194,36.91923],[67.87917,37.0591],[67.7803,37.08978],[67.78329,37.1834],[67.51868,37.26102],[67.2581,37.17216],[67.2224,37.24545],[67.13039,37.27168],[67.08232,37.35469],[66.95598,37.40162],[66.64699,37.32958],[66.55743,37.35409],[66.30993,37.32409],[65.72274,37.55438],[65.64137,37.45061],[65.64263,37.34388],[65.51778,37.23881],[64.97945,37.21913],[64.61141,36.6351],[64.62514,36.44311],[64.57295,36.34362],[64.43288,36.24401],[64.05385,36.10433],[63.98519,36.03773],[63.56496,35.95106],[63.53475,35.90881],[63.29579,35.85985],[63.12276,35.86208],[63.10318,35.81782],[63.23262,35.67487],[63.10079,35.63024],[63.12276,35.53196],[63.0898,35.43131],[62.90853,35.37086],[62.74098,35.25432],[62.62288,35.22067],[62.48006,35.28796],[62.29878,35.13312],[62.29191,35.25964],[62.15871,35.33278],[62.05709,35.43803],[61.97743,35.4604],[61.77693,35.41341],[61.58742,35.43803],[61.27371,35.61482],[61.18187,35.30249],[61.0991,35.27845],[61.12831,35.09938],[61.06926,34.82139],[61.00197,34.70631],[60.99922,34.63064],[60.72316,34.52857],[60.91321,34.30411],[60.66502,34.31539],[60.50209,34.13992]]]}},{"type":"Feature","properties":{"id":"AO"},"geometry":{"type":"Polygon","coordinates":[[[10.5065,-17.25284],[11.75063,-17.25013],[12.07076,-17.15165],[12.52111,-17.24495],[12.97145,-16.98567],[13.36212,-16.98048],[13.95896,-17.43141],[14.28743,-17.38814],[18.39229,-17.38927],[18.84226,-17.80375],[21.14283,-17.94318],[21.42741,-18.02787],[23.47474,-17.62877],[23.20038,-17.47563],[22.17217,-16.50269],[22.00323,-16.18028],[21.97988,-13.00148],[24.03339,-12.99091],[23.90937,-12.844],[24.06672,-12.29058],[23.98804,-12.13149],[24.02603,-11.15368],[24.00027,-10.89356],[23.86868,-11.02856],[23.45631,-10.946],[23.16602,-11.10577],[22.54205,-11.05784],[22.25951,-11.24911],[22.17954,-10.85884],[22.32604,-10.76291],[22.19039,-9.94628],[21.84856,-9.59871],[21.79824,-7.29628],[20.56263,-7.28566],[20.61689,-6.90876],[20.31846,-6.91953],[20.30218,-6.98955],[19.5469,-7.00195],[19.33698,-7.99743],[18.33635,-8.00126],[17.5828,-8.13784],[16.96282,-7.21787],[16.55507,-5.85631],[13.04371,-5.87078],[12.42245,-6.07585],[11.95767,-5.94705],[12.20376,-5.76338],[12.26557,-5.74031],[12.52318,-5.74353],[12.52301,-5.17481],[12.53599,-5.1618],[12.53586,-5.14658],[12.51589,-5.1332],[12.49815,-5.14058],[12.46297,-5.09408],[12.60251,-5.01715],[12.63465,-4.94632],[12.70868,-4.95505],[12.8733,-4.74346],[13.11195,-4.67745],[13.09648,-4.63739],[12.91489,-4.47907],[12.87096,-4.40315],[12.76844,-4.38709],[12.64835,-4.55937],[12.40964,-4.60609],[12.32324,-4.78415],[12.25587,-4.79437],[12.20901,-4.75642],[12.16068,-4.90089],[12.00924,-5.02627],[11.50888,-5.33417],[10.5065,-17.25284]]]}},{"type":"Feature","properties":{"id":"AL"},"geometry":{"type":"Polygon","coordinates":[[[19.0384,40.35325],[19.95905,39.82857],[19.97622,39.78684],[19.92466,39.69533],[19.98042,39.6504],[20.00957,39.69227],[20.05189,39.69112],[20.12956,39.65805],[20.15988,39.652],[20.22376,39.64532],[20.22707,39.67459],[20.27412,39.69884],[20.31961,39.72799],[20.29152,39.80421],[20.30804,39.81563],[20.38572,39.78516],[20.41475,39.81437],[20.41546,39.82832],[20.31135,39.99438],[20.37911,39.99058],[20.42373,40.06777],[20.48487,40.06271],[20.51297,40.08168],[20.55593,40.06524],[20.61081,40.07866],[20.62566,40.0897],[20.67162,40.09433],[20.71789,40.27739],[20.78234,40.35803],[20.7906,40.42726],[20.83688,40.47882],[20.94925,40.46625],[20.96908,40.51526],[21.03932,40.56299],[21.05833,40.66586],[20.98134,40.76046],[20.95752,40.76982],[20.98396,40.79109],[20.97887,40.85475],[20.97693,40.90103],[20.94305,40.92399],[20.83671,40.92752],[20.81567,40.89662],[20.73504,40.9081],[20.71634,40.91781],[20.65558,41.08009],[20.63454,41.0889],[20.59832,41.09066],[20.58546,41.11179],[20.59715,41.13644],[20.51068,41.2323],[20.49432,41.33679],[20.52119,41.34381],[20.55976,41.4087],[20.51301,41.442],[20.49039,41.49277],[20.45331,41.51436],[20.45809,41.5549],[20.52103,41.56473],[20.55508,41.58113],[20.51769,41.65975],[20.52937,41.69292],[20.51301,41.72433],[20.53405,41.78099],[20.57144,41.7897],[20.55976,41.87068],[20.59524,41.8818],[20.57946,41.91593],[20.63069,41.94913],[20.59434,42.03879],[20.55633,42.08173],[20.56955,42.12097],[20.48857,42.25444],[20.3819,42.3029],[20.34479,42.32656],[20.24399,42.32168],[20.21797,42.41237],[20.17127,42.50469],[20.07761,42.55582],[20.01834,42.54622],[20.00842,42.5109],[19.9324,42.51699],[19.82333,42.46581],[19.76549,42.50237],[19.74731,42.57422],[19.77375,42.58517],[19.73244,42.66299],[19.65972,42.62774],[19.4836,42.40831],[19.42352,42.36546],[19.42,42.33019],[19.28623,42.17745],[19.40687,42.10024],[19.37548,42.06835],[19.36867,42.02564],[19.37691,41.96977],[19.34601,41.95675],[19.33812,41.90669],[19.37451,41.8842],[19.37597,41.84849],[19.26406,41.74971],[19.0384,40.35325]]]}},{"type":"Feature","properties":{"id":"AD"},"geometry":{"type":"Polygon","coordinates":[[[1.41245,42.53539],[1.44759,42.54431],[1.46661,42.50949],[1.41648,42.48315],[1.43838,42.47848],[1.44529,42.43724],[1.5127,42.42959],[1.55073,42.43299],[1.55937,42.45808],[1.57953,42.44957],[1.58933,42.46275],[1.65674,42.47125],[1.66826,42.50779],[1.70571,42.48867],[1.72515,42.50338],[1.73683,42.55492],[1.7858,42.57698],[1.72588,42.59098],[1.73452,42.61515],[1.68267,42.62533],[1.6625,42.61982],[1.63485,42.62957],[1.60085,42.62703],[1.55418,42.65669],[1.50867,42.64483],[1.48043,42.65203],[1.46718,42.63296],[1.47986,42.61346],[1.44197,42.60217],[1.42512,42.58292],[1.44529,42.56722],[1.4234,42.55959],[1.41245,42.53539]]]}},{"type":"Feature","properties":{"id":"CN-SC"},"geometry":{"type":"Polygon","coordinates":[[[97.33989,32.9038],[97.37834,32.86978],[97.37766,32.80718],[97.4271,32.7122],[97.48168,32.65556],[97.5325,32.64241],[97.54417,32.62332],[97.66296,32.55781],[97.72544,32.52886],[97.99255,32.46748],[98.21639,32.33936],[98.30497,32.12619],[98.43475,32.00458],[98.409,31.83089],[98.56246,31.67705],[98.71971,31.50626],[98.84433,31.42954],[98.88896,31.37767],[98.77567,31.24949],[98.69052,31.33751],[98.63559,31.33839],[98.60469,31.18725],[98.71009,31.11997],[98.75404,31.03293],[98.80725,30.98496],[98.78528,30.92431],[98.95523,30.74862],[98.90613,30.68457],[98.99642,30.15344],[99.0438,30.07979],[99.05651,29.93708],[99.01119,29.8189],[98.98956,29.66359],[99.06234,29.45051],[99.05479,29.30586],[99.11487,29.22679],[99.15504,28.43488],[99.39742,28.16463],[99.38713,28.51033],[99.66384,28.82362],[99.96116,28.56582],[100.00785,28.21063],[100.33607,27.72608],[100.71166,27.83907],[101.15867,27.0316],[101.37428,26.88778],[101.46629,26.60387],[101.37908,26.60571],[101.79725,26.16653],[101.81167,26.05061],[102.10075,26.07652],[102.38708,26.29095],[102.957,26.34019],[103.05725,26.53079],[102.90412,26.90308],[102.91992,27.13248],[102.88352,27.25585],[102.93777,27.41078],[103.09295,27.39676],[103.57601,27.97135],[103.40434,28.04077],[103.63574,28.26719],[103.70304,28.19974],[103.76552,28.23453],[103.87367,28.30256],[103.85959,28.38807],[103.78749,28.51757],[103.85616,28.68004],[103.89015,28.62672],[104.08241,28.61014],[104.36977,28.65293],[104.46659,28.61556],[104.23725,28.54532],[104.30076,28.30679],[104.35432,28.34366],[104.44805,28.11801],[104.28909,28.05834],[104.56409,27.85],[104.87205,27.90766],[105.03616,28.09742],[105.17074,28.07258],[105.31219,27.71088],[105.60676,27.69751],[105.92262,27.72973],[106.35864,27.83907],[106.20826,28.13497],[106.13857,28.16948],[105.9233,28.1277],[105.8876,28.23785],[105.65895,28.308],[105.61981,28.43488],[105.68778,28.57547],[105.88142,28.602],[105.96588,28.7496],[106.36756,28.52662],[106.26113,28.83715],[106.0033,28.97811],[105.90408,28.9054],[105.74958,29.01894],[105.7101,29.30047],[105.65036,29.24357],[105.43888,29.31783],[105.44128,29.40131],[105.37261,29.42285],[105.29193,29.58122],[105.45707,29.67612],[105.60127,29.83707],[105.70667,29.83796],[105.75302,30.01619],[105.59062,30.11216],[105.61431,30.26381],[105.68778,30.26618],[105.79627,30.44393],[106.16397,30.30413],[106.21067,30.18134],[106.54643,30.32843],[106.76582,30.01738],[106.96941,30.08484],[107.03155,30.04532],[107.19291,30.18727],[107.50431,30.63732],[107.42637,30.7318],[107.48508,30.84476],[107.63683,30.81233],[107.69657,30.876],[107.84866,30.79405],[107.99114,30.91076],[107.92282,30.92578],[108.07182,31.18049],[108.02032,31.24803],[108.54011,31.67559],[108.27369,31.92885],[108.50234,32.20641],[108.25309,32.28365],[107.97569,32.13782],[107.42362,32.55433],[107.25677,32.40779],[107.1215,32.48543],[107.1009,32.67174],[106.85851,32.71855],[106.43073,32.6307],[106.11007,32.72144],[106.04484,32.86805],[105.62461,32.70526],[105.47878,32.89406],[105.43647,32.94875],[105.39321,32.72433],[105.11032,32.60004],[104.40238,32.79189],[104.4326,33.00636],[104.33921,33.1979],[104.45526,33.32938],[104.28634,33.35978],[104.10026,33.68435],[103.77891,33.66606],[103.16162,33.7974],[103.17672,34.07484],[102.89039,34.33266],[102.66792,34.07541],[102.45849,34.09929],[102.3912,33.97753],[102.14332,33.98379],[102.46879,33.47211],[101.81579,33.10937],[101.94625,33.58773],[101.76223,33.46925],[101.64001,33.09844],[101.1621,33.22835],[101.22665,32.76071],[101.1185,32.63619],[100.94032,32.60756],[100.71819,32.67492],[100.66463,32.52481],[100.54687,32.5714],[100.49554,32.65671],[99.8822,33.04781],[99.73388,32.72375],[99.36035,32.90092],[98.85497,33.14675],[98.73962,33.43717],[98.42651,33.85217],[98.41689,34.09816],[97.66158,34.12431],[97.65266,33.93538],[97.39517,33.89492],[97.40409,33.63062],[97.7584,33.40536],[97.62348,33.33769],[97.59841,33.25964],[97.4858,33.166],[97.49061,33.11512],[97.53078,32.99167],[97.4319,32.9813],[97.33989,32.9038]]]}},{"type":"Feature","properties":{"id":"AR"},"geometry":{"type":"Polygon","coordinates":[[[-73.55259,-49.92488],[-73.15765,-50.78337],[-72.31343,-50.58411],[-72.33873,-51.59954],[-71.99889,-51.98018],[-69.97824,-52.00845],[-68.41683,-52.33516],[-68.60702,-52.65781],[-68.60733,-54.9125],[-68.01394,-54.8753],[-67.46182,-54.92205],[-67.11046,-54.94199],[-66.07313,-55.19618],[-63.07977,-55.04486],[-62.78369,-53.1401],[-62.3754,-50.36819],[-55.71154,-35.78518],[-57.83001,-34.69099],[-58.34425,-34.15035],[-58.44442,-33.84033],[-58.40475,-33.11777],[-58.1224,-32.98842],[-58.22362,-32.52416],[-58.10036,-32.25338],[-58.20252,-31.86966],[-58.00076,-31.65016],[-58.0023,-31.53084],[-58.07569,-31.44916],[-57.98127,-31.3872],[-57.9908,-31.34924],[-57.86729,-31.06352],[-57.89476,-30.95994],[-57.8024,-30.77193],[-57.89115,-30.49572],[-57.64859,-30.35095],[-57.61478,-30.25165],[-57.65132,-30.19229],[-57.09386,-29.74211],[-56.81251,-29.48154],[-56.62789,-29.18073],[-56.57295,-29.11357],[-56.54171,-29.11447],[-56.05265,-28.62651],[-56.00458,-28.60421],[-56.01729,-28.51223],[-55.65418,-28.18304],[-55.6262,-28.17124],[-55.33303,-27.94661],[-55.16872,-27.86224],[-55.1349,-27.89759],[-54.90805,-27.73149],[-54.90159,-27.63132],[-54.67657,-27.57214],[-54.50416,-27.48232],[-54.41888,-27.40882],[-54.19268,-27.30751],[-54.19062,-27.27639],[-54.15978,-27.2889],[-53.80144,-27.09844],[-53.73372,-26.6131],[-53.68269,-26.33359],[-53.64505,-26.28089],[-53.64186,-26.25976],[-53.64632,-26.24798],[-53.63881,-26.25075],[-53.63739,-26.2496],[-53.65237,-26.23289],[-53.65018,-26.19501],[-53.73968,-26.10012],[-53.73391,-26.07006],[-53.7264,-26.0664],[-53.73086,-26.05842],[-53.73511,-26.04211],[-53.83691,-25.94849],[-53.90831,-25.55513],[-54.52926,-25.62846],[-54.5502,-25.58915],[-54.59398,-25.59224],[-54.62063,-25.91213],[-54.60664,-25.9691],[-54.67359,-25.98607],[-54.69333,-26.37705],[-54.70732,-26.45099],[-54.80868,-26.55669],[-55.00584,-26.78754],[-55.06351,-26.80195],[-55.16948,-26.96068],[-55.25243,-26.93808],[-55.39611,-26.97679],[-55.62322,-27.1941],[-55.59094,-27.32444],[-55.74475,-27.44485],[-55.89195,-27.3467],[-56.18313,-27.29851],[-56.85337,-27.5165],[-58.04205,-27.2387],[-58.59549,-27.29973],[-58.65321,-27.14028],[-58.3198,-26.83443],[-58.1188,-26.16704],[-57.87176,-25.93604],[-57.57431,-25.47269],[-57.80821,-25.13863],[-58.25492,-24.92528],[-58.33055,-24.97099],[-59.33886,-24.49935],[-59.45482,-24.34787],[-60.03367,-24.00701],[-60.28163,-24.04436],[-60.99754,-23.80934],[-61.0782,-23.62932],[-61.9756,-23.0507],[-62.22768,-22.55807],[-62.51761,-22.37684],[-62.64455,-22.25091],[-62.8078,-22.12534],[-62.81124,-21.9987],[-63.66482,-21.99918],[-63.68113,-22.0544],[-63.70963,-21.99934],[-63.93287,-21.99934],[-64.22918,-22.55807],[-64.31489,-22.88824],[-64.35108,-22.73282],[-64.4176,-22.67692],[-64.58888,-22.25035],[-64.67174,-22.18957],[-64.90014,-22.12136],[-64.99524,-22.08255],[-65.47435,-22.08908],[-65.57743,-22.07675],[-65.58694,-22.09794],[-65.61166,-22.09504],[-65.7467,-22.10105],[-65.9261,-21.93335],[-66.04832,-21.9187],[-66.03836,-21.84829],[-66.24077,-21.77837],[-66.29714,-22.08741],[-66.7298,-22.23644],[-67.18382,-22.81525],[-66.99632,-22.99839],[-67.33563,-24.04237],[-68.24825,-24.42596],[-68.56909,-24.69831],[-68.38372,-25.08636],[-68.57622,-25.32505],[-68.38372,-26.15353],[-68.56909,-26.28146],[-68.59048,-26.49861],[-68.27677,-26.90626],[-68.43363,-27.08414],[-68.77586,-27.16029],[-69.22504,-27.95042],[-69.66709,-28.44055],[-69.80969,-29.07185],[-69.99507,-29.28351],[-69.8596,-30.26131],[-70.14479,-30.36595],[-70.55832,-31.51559],[-69.88099,-33.34489],[-69.87386,-34.13344],[-70.49416,-35.24145],[-70.38008,-36.02375],[-70.95047,-36.4321],[-71.24279,-37.20264],[-70.89532,-38.6923],[-71.37826,-38.91474],[-71.92726,-40.72714],[-71.74901,-42.11711],[-72.15541,-42.15941],[-72.14828,-42.85321],[-71.64206,-43.64774],[-71.81318,-44.38097],[-71.16436,-44.46244],[-71.26418,-44.75684],[-72.06985,-44.81756],[-71.35687,-45.22075],[-71.75614,-45.61611],[-71.68577,-46.55385],[-71.94152,-47.13595],[-72.50478,-47.80586],[-72.27662,-48.28727],[-72.54042,-48.52392],[-72.56894,-48.81116],[-73.09655,-49.14342],[-73.45156,-49.79461],[-73.55259,-49.92488]]]}},{"type":"Feature","properties":{"id":"CN-YN"},"geometry":{"type":"Polygon","coordinates":[[[97.5247,23.94032],[97.64667,23.84574],[97.72302,23.89288],[97.79456,23.94836],[97.79416,23.95663],[97.84328,23.97603],[97.86545,23.97723],[97.88811,23.97446],[97.8955,23.97758],[97.89676,23.97931],[97.89683,23.98389],[97.88814,23.98605],[97.88414,23.99405],[97.88616,24.00463],[97.90998,24.02094],[97.93951,24.01953],[97.98691,24.03897],[97.99583,24.04932],[98.04709,24.07616],[98.05302,24.07408],[98.05671,24.07961],[98.0607,24.07812],[98.06703,24.08028],[98.07806,24.07988],[98.20666,24.11406],[98.54476,24.13119],[98.59256,24.08371],[98.85319,24.13042],[98.87998,24.15624],[98.89632,24.10612],[98.67797,23.9644],[98.68209,23.80492],[98.79607,23.77947],[98.82933,23.72921],[98.81775,23.694],[98.88396,23.59555],[98.80294,23.5345],[98.82877,23.47908],[98.87683,23.48995],[98.92104,23.36946],[98.87573,23.33038],[98.93958,23.31414],[98.92515,23.29535],[98.88597,23.18656],[99.05975,23.16382],[99.04601,23.12215],[99.25741,23.09025],[99.34127,23.13099],[99.52214,23.08218],[99.54218,22.90014],[99.43537,22.94086],[99.45654,22.85726],[99.31243,22.73893],[99.38247,22.57544],[99.37972,22.50188],[99.28771,22.4105],[99.17318,22.18025],[99.19176,22.16983],[99.1552,22.15874],[99.33166,22.09656],[99.47585,22.13345],[99.85351,22.04183],[99.96612,22.05965],[99.99084,21.97053],[99.94003,21.82782],[99.98654,21.71064],[100.04956,21.66843],[100.12679,21.70539],[100.17486,21.65306],[100.10757,21.59945],[100.12542,21.50365],[100.1625,21.48704],[100.18447,21.51898],[100.25863,21.47043],[100.35201,21.53176],[100.42892,21.54325],[100.4811,21.46148],[100.57861,21.45637],[100.72143,21.51898],[100.87265,21.67396],[101.11744,21.77659],[101.15156,21.56129],[101.2124,21.56422],[101.19349,21.41959],[101.26912,21.36482],[101.2229,21.23271],[101.29326,21.17254],[101.54563,21.25668],[101.6068,21.23329],[101.59491,21.18621],[101.60886,21.17947],[101.66977,21.20004],[101.70548,21.14911],[101.7622,21.14813],[101.79266,21.19025],[101.76745,21.21571],[101.83887,21.20983],[101.84412,21.25291],[101.74014,21.30967],[101.74224,21.48276],[101.7727,21.51794],[101.7475,21.5873],[101.80001,21.57461],[101.83257,21.61562],[101.74555,21.72852],[101.7791,21.83019],[101.62566,21.96574],[101.57525,22.13026],[101.60675,22.13513],[101.53638,22.24794],[101.56789,22.28876],[101.61306,22.27515],[101.68973,22.46843],[101.7685,22.50337],[101.86828,22.38397],[101.90714,22.38688],[101.91344,22.44417],[101.98487,22.42766],[102.03633,22.46164],[102.1245,22.43372],[102.14099,22.40092],[102.16621,22.43336],[102.26428,22.41321],[102.25339,22.4607],[102.41061,22.64184],[102.38415,22.67919],[102.42618,22.69212],[102.46665,22.77108],[102.51802,22.77969],[102.57095,22.7036],[102.60675,22.73376],[102.8636,22.60735],[102.9321,22.48659],[103.0722,22.44775],[103.07843,22.50097],[103.17961,22.55705],[103.15782,22.59873],[103.18895,22.64471],[103.28079,22.68063],[103.32282,22.8127],[103.43179,22.75816],[103.43646,22.70648],[103.52675,22.59155],[103.57812,22.65764],[103.56255,22.69499],[103.64506,22.79979],[103.87904,22.56683],[103.93286,22.52703],[103.94513,22.52553],[103.95191,22.5134],[103.96352,22.50584],[103.96783,22.51173],[103.97384,22.50634],[103.99247,22.51958],[104.01088,22.51823],[104.03734,22.72945],[104.11384,22.80363],[104.27084,22.8457],[104.25683,22.76534],[104.35593,22.69353],[104.47225,22.75813],[104.58122,22.85571],[104.60457,22.81841],[104.65283,22.83419],[104.72755,22.81984],[104.77114,22.90017],[104.84942,22.93631],[104.86765,22.95178],[104.8334,23.01484],[104.79478,23.12934],[104.87382,23.12854],[104.87992,23.17141],[104.91435,23.18666],[104.9486,23.17235],[104.96532,23.20463],[104.98712,23.19176],[105.07002,23.26248],[105.11672,23.25247],[105.17276,23.28679],[105.22569,23.27249],[105.32376,23.39684],[105.40782,23.28107],[105.42805,23.30824],[105.49966,23.20669],[105.56037,23.16806],[105.59509,23.31766],[105.8773,23.53314],[106.00089,23.4519],[106.14097,23.5772],[106.15608,23.90592],[106.00364,24.12858],[105.64796,24.03831],[105.57174,24.13798],[105.49552,24.01949],[105.16868,24.14988],[105.19615,24.34209],[105.0238,24.43839],[104.71481,24.43777],[104.70588,24.31018],[104.56718,24.44527],[104.4659,24.65044],[104.52804,24.73498],[104.60151,24.89889],[104.71206,25.00348],[104.72545,25.19096],[104.83119,25.172],[104.78622,25.28195],[104.66022,25.2745],[104.52152,25.52695],[104.44633,25.47861],[104.309,25.65947],[104.47002,26.02007],[104.59258,26.31926],[104.68666,26.3691],[104.56031,26.59405],[104.47963,26.58422],[104.40925,26.73028],[104.34162,26.62444],[104.15279,26.67323],[104.00104,26.51389],[103.8153,26.53417],[103.7051,26.83173],[103.78063,26.949],[103.68621,27.06248],[103.61377,27.00591],[103.62716,27.11872],[103.83865,27.27202],[103.94165,27.45375],[104.18334,27.2708],[104.37217,27.47233],[104.50984,27.40712],[104.6046,27.30528],[104.85145,27.34523],[104.86312,27.28362],[105.08079,27.42114],[105.21057,27.37603],[105.31219,27.71088],[105.23254,27.90584],[105.17074,28.07258],[105.03616,28.09742],[104.87205,27.90766],[104.56409,27.85],[104.28909,28.05834],[104.44805,28.11801],[104.35432,28.34366],[104.30076,28.30679],[104.23725,28.54532],[104.46659,28.61556],[104.36977,28.65293],[104.08241,28.61014],[103.89015,28.62672],[103.85616,28.68004],[103.78749,28.51757],[103.85959,28.38807],[103.87367,28.30256],[103.76552,28.23453],[103.70304,28.19974],[103.63574,28.26719],[103.40434,28.04077],[103.57601,27.97135],[103.09295,27.39676],[102.93777,27.41078],[102.88352,27.25585],[102.91992,27.13248],[102.90412,26.90308],[103.05725,26.53079],[102.957,26.34019],[102.38708,26.29095],[102.10075,26.07652],[101.81167,26.05061],[101.79725,26.16653],[101.37908,26.60571],[101.46629,26.60387],[101.37428,26.88778],[101.15867,27.0316],[100.71166,27.83907],[100.33607,27.72608],[100.00785,28.21063],[99.96116,28.56582],[99.66384,28.82362],[99.38713,28.51033],[99.39742,28.16463],[99.15504,28.43488],[99.11487,29.22679],[98.96003,29.18663],[99.0184,29.03425],[98.92501,28.98111],[98.91815,28.88796],[98.97376,28.87564],[98.97548,28.82933],[98.83197,28.80406],[98.78974,29.01054],[98.62495,28.9721],[98.65447,28.8585],[98.66254,28.79549],[98.68125,28.73319],[98.63113,28.69103],[98.5968,28.68622],[98.63697,28.49072],[98.75884,28.33218],[98.69361,28.21789],[98.60881,28.1725],[98.39355,28.10953],[98.36952,28.26084],[98.28987,28.39804],[98.20129,28.35666],[98.26789,28.24421],[98.16696,28.21002],[98.15337,28.12114],[98.13964,27.9478],[98.32641,27.51385],[98.42529,27.55404],[98.43353,27.67086],[98.69582,27.56499],[98.7333,26.85615],[98.77547,26.61994],[98.72741,26.36183],[98.67797,26.24487],[98.7329,26.17218],[98.66884,26.09165],[98.63128,26.15492],[98.57085,26.11547],[98.60763,26.01512],[98.70818,25.86241],[98.63128,25.79937],[98.54064,25.85129],[98.40606,25.61129],[98.31268,25.55307],[98.25774,25.6051],[98.16848,25.62739],[98.18084,25.56298],[98.12591,25.50722],[98.14925,25.41547],[97.92541,25.20815],[97.83614,25.2715],[97.77023,25.11492],[97.72216,25.08508],[97.72903,24.91332],[97.79949,24.85655],[97.76481,24.8289],[97.73127,24.83015],[97.70181,24.84557],[97.64354,24.79171],[97.56648,24.76475],[97.56383,24.75535],[97.5542,24.74943],[97.54675,24.74202],[97.56525,24.72838],[97.56286,24.54535],[97.52757,24.43748],[97.60029,24.4401],[97.66998,24.45288],[97.7098,24.35658],[97.65624,24.33781],[97.66723,24.30027],[97.71941,24.29652],[97.76799,24.26365],[97.72998,24.2302],[97.72799,24.18883],[97.75305,24.16902],[97.72903,24.12606],[97.62363,24.00506],[97.5247,23.94032]]]}},{"type":"Feature","properties":{"id":"IN-TR"},"geometry":{"type":"Polygon","coordinates":[[[91.15579,23.6599],[91.28293,23.37538],[91.36453,23.06612],[91.40848,23.07117],[91.4035,23.27522],[91.46615,23.2328],[91.54993,23.01051],[91.61571,22.93929],[91.7324,23.00043],[91.81634,23.08001],[91.76417,23.26619],[91.84789,23.42235],[91.95642,23.47361],[91.95093,23.73284],[92.04706,23.64229],[92.15417,23.73409],[92.26541,23.70392],[92.31777,23.86543],[92.29819,24.25406],[92.21786,24.25259],[92.27931,24.39213],[92.22953,24.50245],[92.15796,24.54435],[92.11662,24.38997],[91.96603,24.3799],[91.89258,24.14674],[91.82596,24.22345],[91.76004,24.23848],[91.73257,24.14703],[91.65292,24.22095],[91.63782,24.1132],[91.55542,24.08687],[91.37414,24.10693],[91.35741,23.99072],[91.29587,24.0041],[91.22308,23.89616],[91.25192,23.83463],[91.15579,23.6599]]]}},{"type":"Feature","properties":{"id":"AT"},"geometry":{"type":"Polygon","coordinates":[[[9.53116,47.27029],[9.56766,47.24281],[9.55176,47.22585],[9.56981,47.21926],[9.58264,47.20673],[9.56539,47.17124],[9.62623,47.14685],[9.63395,47.08443],[9.61216,47.07732],[9.60717,47.06091],[9.87935,47.01337],[9.88266,46.93343],[9.98058,46.91434],[10.10715,46.84296],[10.22675,46.86942],[10.24128,46.93147],[10.30031,46.92093],[10.36933,47.00212],[10.48376,46.93891],[10.47197,46.85698],[10.54783,46.84505],[10.66405,46.87614],[10.75753,46.82258],[10.72974,46.78972],[11.00764,46.76896],[11.10618,46.92966],[11.33355,46.99862],[11.50739,47.00644],[11.74789,46.98484],[12.19254,47.09331],[12.21781,47.03996],[12.11675,47.01241],[12.2006,46.88854],[12.27591,46.88651],[12.38708,46.71529],[12.59992,46.6595],[12.94445,46.60401],[13.27627,46.56059],[13.64088,46.53438],[13.7148,46.5222],[13.89837,46.52331],[14.00422,46.48474],[14.04002,46.49117],[14.12097,46.47724],[14.15989,46.43327],[14.28326,46.44315],[14.314,46.43327],[14.42608,46.44614],[14.45877,46.41717],[14.52176,46.42617],[14.56463,46.37208],[14.5942,46.43434],[14.66892,46.44936],[14.72185,46.49974],[14.81836,46.51046],[14.83549,46.56614],[14.86419,46.59411],[14.87129,46.61],[14.92283,46.60848],[14.96002,46.63459],[14.98024,46.6009],[15.01451,46.641],[15.14215,46.66131],[15.23711,46.63994],[15.41235,46.65556],[15.45514,46.63697],[15.46906,46.61321],[15.54431,46.6312],[15.55333,46.64988],[15.54533,46.66985],[15.59826,46.68908],[15.62317,46.67947],[15.63255,46.68069],[15.6365,46.6894],[15.6543,46.69228],[15.6543,46.70616],[15.67411,46.70735],[15.69523,46.69823],[15.72279,46.69548],[15.73823,46.70011],[15.76771,46.69863],[15.78518,46.70712],[15.8162,46.71897],[15.87691,46.7211],[15.94864,46.68769],[15.98512,46.68463],[15.99988,46.67947],[16.04036,46.6549],[16.04347,46.68694],[16.02808,46.71094],[15.99769,46.7266],[15.98432,46.74991],[15.99126,46.78199],[15.99054,46.82772],[16.05786,46.83927],[16.10983,46.867],[16.19904,46.94134],[16.22403,46.939],[16.27594,46.9643],[16.28202,47.00159],[16.51369,47.00084],[16.43936,47.03548],[16.52176,47.05747],[16.46134,47.09395],[16.52863,47.13974],[16.44932,47.14418],[16.46442,47.16845],[16.4523,47.18812],[16.42801,47.18422],[16.41739,47.20649],[16.43663,47.21127],[16.44142,47.25079],[16.47782,47.25918],[16.45104,47.41181],[16.49908,47.39416],[16.52414,47.41007],[16.57152,47.40868],[16.6718,47.46139],[16.64821,47.50155],[16.71059,47.52692],[16.64193,47.63114],[16.58699,47.61772],[16.4222,47.66537],[16.55129,47.72268],[16.53514,47.73837],[16.54779,47.75074],[16.61183,47.76171],[16.65679,47.74197],[16.72089,47.73469],[16.7511,47.67878],[16.82938,47.68432],[16.86509,47.72268],[16.87538,47.68895],[17.08893,47.70928],[17.05048,47.79377],[17.07039,47.81129],[17.00997,47.86245],[17.08275,47.87719],[17.11022,47.92461],[17.09786,47.97336],[17.16001,48.00636],[17.07039,48.0317],[17.09168,48.09366],[17.05735,48.14179],[17.02919,48.13996],[16.97701,48.17385],[16.89461,48.31332],[16.90903,48.32519],[16.84243,48.35258],[16.83317,48.38138],[16.83588,48.3844],[16.8497,48.38321],[16.85204,48.44968],[16.94611,48.53614],[16.93955,48.60371],[16.90354,48.71541],[16.79779,48.70998],[16.71883,48.73806],[16.68518,48.7281],[16.67008,48.77699],[16.46134,48.80865],[16.40915,48.74576],[16.37345,48.729],[16.06034,48.75436],[15.84404,48.86921],[15.78087,48.87644],[15.75341,48.8516],[15.6921,48.85973],[15.61622,48.89541],[15.51357,48.91549],[15.48027,48.94481],[15.34823,48.98444],[15.28305,48.98831],[15.26177,48.95766],[15.16358,48.94278],[15.15534,48.99056],[14.99878,49.01444],[14.97612,48.96983],[14.98917,48.90082],[14.95072,48.79101],[14.98032,48.77959],[14.9782,48.7766],[14.98112,48.77524],[14.9758,48.76857],[14.95641,48.75915],[14.94773,48.76268],[14.81545,48.7874],[14.80821,48.77711],[14.80584,48.73489],[14.72756,48.69502],[14.71794,48.59794],[14.66762,48.58215],[14.60808,48.62881],[14.56139,48.60429],[14.4587,48.64695],[14.43076,48.58855],[14.33909,48.55852],[14.20691,48.5898],[14.09104,48.5943],[14.01482,48.63788],[14.06151,48.66873],[13.84023,48.76988],[13.82266,48.75544],[13.81863,48.73257],[13.79337,48.71375],[13.81791,48.69832],[13.81283,48.68426],[13.81901,48.6761],[13.82609,48.62345],[13.80038,48.59487],[13.80519,48.58026],[13.76921,48.55324],[13.7513,48.5624],[13.74816,48.53058],[13.72802,48.51208],[13.66113,48.53558],[13.65186,48.55092],[13.62508,48.55501],[13.59705,48.57013],[13.57535,48.55912],[13.51291,48.59023],[13.50131,48.58091],[13.50663,48.57506],[13.46967,48.55157],[13.45214,48.56472],[13.43695,48.55776],[13.45727,48.51092],[13.42527,48.45711],[13.43929,48.43386],[13.40709,48.37292],[13.30897,48.31575],[13.26039,48.29422],[13.18093,48.29577],[13.126,48.27867],[13.0851,48.27711],[13.02083,48.25689],[12.95306,48.20629],[12.87126,48.20318],[12.84475,48.16556],[12.836,48.1647],[12.8362,48.15876],[12.82673,48.15245],[12.80676,48.14979],[12.78595,48.12445],[12.7617,48.12796],[12.74973,48.10885],[12.76141,48.07373],[12.8549,48.01122],[12.87476,47.96195],[12.91683,47.95647],[12.9211,47.95135],[12.91985,47.94069],[12.92668,47.93879],[12.93419,47.94063],[12.93642,47.94436],[12.93886,47.94046],[12.94163,47.92927],[13.00588,47.84374],[12.98543,47.82896],[12.96311,47.79957],[12.93202,47.77302],[12.94371,47.76281],[12.9353,47.74788],[12.91711,47.74026],[12.90274,47.72513],[12.91333,47.7178],[12.92969,47.71094],[12.98578,47.7078],[13.01382,47.72116],[13.07692,47.68814],[13.09562,47.63304],[13.06407,47.60075],[13.06641,47.58577],[13.04537,47.58183],[13.05355,47.56291],[13.03252,47.53373],[13.04537,47.49426],[12.9998,47.46267],[12.98344,47.48716],[12.9624,47.47452],[12.85256,47.52741],[12.84672,47.54556],[12.80699,47.54477],[12.77427,47.58025],[12.82101,47.61493],[12.76492,47.64485],[12.77777,47.66689],[12.7357,47.6787],[12.6071,47.6741],[12.57438,47.63238],[12.53816,47.63553],[12.50076,47.62293],[12.44117,47.6741],[12.43883,47.6977],[12.37222,47.68433],[12.336,47.69534],[12.27991,47.68827],[12.26004,47.67725],[12.24017,47.69534],[12.26238,47.73544],[12.2542,47.7433],[12.22571,47.71776],[12.18303,47.70065],[12.16217,47.70105],[12.16769,47.68167],[12.18347,47.66663],[12.18507,47.65984],[12.19895,47.64085],[12.20801,47.61082],[12.20398,47.60667],[12.18568,47.6049],[12.17737,47.60121],[12.18145,47.61019],[12.17824,47.61506],[12.13734,47.60639],[12.05788,47.61742],[12.02282,47.61033],[12.0088,47.62451],[11.85572,47.60166],[11.84052,47.58354],[11.63934,47.59202],[11.60681,47.57881],[11.58811,47.55515],[11.58578,47.52281],[11.52618,47.50939],[11.4362,47.51413],[11.38128,47.47465],[11.4175,47.44621],[11.33804,47.44937],[11.29597,47.42566],[11.27844,47.39956],[11.22002,47.3964],[11.25157,47.43277],[11.20482,47.43198],[11.12536,47.41222],[11.11835,47.39719],[10.97111,47.39561],[10.97111,47.41617],[10.98513,47.42882],[10.92437,47.46991],[10.93839,47.48018],[10.90918,47.48571],[10.87061,47.4786],[10.86945,47.5015],[10.91268,47.51334],[10.88814,47.53701],[10.77596,47.51729],[10.7596,47.53228],[10.6965,47.54253],[10.68832,47.55752],[10.63456,47.5591],[10.60337,47.56755],[10.56912,47.53584],[10.48849,47.54057],[10.47329,47.58552],[10.43473,47.58394],[10.44992,47.5524],[10.4324,47.50111],[10.44291,47.48453],[10.46278,47.47901],[10.47446,47.43318],[10.4359,47.41183],[10.4324,47.38494],[10.39851,47.37623],[10.33424,47.30813],[10.23257,47.27088],[10.17531,47.27167],[10.17648,47.29149],[10.2147,47.31014],[10.19998,47.32832],[10.23757,47.37609],[10.22774,47.38904],[10.2127,47.38019],[10.17648,47.38889],[10.16362,47.36674],[10.11805,47.37228],[10.09819,47.35724],[10.06897,47.40709],[10.1052,47.4316],[10.09001,47.46005],[10.07131,47.45531],[10.03859,47.48927],[10.00003,47.48216],[9.96029,47.53899],[9.92407,47.53111],[9.87733,47.54688],[9.87499,47.52953],[9.8189,47.54688],[9.82591,47.58158],[9.80254,47.59419],[9.76748,47.5934],[9.72736,47.53457],[9.55125,47.53629],[9.56312,47.49495],[9.58208,47.48344],[9.59482,47.46305],[9.60205,47.46165],[9.60484,47.46358],[9.60841,47.47178],[9.62158,47.45858],[9.62475,47.45685],[9.6423,47.45599],[9.65728,47.45383],[9.65863,47.44847],[9.64483,47.43842],[9.6446,47.43233],[9.65043,47.41937],[9.65136,47.40504],[9.6629,47.39591],[9.67334,47.39191],[9.67445,47.38429],[9.6711,47.37824],[9.66243,47.37136],[9.65427,47.36824],[9.62476,47.36639],[9.59978,47.34671],[9.58513,47.31334],[9.55857,47.29919],[9.54773,47.2809],[9.53116,47.27029]]]}},{"type":"Feature","properties":{"id":"AZ-NX"},"geometry":{"type":"Polygon","coordinates":[[[44.75779,39.7148],[44.80977,39.65768],[44.81043,39.62677],[44.88916,39.59653],[44.96746,39.42998],[45.05932,39.36435],[45.08751,39.35052],[45.16168,39.21952],[45.30489,39.18333],[45.40148,39.09007],[45.40452,39.07224],[45.44811,39.04927],[45.44966,38.99243],[45.6131,38.964],[45.6155,38.94304],[45.65172,38.95199],[45.83883,38.90768],[45.90266,38.87739],[45.94624,38.89072],[46.00228,38.87376],[46.06766,38.87861],[46.14785,38.84206],[46.06973,39.0744],[46.02303,39.09978],[45.99774,39.28931],[45.79225,39.3695],[45.83,39.46487],[45.80804,39.56716],[45.70547,39.60174],[45.46992,39.49888],[45.29606,39.57654],[45.30385,39.61373],[45.23535,39.61373],[45.21784,39.58074],[45.17464,39.58614],[45.18554,39.67846],[45.06604,39.79277],[44.92869,39.72157],[44.88354,39.74432],[44.75779,39.7148]]]}},{"type":"Feature","properties":{"id":"BI"},"geometry":{"type":"Polygon","coordinates":[[[29.00167,-2.78523],[29.00404,-2.81978],[29.0505,-2.81774],[29.09119,-2.87871],[29.09797,-2.91935],[29.16037,-2.95457],[29.17258,-2.99385],[29.25633,-3.05471],[29.21463,-3.3514],[29.23708,-3.75856],[29.43673,-4.44845],[29.63827,-4.44681],[29.75109,-4.45836],[29.77289,-4.41733],[29.82885,-4.36153],[29.88172,-4.35743],[30.03323,-4.26631],[30.22042,-4.01738],[30.45915,-3.56532],[30.84165,-3.25152],[30.83823,-2.97837],[30.6675,-2.98987],[30.57926,-2.89791],[30.4987,-2.9573],[30.40662,-2.86151],[30.52747,-2.65841],[30.41789,-2.66266],[30.54501,-2.41404],[30.42933,-2.31064],[30.14034,-2.43626],[29.95911,-2.33348],[29.88237,-2.75105],[29.36805,-2.82933],[29.32234,-2.6483],[29.0562,-2.58632],[29.04081,-2.7416],[29.00167,-2.78523]]]}},{"type":"Feature","properties":{"id":"BJ"},"geometry":{"type":"Polygon","coordinates":[[[0.77666,10.37665],[1.35507,9.99525],[1.36624,9.5951],[1.33675,9.54765],[1.41746,9.3226],[1.5649,9.16941],[1.61838,9.0527],[1.64249,6.99562],[1.55877,6.99737],[1.61812,6.74843],[1.58105,6.68619],[1.76906,6.43189],[1.79826,6.28221],[1.62913,6.24075],[1.67336,6.02702],[2.74181,6.13349],[2.70566,6.38038],[2.70464,6.50831],[2.74334,6.57291],[2.7325,6.64057],[2.78204,6.70514],[2.78823,6.76356],[2.73405,6.78508],[2.74024,6.92802],[2.71702,6.95722],[2.76965,7.13543],[2.74489,7.42565],[2.79442,7.43486],[2.78668,7.5116],[2.73405,7.5423],[2.73095,7.7755],[2.67523,7.87825],[2.77907,9.06924],[3.08017,9.10006],[3.14147,9.28375],[3.13928,9.47167],[3.25093,9.61632],[3.34726,9.70696],[3.32099,9.78032],[3.35383,9.83641],[3.54429,9.87739],[3.66908,10.18136],[3.57275,10.27185],[3.6844,10.46351],[3.78292,10.40538],[3.84243,10.59316],[3.71505,11.13015],[3.49175,11.29765],[3.59375,11.70269],[3.48187,11.86092],[3.31613,11.88495],[3.25352,12.01467],[2.83978,12.40585],[2.6593,12.30631],[2.37783,12.24804],[2.39657,12.10952],[2.45824,11.98672],[2.39723,11.89473],[2.29983,11.68254],[2.00988,11.42227],[1.42823,11.46822],[1.03409,11.04719],[0.9813,11.08876],[0.91245,10.99597],[0.8804,10.803],[0.80358,10.71459],[0.77666,10.37665]]]}},{"type":"Feature","properties":{"id":"BF"},"geometry":{"type":"Polygon","coordinates":[[[-5.51058,10.43177],[-5.39602,10.2929],[-5.12465,10.29788],[-4.96453,9.99923],[-4.96621,9.89132],[-4.6426,9.70696],[-4.31392,9.60062],[-4.25999,9.76012],[-3.69703,9.94279],[-3.31779,9.91125],[-3.27228,9.84981],[-3.19306,9.93781],[-3.16609,9.85147],[-3.00765,9.74019],[-2.93012,9.57403],[-2.76494,9.40778],[-2.68802,9.49343],[-2.76534,9.56589],[-2.74174,9.83172],[-2.83108,10.40252],[-2.94232,10.64281],[-2.83373,11.0067],[-0.67143,10.99811],[-0.61937,10.91305],[-0.44298,11.04292],[-0.42391,11.11661],[-0.38219,11.12596],[-0.35955,11.07801],[-0.28566,11.12713],[-0.27374,11.17157],[-0.13493,11.14075],[0.50388,11.01011],[0.48852,10.98561],[0.50521,10.98035],[0.4958,10.93269],[0.66104,10.99964],[0.91245,10.99597],[0.9813,11.08876],[1.03409,11.04719],[1.42823,11.46822],[2.00988,11.42227],[2.29983,11.68254],[2.39723,11.89473],[2.05785,12.35539],[2.26349,12.41915],[0.99167,13.10727],[0.99253,13.37515],[1.18873,13.31771],[1.21217,13.37853],[1.24516,13.33968],[1.28509,13.35488],[1.24429,13.39373],[1.20088,13.38951],[1.02813,13.46635],[0.99514,13.5668],[0.77637,13.64442],[0.77377,13.6866],[0.61924,13.68491],[0.38051,14.05575],[0.16936,14.51654],[0.23859,15.00135],[0.06588,14.96961],[-0.24673,15.07805],[-0.72004,15.08655],[-1.05875,14.7921],[-1.32166,14.72774],[-1.68083,14.50023],[-1.97945,14.47709],[-1.9992,14.19011],[-2.10223,14.14878],[-2.47587,14.29671],[-2.66175,14.14713],[-2.84667,14.05532],[-2.90831,13.81174],[-2.88189,13.64921],[-3.26407,13.70699],[-3.28396,13.5422],[-3.23599,13.29035],[-3.43507,13.27272],[-3.4313,13.1588],[-3.54454,13.1781],[-3.7911,13.36665],[-3.96282,13.38164],[-3.90558,13.44375],[-3.96501,13.49778],[-4.34477,13.12927],[-4.21819,12.95722],[-4.238,12.71467],[-4.47356,12.71252],[-4.41412,12.31922],[-4.57703,12.19875],[-4.54841,12.1385],[-4.62546,12.13204],[-4.62987,12.06531],[-4.70692,12.06746],[-4.72893,12.01579],[-5.07897,11.97918],[-5.26389,11.84778],[-5.40258,11.8327],[-5.26389,11.75728],[-5.29251,11.61715],[-5.22867,11.60421],[-5.20665,11.43811],[-5.25509,11.36905],[-5.25949,11.24816],[-5.32553,11.21578],[-5.32994,11.13371],[-5.49284,11.07538],[-5.41579,10.84628],[-5.47083,10.75329],[-5.46643,10.56074],[-5.51058,10.43177]]]}},{"type":"Feature","properties":{"id":"BD"},"geometry":{"type":"Polygon","coordinates":[[[88.00683,24.66477],[88.08786,24.63232],[88.12296,24.51301],[88.50934,24.32474],[88.68801,24.31464],[88.74841,24.1959],[88.6976,24.14703],[88.73743,23.91751],[88.66189,23.87607],[88.58087,23.87105],[88.56507,23.64044],[88.74841,23.47361],[88.79351,23.50535],[88.79254,23.46028],[88.71133,23.2492],[88.99148,23.21134],[88.86377,23.08759],[88.88327,23.03885],[88.87063,22.95235],[88.96713,22.83346],[88.9151,22.75228],[88.94614,22.66941],[88.9367,22.58527],[89.07114,22.15335],[89.03553,21.77397],[89.13927,21.60785],[89.13606,21.42955],[92.39837,20.38919],[92.4302,20.5688],[92.31348,20.57137],[92.28464,20.63179],[92.37665,20.72172],[92.26071,21.05697],[92.17752,21.17445],[92.20087,21.337],[92.37939,21.47764],[92.43158,21.37025],[92.55105,21.3856],[92.60187,21.24615],[92.68152,21.28454],[92.59775,21.6092],[92.62187,21.87037],[92.60949,21.97638],[92.56616,22.13554],[92.60029,22.1522],[92.5181,22.71441],[92.37665,22.9435],[92.38214,23.28705],[92.26541,23.70392],[92.15417,23.73409],[92.04706,23.64229],[91.95093,23.73284],[91.95642,23.47361],[91.84789,23.42235],[91.76417,23.26619],[91.81634,23.08001],[91.7324,23.00043],[91.61571,22.93929],[91.54993,23.01051],[91.46615,23.2328],[91.4035,23.27522],[91.40848,23.07117],[91.36453,23.06612],[91.28293,23.37538],[91.15579,23.6599],[91.25192,23.83463],[91.22308,23.89616],[91.29587,24.0041],[91.35741,23.99072],[91.37414,24.10693],[91.55542,24.08687],[91.63782,24.1132],[91.65292,24.22095],[91.73257,24.14703],[91.76004,24.23848],[91.82596,24.22345],[91.89258,24.14674],[91.96603,24.3799],[92.11662,24.38997],[92.15796,24.54435],[92.25854,24.9191],[92.38626,24.86055],[92.49887,24.88796],[92.39147,25.01471],[92.33957,25.07593],[92.0316,25.1834],[91.63648,25.12846],[91.25517,25.20677],[90.87427,25.15799],[90.65042,25.17788],[90.40034,25.1534],[90.1155,25.22686],[89.90478,25.31038],[89.87629,25.28337],[89.83371,25.29548],[89.84086,25.31854],[89.81208,25.37244],[89.86129,25.61714],[89.84388,25.70042],[89.80585,25.82489],[89.86592,25.93115],[89.77728,26.04254],[89.77865,26.08387],[89.73581,26.15818],[89.70201,26.15138],[89.63968,26.22595],[89.57101,25.9682],[89.53515,26.00382],[89.35953,26.0077],[89.15869,26.13708],[89.08899,26.38845],[88.95612,26.4564],[88.92357,26.40711],[88.91321,26.37984],[89.05328,26.2469],[88.85004,26.23211],[88.78961,26.31093],[88.67837,26.26291],[88.69485,26.38353],[88.62144,26.46783],[88.4298,26.54489],[88.41196,26.63837],[88.33093,26.48929],[88.35153,26.45241],[88.36938,26.48683],[88.48749,26.45855],[88.51649,26.35923],[88.35153,26.29123],[88.34757,26.22216],[88.1844,26.14417],[88.16581,26.0238],[88.08804,25.91334],[88.13138,25.78773],[88.242,25.80811],[88.45103,25.66245],[88.4559,25.59227],[88.677,25.46959],[88.81296,25.51546],[88.85278,25.34679],[89.01105,25.30303],[89.00463,25.26583],[88.94067,25.18534],[88.44766,25.20149],[88.46277,25.07468],[88.33917,24.86803],[88.27325,24.88796],[88.21832,24.96642],[88.14004,24.93529],[88.15515,24.85806],[88.00683,24.66477]]]}},{"type":"Feature","properties":{"id":"BG"},"geometry":{"type":"Polygon","coordinates":[[[22.34773,42.31725],[22.38136,42.30339],[22.47251,42.20393],[22.50289,42.19527],[22.51224,42.15457],[22.67701,42.06614],[22.86749,42.02275],[22.90254,41.87587],[22.96682,41.77137],[23.01239,41.76527],[23.03342,41.71034],[22.95513,41.63265],[22.96331,41.35782],[22.93334,41.34104],[23.1833,41.31755],[23.21953,41.33773],[23.22771,41.37106],[23.31301,41.40525],[23.33639,41.36317],[23.40416,41.39999],[23.52453,41.40262],[23.63203,41.37632],[23.67644,41.41139],[23.76525,41.40175],[23.80148,41.43943],[23.89613,41.45257],[23.91483,41.47971],[23.96975,41.44118],[24.06908,41.46132],[24.06323,41.53222],[24.10063,41.54796],[24.18126,41.51735],[24.27124,41.57682],[24.30513,41.51297],[24.52599,41.56808],[24.61129,41.42278],[24.71529,41.41928],[24.8041,41.34913],[24.82514,41.4035],[24.86136,41.39298],[24.90928,41.40876],[24.942,41.38685],[25.11611,41.34212],[25.28322,41.23411],[25.48187,41.28506],[25.52394,41.2798],[25.55082,41.31667],[25.61042,41.30614],[25.66183,41.31316],[25.70507,41.29209],[25.8266,41.34563],[25.87919,41.30526],[26.12926,41.35878],[26.16548,41.42278],[26.20288,41.43943],[26.14796,41.47533],[26.176,41.50072],[26.17951,41.55409],[26.14328,41.55496],[26.15146,41.60828],[26.07083,41.64584],[26.06148,41.70345],[26.16841,41.74858],[26.21325,41.73223],[26.22888,41.74139],[26.2654,41.71544],[26.30255,41.70925],[26.35957,41.71149],[26.32952,41.73637],[26.33589,41.76802],[26.36952,41.82265],[26.53968,41.82653],[26.57961,41.90024],[26.56051,41.92995],[26.62996,41.97644],[26.79143,41.97386],[26.95638,42.00741],[27.03277,42.0809],[27.08486,42.08735],[27.19251,42.06028],[27.22376,42.10152],[27.27411,42.10409],[27.45478,41.96591],[27.52379,41.93756],[27.55191,41.90928],[27.69949,41.97515],[27.81235,41.94803],[27.83492,41.99709],[27.91479,41.97902],[28.02971,41.98066],[28.32297,41.98371],[29.24336,43.70874],[28.23293,43.76],[27.99558,43.84193],[27.92008,44.00761],[27.73468,43.95326],[27.64542,44.04958],[27.60834,44.01206],[27.39757,44.0141],[27.26845,44.12602],[26.95141,44.13555],[26.62712,44.05698],[26.38764,44.04356],[26.10115,43.96908],[26.05584,43.90925],[25.94911,43.85745],[25.72792,43.69263],[25.39528,43.61866],[25.17144,43.70261],[25.10718,43.6831],[24.96682,43.72693],[24.73542,43.68523],[24.62281,43.74082],[24.50264,43.76314],[24.35364,43.70211],[24.18149,43.68218],[23.73978,43.80627],[23.61687,43.79289],[23.4507,43.84936],[23.26772,43.84843],[23.05288,43.79494],[22.85314,43.84452],[22.83753,43.88055],[22.87873,43.9844],[23.01674,44.01946],[23.04988,44.07694],[22.67173,44.21564],[22.61711,44.16938],[22.61688,44.06534],[22.41449,44.00514],[22.35558,43.81281],[22.41043,43.69566],[22.47582,43.6558],[22.53397,43.47225],[22.82036,43.33665],[22.89727,43.22417],[23.00806,43.19279],[22.98104,43.11199],[22.89521,43.03625],[22.78397,42.98253],[22.74826,42.88701],[22.54302,42.87774],[22.43309,42.82057],[22.4997,42.74144],[22.43983,42.56851],[22.55669,42.50144],[22.51961,42.3991],[22.47498,42.3915],[22.45919,42.33822],[22.34773,42.31725]]]}},{"type":"Feature","properties":{"id":"BH"},"geometry":{"type":"Polygon","coordinates":[[[50.26923,26.08243],[50.302,25.87592],[50.57069,25.57887],[50.80824,25.54641],[50.7801,25.595],[50.86149,25.6965],[50.81266,25.88946],[50.93865,26.30758],[50.71771,26.73086],[50.38162,26.53976],[50.26923,26.08243]]]}},{"type":"Feature","properties":{"id":"BA"},"geometry":{"type":"Polygon","coordinates":[[[15.72584,44.82334],[15.8255,44.71501],[15.89348,44.74964],[16.05828,44.61538],[16.00884,44.58605],[16.03012,44.55572],[16.10566,44.52586],[16.16814,44.40679],[16.12969,44.38275],[16.21346,44.35231],[16.18688,44.27012],[16.36864,44.08263],[16.43662,44.07523],[16.43629,44.02826],[16.50528,44.0244],[16.55472,43.95326],[16.70922,43.84887],[16.75316,43.77157],[16.80736,43.76011],[17.00585,43.58037],[17.15828,43.49376],[17.24411,43.49376],[17.29699,43.44542],[17.25579,43.40353],[17.286,43.33065],[17.46986,43.16559],[17.64268,43.08595],[17.70879,42.97223],[17.5392,42.92787],[17.6444,42.88641],[17.68151,42.92725],[17.7948,42.89556],[17.80854,42.9182],[17.88201,42.83668],[18.24318,42.6112],[18.36197,42.61423],[18.43735,42.55921],[18.49778,42.58409],[18.53751,42.57376],[18.55504,42.58409],[18.52232,42.62279],[18.57373,42.64429],[18.54841,42.68328],[18.54603,42.69171],[18.55221,42.69045],[18.56789,42.72074],[18.47324,42.74992],[18.45921,42.81682],[18.47633,42.85829],[18.4935,42.86433],[18.49661,42.89306],[18.49076,42.95553],[18.52232,43.01451],[18.66254,43.03928],[18.64735,43.14766],[18.66605,43.2056],[18.71747,43.2286],[18.6976,43.25243],[18.76538,43.29838],[18.85342,43.32426],[18.84794,43.33735],[18.83912,43.34795],[18.90911,43.36383],[18.95819,43.32899],[18.95001,43.29327],[19.00844,43.24988],[19.04233,43.30008],[19.08206,43.29668],[19.08673,43.31453],[19.04071,43.397],[19.01078,43.43854],[18.96053,43.45042],[18.95469,43.49367],[18.91379,43.50299],[19.01078,43.55806],[19.04934,43.50384],[19.13933,43.5282],[19.15685,43.53943],[19.22807,43.5264],[19.24774,43.53061],[19.2553,43.5938],[19.33426,43.58833],[19.36653,43.60921],[19.41941,43.54056],[19.42696,43.57987],[19.50455,43.58385],[19.5176,43.71403],[19.3986,43.79668],[19.23465,43.98764],[19.24363,44.01502],[19.38439,43.96611],[19.52515,43.95573],[19.56498,43.99922],[19.61836,44.01464],[19.61991,44.05254],[19.57467,44.04716],[19.55999,44.06894],[19.51167,44.08158],[19.47321,44.1193],[19.48386,44.14332],[19.47338,44.15034],[19.43905,44.13088],[19.40927,44.16722],[19.3588,44.18353],[19.34773,44.23244],[19.32464,44.27185],[19.26945,44.26957],[19.23306,44.26097],[19.20508,44.2917],[19.18328,44.28383],[19.16741,44.28648],[19.13332,44.31492],[19.13556,44.338],[19.11547,44.34218],[19.1083,44.3558],[19.11865,44.36712],[19.10298,44.36924],[19.10365,44.37795],[19.10704,44.38249],[19.10749,44.39421],[19.11785,44.40313],[19.14681,44.41463],[19.14837,44.45253],[19.12278,44.50132],[19.13369,44.52521],[19.16699,44.52197],[19.26388,44.65412],[19.32543,44.74058],[19.36722,44.88164],[19.18183,44.92055],[19.01994,44.85493],[18.8704,44.85097],[18.76347,44.90669],[18.76369,44.93707],[18.80661,44.93561],[18.78357,44.97741],[18.65723,45.07544],[18.47939,45.05871],[18.41896,45.11083],[18.32077,45.1021],[18.24387,45.13699],[18.1624,45.07654],[18.03121,45.12632],[18.01594,45.15163],[17.99479,45.14958],[17.97834,45.13831],[17.97336,45.12245],[17.93706,45.08016],[17.87148,45.04645],[17.84826,45.04489],[17.66571,45.13408],[17.59104,45.10816],[17.51469,45.10791],[17.47589,45.12656],[17.45615,45.12523],[17.4498,45.16119],[17.41229,45.13335],[17.33573,45.14521],[17.32092,45.16246],[17.26815,45.18444],[17.25131,45.14957],[17.24325,45.146],[17.18438,45.14764],[17.0415,45.20759],[16.9385,45.22742],[16.92405,45.27607],[16.83804,45.18951],[16.81137,45.18434],[16.78219,45.19002],[16.74845,45.20393],[16.64962,45.20714],[16.60194,45.23042],[16.56559,45.22307],[16.5501,45.2212],[16.52982,45.22713],[16.49155,45.21153],[16.4634,45.14522],[16.40023,45.1147],[16.38309,45.05955],[16.38219,45.05139],[16.3749,45.05206],[16.35863,45.03529],[16.35404,45.00241],[16.29036,44.99732],[16.12153,45.09616],[15.98412,45.23088],[15.83512,45.22459],[15.76371,45.16508],[15.78842,45.11519],[15.74585,45.0638],[15.78568,44.97401],[15.74723,44.96818],[15.76096,44.87045],[15.79472,44.8455],[15.72584,44.82334]]]}},{"type":"Feature","properties":{"id":"BY"},"geometry":{"type":"Polygon","coordinates":[[[23.18196,52.28812],[23.20071,52.22848],[23.47859,52.18215],[23.54314,52.12148],[23.61,52.11264],[23.64066,52.07626],[23.68733,51.9906],[23.61523,51.92066],[23.62691,51.78208],[23.53198,51.74298],[23.57053,51.55938],[23.56236,51.53673],[23.62751,51.50512],[23.6736,51.50255],[23.60906,51.62122],[23.7766,51.66809],[23.91118,51.63316],[23.8741,51.59734],[23.99907,51.58369],[24.13075,51.66979],[24.3163,51.75063],[24.29021,51.80841],[24.37123,51.88222],[24.98784,51.91273],[25.20228,51.97143],[25.46163,51.92205],[25.73673,51.91973],[25.80574,51.94556],[25.83217,51.92587],[26.00408,51.92967],[26.19084,51.86781],[26.39367,51.87315],[26.46962,51.80501],[26.69759,51.82284],[26.80043,51.75777],[26.9489,51.73788],[26.99422,51.76933],[27.20602,51.77291],[27.20948,51.66713],[27.26613,51.65957],[27.24828,51.60161],[27.47212,51.61184],[27.51058,51.5854],[27.55727,51.63486],[27.71932,51.60672],[27.67125,51.50854],[27.76052,51.47604],[27.85253,51.62293],[27.91844,51.61952],[27.95827,51.56065],[28.10658,51.57857],[28.23452,51.66988],[28.37592,51.54505],[28.47051,51.59734],[28.64429,51.5664],[28.69161,51.44695],[28.73143,51.46236],[28.75615,51.41442],[28.78224,51.45294],[28.76027,51.48802],[28.81795,51.55552],[28.95528,51.59222],[28.99098,51.56833],[29.1187,51.65872],[29.16402,51.64679],[29.20659,51.56918],[29.25603,51.57089],[29.25191,51.49828],[29.32881,51.37843],[29.42357,51.4187],[29.49773,51.39814],[29.54372,51.48372],[29.7408,51.53417],[29.77376,51.4461],[30.17888,51.51025],[30.34642,51.42555],[30.36153,51.33984],[30.56203,51.25655],[30.64992,51.35014],[30.51946,51.59649],[30.68804,51.82806],[30.76443,51.89739],[30.90897,52.00699],[30.95589,52.07775],[31.13332,52.1004],[31.25142,52.04131],[31.38326,52.12991],[31.7822,52.11406],[31.77877,52.18636],[31.6895,52.1973],[31.70735,52.26711],[31.57971,52.32146],[31.62084,52.33849],[31.61397,52.48843],[31.56316,52.51518],[31.63869,52.55361],[31.50406,52.69707],[31.57277,52.71613],[31.592,52.79011],[31.35667,52.97854],[31.24147,53.031],[31.32283,53.04101],[31.33519,53.08805],[31.3915,53.09712],[31.36403,53.13504],[31.40523,53.21406],[31.56316,53.19432],[31.62496,53.22886],[31.787,53.18033],[31.82373,53.10042],[32.15368,53.07594],[32.40773,53.18856],[32.51725,53.28431],[32.73257,53.33494],[32.74968,53.45597],[32.47777,53.5548],[32.40499,53.6656],[32.50112,53.68594],[32.45717,53.74039],[32.36663,53.7166],[32.12621,53.81586],[31.89137,53.78099],[31.77028,53.80015],[31.85019,53.91801],[31.88744,54.03653],[31.89599,54.0837],[31.57002,54.14535],[31.30791,54.25315],[31.3177,54.34067],[31.22945,54.46585],[31.08543,54.50361],[31.21399,54.63113],[31.19339,54.66947],[30.99187,54.67046],[30.98226,54.68872],[31.0262,54.70698],[30.97127,54.71967],[30.95479,54.74346],[30.75165,54.80699],[30.8264,54.90062],[30.81759,54.94064],[30.93144,54.9585],[30.95754,54.98609],[30.9081,55.02232],[30.94243,55.03964],[31.00972,55.02783],[31.02071,55.06167],[30.97369,55.17134],[30.87944,55.28223],[30.81946,55.27931],[30.8257,55.3313],[30.93144,55.3914],[30.90123,55.46621],[30.95204,55.50667],[30.93419,55.6185],[30.86003,55.63169],[30.7845,55.58514],[30.72957,55.66268],[30.67464,55.64176],[30.63344,55.73079],[30.51037,55.76568],[30.51346,55.78982],[30.48257,55.81066],[30.30987,55.83592],[30.27776,55.86819],[30.12136,55.8358],[29.97975,55.87281],[29.80672,55.79569],[29.61446,55.77716],[29.51283,55.70294],[29.3604,55.75862],[29.44692,55.95978],[29.21717,55.98971],[29.08299,56.03427],[28.73418,55.97131],[28.63668,56.07262],[28.68337,56.10173],[28.5529,56.11705],[28.43068,56.09407],[28.37987,56.11399],[28.36888,56.05805],[28.30571,56.06035],[28.15217,56.16964],[27.97865,56.11849],[27.63065,55.89687],[27.61683,55.78558],[27.3541,55.8089],[27.27804,55.78299],[27.1559,55.85032],[26.97153,55.8102],[26.87448,55.7172],[26.76872,55.67658],[26.71802,55.70645],[26.64888,55.70515],[26.63231,55.67968],[26.63167,55.57887],[26.55094,55.5093],[26.5522,55.40277],[26.44937,55.34832],[26.5709,55.32572],[26.6714,55.33902],[26.80929,55.31642],[26.83266,55.30444],[26.835,55.28182],[26.73017,55.24226],[26.72983,55.21788],[26.68075,55.19787],[26.69243,55.16718],[26.54753,55.14181],[26.51481,55.16051],[26.46249,55.12814],[26.35121,55.1525],[26.30628,55.12536],[26.23202,55.10439],[26.26941,55.08032],[26.20397,54.99729],[26.13386,54.98924],[26.05907,54.94631],[25.99129,54.95705],[25.89462,54.93438],[25.74122,54.80108],[25.75977,54.57252],[25.68045,54.5321],[25.64813,54.48704],[25.62203,54.4656],[25.63371,54.42075],[25.5376,54.33158],[25.55425,54.31591],[25.68513,54.31727],[25.78553,54.23327],[25.78563,54.15747],[25.71084,54.16704],[25.64875,54.1259],[25.54724,54.14925],[25.51452,54.17799],[25.56823,54.25212],[25.509,54.30267],[25.35559,54.26544],[25.22705,54.26271],[25.19199,54.219],[25.0728,54.13419],[24.991,54.14241],[24.96894,54.17589],[24.77131,54.11091],[24.85311,54.02862],[24.74279,53.96663],[24.69185,53.96543],[24.69652,54.01901],[24.62275,54.00217],[24.44411,53.90076],[24.34128,53.90076],[24.19638,53.96405],[23.98837,53.92554],[23.95098,53.9613],[23.81309,53.94205],[23.80543,53.89558],[23.71726,53.93379],[23.61677,53.92691],[23.51284,53.95052],[23.62004,53.60942],[23.81995,53.24131],[23.85657,53.22923],[23.91393,53.16469],[23.87548,53.0831],[23.92184,53.02079],[23.94689,52.95919],[23.91805,52.94016],[23.93763,52.71332],[23.73615,52.6149],[23.58296,52.59868],[23.45112,52.53774],[23.34141,52.44845],[23.18196,52.28812]]]}},{"type":"Feature","properties":{"id":"BZ"},"geometry":{"type":"Polygon","coordinates":[[[-89.22683,15.88619],[-89.17418,15.90898],[-89.02415,15.9063],[-88.95358,15.88698],[-88.40779,16.09624],[-87.3359,17.10872],[-87.24084,17.80373],[-87.84815,18.18511],[-87.85693,18.18266],[-87.86657,18.19971],[-87.87604,18.18313],[-87.90671,18.15213],[-88.03165,18.16657],[-88.03238,18.41778],[-88.26593,18.47617],[-88.29909,18.47591],[-88.3268,18.49048],[-88.48242,18.49164],[-88.71505,18.0707],[-88.8716,17.89535],[-89.03839,18.0067],[-89.15105,17.95104],[-89.14985,17.81563],[-89.15025,17.04813],[-89.22683,15.88619]]]}},{"type":"Feature","properties":{"id":"BO"},"geometry":{"type":"Polygon","coordinates":[[[-69.62883,-17.28142],[-69.46863,-17.37466],[-69.46897,-17.4988],[-69.46623,-17.60518],[-69.34126,-17.72753],[-69.28671,-17.94844],[-69.07496,-18.03715],[-69.14807,-18.16893],[-69.07432,-18.28259],[-68.94987,-18.93302],[-68.87082,-19.06003],[-68.80602,-19.08355],[-68.61989,-19.27584],[-68.41218,-19.40499],[-68.66761,-19.72118],[-68.54611,-19.84651],[-68.57132,-20.03134],[-68.74273,-20.08817],[-68.7276,-20.46178],[-68.44023,-20.62701],[-68.55383,-20.7355],[-68.53957,-20.91542],[-68.40403,-20.94562],[-68.18816,-21.28614],[-67.85114,-22.87076],[-67.54284,-22.89771],[-67.18382,-22.81525],[-66.7298,-22.23644],[-66.29714,-22.08741],[-66.24077,-21.77837],[-66.03836,-21.84829],[-66.04832,-21.9187],[-65.9261,-21.93335],[-65.7467,-22.10105],[-65.61166,-22.09504],[-65.58694,-22.09794],[-65.57743,-22.07675],[-65.47435,-22.08908],[-64.99524,-22.08255],[-64.90014,-22.12136],[-64.67174,-22.18957],[-64.58888,-22.25035],[-64.4176,-22.67692],[-64.35108,-22.73282],[-64.31489,-22.88824],[-64.22918,-22.55807],[-63.93287,-21.99934],[-63.70963,-21.99934],[-63.68113,-22.0544],[-63.66482,-21.99918],[-62.81124,-21.9987],[-62.8078,-22.12534],[-62.64455,-22.25091],[-62.2757,-21.06657],[-62.26883,-20.55311],[-61.93912,-20.10053],[-61.73723,-19.63958],[-60.00638,-19.2981],[-59.06965,-19.29148],[-58.23216,-19.80058],[-58.16225,-20.16193],[-57.8496,-19.98346],[-58.14215,-19.76276],[-57.78463,-19.03259],[-57.71113,-19.03161],[-57.69134,-19.00544],[-57.71995,-18.97546],[-57.71995,-18.89573],[-57.76764,-18.90087],[-57.56807,-18.25655],[-57.48237,-18.24219],[-57.69877,-17.8431],[-57.73949,-17.56095],[-57.90082,-17.44555],[-57.99661,-17.5273],[-58.32935,-17.28195],[-58.5058,-16.80958],[-58.30918,-16.3699],[-58.32431,-16.25861],[-58.41506,-16.32636],[-60.16069,-16.26479],[-60.23797,-15.50267],[-60.58224,-15.09887],[-60.23968,-15.09515],[-60.27887,-14.63021],[-60.46037,-14.22496],[-60.48053,-13.77981],[-61.05527,-13.50054],[-61.81151,-13.49564],[-63.76259,-12.42952],[-63.90248,-12.52544],[-64.22539,-12.45267],[-64.30708,-12.46398],[-64.99778,-11.98604],[-65.30027,-11.48749],[-65.28141,-10.86289],[-65.35402,-10.78685],[-65.37923,-10.35141],[-65.29019,-9.86253],[-65.40615,-9.63894],[-65.56244,-9.84266],[-65.68343,-9.75323],[-67.17784,-10.34016],[-68.71533,-11.14749],[-68.7651,-11.0496],[-68.75179,-11.03688],[-68.75265,-11.02383],[-68.74802,-11.00891],[-69.42792,-10.93451],[-69.47839,-10.95254],[-69.57156,-10.94555],[-68.98115,-11.8979],[-68.65044,-12.50689],[-68.85615,-12.87769],[-68.8864,-13.40792],[-69.05265,-13.68546],[-68.88135,-14.18639],[-69.36254,-14.94634],[-69.14856,-15.23478],[-69.40336,-15.61358],[-69.20291,-16.16668],[-69.09986,-16.22693],[-68.96238,-16.194],[-68.79464,-16.33272],[-68.98358,-16.42165],[-69.04027,-16.57214],[-69.00853,-16.66769],[-69.16896,-16.72233],[-69.62883,-17.28142]]]}},{"type":"Feature","properties":{"id":"BR"},"geometry":{"type":"Polygon","coordinates":[[[-73.96938,-7.58465],[-73.65485,-7.77897],[-73.76576,-7.89884],[-72.92886,-9.04074],[-73.21498,-9.40904],[-72.72216,-9.41397],[-72.31883,-9.5184],[-72.14742,-9.98049],[-71.23394,-9.9668],[-70.53373,-9.42628],[-70.58453,-9.58303],[-70.55429,-9.76692],[-70.62487,-9.80666],[-70.64134,-11.0108],[-70.51395,-10.92249],[-70.38791,-11.07096],[-69.90896,-10.92744],[-69.57835,-10.94051],[-69.57156,-10.94555],[-69.47839,-10.95254],[-69.42792,-10.93451],[-68.74802,-11.00891],[-68.75265,-11.02383],[-68.75179,-11.03688],[-68.7651,-11.0496],[-68.71533,-11.14749],[-67.17784,-10.34016],[-65.68343,-9.75323],[-65.56244,-9.84266],[-65.40615,-9.63894],[-65.29019,-9.86253],[-65.37923,-10.35141],[-65.35402,-10.78685],[-65.28141,-10.86289],[-65.30027,-11.48749],[-64.99778,-11.98604],[-64.30708,-12.46398],[-64.22539,-12.45267],[-63.90248,-12.52544],[-63.76259,-12.42952],[-61.81151,-13.49564],[-61.05527,-13.50054],[-60.48053,-13.77981],[-60.46037,-14.22496],[-60.27887,-14.63021],[-60.23968,-15.09515],[-60.58224,-15.09887],[-60.23797,-15.50267],[-60.16069,-16.26479],[-58.41506,-16.32636],[-58.32431,-16.25861],[-58.30918,-16.3699],[-58.5058,-16.80958],[-58.32935,-17.28195],[-57.99661,-17.5273],[-57.90082,-17.44555],[-57.73949,-17.56095],[-57.69877,-17.8431],[-57.48237,-18.24219],[-57.56807,-18.25655],[-57.76764,-18.90087],[-57.71995,-18.89573],[-57.71995,-18.97546],[-57.69134,-19.00544],[-57.71113,-19.03161],[-57.78463,-19.03259],[-58.14215,-19.76276],[-57.8496,-19.98346],[-58.16225,-20.16193],[-57.84536,-20.93155],[-57.93492,-21.65505],[-57.88239,-21.6868],[-57.94642,-21.73799],[-57.98625,-22.09157],[-56.6508,-22.28387],[-56.5212,-22.11556],[-56.45893,-22.08072],[-56.23206,-22.25347],[-55.8331,-22.29008],[-55.74941,-22.46436],[-55.741,-22.52018],[-55.72366,-22.5519],[-55.6986,-22.56268],[-55.68742,-22.58407],[-55.62493,-22.62765],[-55.63849,-22.95122],[-55.5446,-23.22811],[-55.52288,-23.2595],[-55.5555,-23.28237],[-55.43585,-23.87157],[-55.44117,-23.9185],[-55.41784,-23.9657],[-55.12292,-23.99669],[-55.0518,-23.98666],[-55.02691,-23.97317],[-54.6238,-23.83078],[-54.32807,-24.01865],[-54.28207,-24.07305],[-54.4423,-25.13381],[-54.62033,-25.46026],[-54.60196,-25.48397],[-54.59509,-25.53696],[-54.59398,-25.59224],[-54.5502,-25.58915],[-54.52926,-25.62846],[-53.90831,-25.55513],[-53.83691,-25.94849],[-53.73511,-26.04211],[-53.73086,-26.05842],[-53.7264,-26.0664],[-53.73391,-26.07006],[-53.73968,-26.10012],[-53.65018,-26.19501],[-53.65237,-26.23289],[-53.63739,-26.2496],[-53.63881,-26.25075],[-53.64632,-26.24798],[-53.64186,-26.25976],[-53.64505,-26.28089],[-53.68269,-26.33359],[-53.73372,-26.6131],[-53.80144,-27.09844],[-54.15978,-27.2889],[-54.19062,-27.27639],[-54.19268,-27.30751],[-54.41888,-27.40882],[-54.50416,-27.48232],[-54.67657,-27.57214],[-54.90159,-27.63132],[-54.90805,-27.73149],[-55.1349,-27.89759],[-55.16872,-27.86224],[-55.33303,-27.94661],[-55.6262,-28.17124],[-55.65418,-28.18304],[-56.01729,-28.51223],[-56.00458,-28.60421],[-56.05265,-28.62651],[-56.54171,-29.11447],[-56.57295,-29.11357],[-56.62789,-29.18073],[-56.81251,-29.48154],[-57.09386,-29.74211],[-57.65132,-30.19229],[-57.22502,-30.26121],[-56.90236,-30.02578],[-56.49267,-30.39471],[-56.4795,-30.3899],[-56.4619,-30.38457],[-55.87388,-31.05053],[-55.58866,-30.84117],[-55.5634,-30.8686],[-55.55373,-30.8732],[-55.55218,-30.88193],[-55.54572,-30.89051],[-55.53431,-30.89714],[-55.53276,-30.90218],[-55.52712,-30.89997],[-55.51862,-30.89828],[-55.50841,-30.9027],[-55.50821,-30.91349],[-54.17384,-31.86168],[-53.76024,-32.0751],[-53.39572,-32.58596],[-53.37671,-32.57005],[-53.1111,-32.71147],[-53.53459,-33.16843],[-53.52794,-33.68908],[-53.44031,-33.69344],[-53.39593,-33.75169],[-53.37138,-33.74313],[-53.18243,-33.86894],[-28.34015,-20.99094],[-28.99601,1.86593],[-51.35485,4.8383],[-51.63798,4.51394],[-51.61983,4.14596],[-51.79599,3.89336],[-51.82312,3.85825],[-51.85573,3.83427],[-52.31787,3.17896],[-52.6906,2.37298],[-52.96539,2.1881],[-53.78743,2.34412],[-54.16286,2.10779],[-54.6084,2.32856],[-55.01919,2.564],[-55.71493,2.40342],[-55.96292,2.53188],[-56.13054,2.27723],[-55.92159,2.05236],[-55.89863,1.89861],[-55.99278,1.83137],[-56.47045,1.95135],[-56.7659,1.89509],[-57.07092,1.95304],[-57.09109,2.01854],[-57.23981,1.95808],[-57.35073,1.98327],[-57.55743,1.69605],[-57.77281,1.73344],[-57.97336,1.64566],[-58.01873,1.51966],[-58.33887,1.58014],[-58.4858,1.48399],[-58.53571,1.29154],[-58.84229,1.17749],[-58.92072,1.31293],[-59.25583,1.40559],[-59.74066,1.87596],[-59.7264,2.27497],[-59.91177,2.36759],[-59.99733,2.92312],[-59.79769,3.37162],[-59.86899,3.57089],[-59.51963,3.91951],[-59.73353,4.20399],[-59.69361,4.34069],[-59.78878,4.45637],[-60.15953,4.53456],[-60.04189,4.69801],[-59.98129,5.07097],[-60.20944,5.28754],[-60.32352,5.21299],[-60.73204,5.20931],[-60.5802,4.94312],[-60.86539,4.70512],[-60.98303,4.54167],[-61.15703,4.49839],[-61.31457,4.54167],[-61.29675,4.44216],[-61.48569,4.43149],[-61.54629,4.2822],[-62.13094,4.08309],[-62.44822,4.18621],[-62.57656,4.04754],[-62.74411,4.03331],[-62.7655,3.73099],[-62.98296,3.59935],[-63.21111,3.96219],[-63.4464,3.9693],[-63.42233,3.89995],[-63.50611,3.83592],[-63.67099,4.01731],[-63.70218,3.91417],[-63.86082,3.94796],[-63.99183,3.90172],[-64.14512,4.12932],[-64.57648,4.12576],[-64.72977,4.28931],[-64.84028,4.24665],[-64.48379,3.7879],[-64.02908,2.79797],[-64.0257,2.48156],[-63.39114,2.4317],[-63.39827,2.16098],[-64.06135,1.94722],[-64.08274,1.64792],[-64.34654,1.35569],[-64.38932,1.5125],[-65.11657,1.12046],[-65.57288,0.62856],[-65.50158,0.92086],[-65.6727,1.01353],[-66.28507,0.74585],[-66.85795,1.22998],[-67.08222,1.17441],[-67.15784,1.80439],[-67.299,1.87494],[-67.40488,2.22258],[-67.9292,1.82455],[-68.18632,2.00091],[-68.26699,1.83463],[-68.18128,1.72881],[-69.38621,1.70865],[-69.53746,1.76408],[-69.83491,1.69353],[-69.82987,1.07864],[-69.26017,1.06856],[-69.14422,0.84172],[-69.20976,0.57958],[-69.47696,0.71065],[-70.04162,0.55437],[-70.03658,-0.19681],[-69.603,-0.51947],[-69.59796,-0.75136],[-69.4215,-1.01853],[-69.43395,-1.42219],[-69.94708,-4.2431],[-70.00888,-4.37833],[-70.11305,-4.27281],[-70.19582,-4.3607],[-70.33236,-4.15214],[-70.77601,-4.15717],[-70.96814,-4.36915],[-71.87003,-4.51661],[-72.64391,-5.0391],[-72.83973,-5.14765],[-73.24579,-6.05764],[-73.12983,-6.43852],[-73.73986,-6.87919],[-73.77011,-7.28944],[-73.96938,-7.58465]]]}},{"type":"Feature","properties":{"id":"BB"},"geometry":{"type":"Polygon","coordinates":[[[-59.92255,13.58015],[-59.88892,12.77667],[-59.14146,12.80638],[-59.17509,13.60976],[-59.92255,13.58015]]]}},{"type":"Feature","properties":{"id":"BN"},"geometry":{"type":"Polygon","coordinates":[[[114.07448,4.58441],[114.15813,4.57],[114.26876,4.49878],[114.32176,4.34942],[114.32176,4.2552],[114.4416,4.27588],[114.49922,4.13108],[114.64211,4.00694],[114.78539,4.12205],[114.88039,4.4257],[114.83189,4.42387],[114.77303,4.72871],[114.8266,4.75062],[114.88841,4.81905],[114.96982,4.81146],[114.99417,4.88201],[115.05038,4.90275],[115.02955,4.82087],[115.02278,4.74137],[115.04064,4.63706],[115.07737,4.53418],[115.09978,4.39123],[115.31275,4.30806],[115.36346,4.33563],[115.2851,4.42295],[115.27819,4.63661],[115.20737,4.8256],[115.15092,4.87604],[115.16236,5.01011],[115.02521,5.35005],[114.08532,4.64632],[114.07448,4.58441]]]}},{"type":"Feature","properties":{"id":"BT"},"geometry":{"type":"Polygon","coordinates":[[[88.74219,27.144],[88.86984,27.10937],[88.8714,26.97488],[88.92301,26.99286],[88.95807,26.92668],[89.09554,26.89089],[89.12825,26.81661],[89.1926,26.81329],[89.37913,26.86224],[89.38319,26.85963],[89.3901,26.84225],[89.42349,26.83727],[89.63369,26.74402],[89.86124,26.73307],[90.04535,26.72422],[90.30402,26.85098],[90.39271,26.90704],[90.48504,26.8594],[90.67715,26.77215],[91.50067,26.79223],[91.83181,26.87318],[92.05523,26.8692],[92.11863,26.893],[92.03457,27.07334],[92.04702,27.26861],[92.12019,27.27829],[92.01132,27.47352],[91.65007,27.48287],[91.55819,27.6144],[91.6469,27.76358],[91.5629,27.84823],[91.48973,27.93903],[91.46327,28.0064],[91.25779,28.07509],[91.20019,27.98715],[90.69894,28.07784],[90.58842,28.02838],[90.13387,28.19178],[89.79762,28.23979],[89.59525,28.16433],[89.12825,27.62502],[89.0582,27.60985],[88.97213,27.51671],[88.95355,27.4106],[89.00216,27.32532],[88.96947,27.30319],[88.93678,27.33777],[88.91901,27.32483],[88.74219,27.144]]]}},{"type":"Feature","properties":{"id":"BW"},"geometry":{"type":"Polygon","coordinates":[[[19.99817,-24.76768],[20.02809,-24.78725],[20.03678,-24.81004],[20.29826,-24.94869],[20.64795,-25.47827],[20.86081,-26.14892],[20.61754,-26.4692],[20.63275,-26.78181],[20.68596,-26.9039],[20.87031,-26.80047],[21.13353,-26.86661],[21.37869,-26.82083],[21.69322,-26.86152],[21.7854,-26.79199],[21.77114,-26.69015],[21.83291,-26.65959],[21.90703,-26.66808],[22.06192,-26.61882],[22.21206,-26.3773],[22.41921,-26.23078],[22.56365,-26.19668],[22.70808,-25.99186],[22.86012,-25.50572],[23.03497,-25.29971],[23.47588,-25.29971],[23.9244,-25.64286],[24.18287,-25.62916],[24.36531,-25.773],[24.44703,-25.73021],[24.67319,-25.81749],[24.8946,-25.80723],[25.01718,-25.72507],[25.12266,-25.75931],[25.33076,-25.76616],[25.58543,-25.6343],[25.6643,-25.4491],[25.69661,-25.29284],[25.72702,-25.25503],[25.88571,-24.87802],[25.84295,-24.78661],[25.8515,-24.75727],[26.39409,-24.63468],[26.46346,-24.60358],[26.51667,-24.47219],[26.84165,-24.24885],[26.99749,-23.65486],[27.33768,-23.40917],[27.52393,-23.37952],[27.6066,-23.21894],[27.74154,-23.2137],[27.93539,-23.04941],[27.93729,-22.96194],[28.04752,-22.90243],[28.04562,-22.8394],[28.34874,-22.5694],[28.63287,-22.55887],[28.91889,-22.44299],[29.0151,-22.22907],[29.10881,-22.21202],[29.15268,-22.21399],[29.18974,-22.18599],[29.21955,-22.17771],[29.37703,-22.19581],[29.3533,-22.18363],[29.24648,-22.05967],[29.1974,-22.07472],[29.14501,-22.07275],[29.08495,-22.04867],[29.04108,-22.00563],[29.02191,-21.95665],[29.02191,-21.90647],[29.04023,-21.85864],[29.07763,-21.81877],[28.58114,-21.63455],[28.49942,-21.66634],[28.29416,-21.59037],[28.01669,-21.57624],[27.91407,-21.31621],[27.69171,-21.08409],[27.72972,-20.51735],[27.69361,-20.48531],[27.28865,-20.49873],[27.29831,-20.28935],[27.21278,-20.08244],[26.72246,-19.92707],[26.17227,-19.53709],[25.96226,-19.08152],[25.99837,-19.02943],[25.94326,-18.90362],[25.82353,-18.82808],[25.79217,-18.6355],[25.68859,-18.56165],[25.53465,-18.39041],[25.39972,-18.12691],[25.31799,-18.07091],[25.23909,-17.90832],[25.26433,-17.79571],[25.16882,-17.78253],[25.05895,-17.84452],[24.95586,-17.79674],[24.73364,-17.89338],[24.71887,-17.9218],[24.6303,-17.9863],[24.57485,-18.07151],[24.40577,-17.95726],[24.19416,-18.01919],[23.61088,-18.4881],[23.29618,-17.99855],[23.0996,-18.00075],[21.45556,-18.31795],[20.99904,-18.31743],[20.99751,-22.00026],[19.99912,-21.99991],[19.99817,-24.76768]]]}},{"type":"Feature","properties":{"id":"CF"},"geometry":{"type":"Polygon","coordinates":[[[14.42917,6.00508],[14.49455,5.91683],[14.60974,5.91838],[14.62375,5.70466],[14.58951,5.59777],[14.62531,5.51411],[14.52724,5.28319],[14.57083,5.23979],[14.65489,5.21343],[14.73383,4.6135],[15.00825,4.41458],[15.08609,4.30282],[15.10644,4.1362],[15.17482,4.05131],[15.07686,4.01805],[15.73522,3.24348],[15.77725,3.26835],[16.05449,3.02306],[16.08252,2.45708],[16.19357,2.21537],[16.50126,2.84739],[16.46701,2.92512],[16.57598,3.47999],[16.68283,3.54257],[17.01746,3.55136],[17.35649,3.63045],[17.46876,3.70515],[17.60966,3.63705],[17.83421,3.61068],[17.85842,3.53378],[18.05656,3.56893],[18.14902,3.54476],[18.17323,3.47665],[18.24148,3.50302],[18.2723,3.57992],[18.39558,3.58212],[18.49245,3.63924],[18.58711,3.49423],[18.62755,3.47564],[20.60184,4.42394],[20.90383,4.44877],[21.08793,4.39603],[21.11214,4.33895],[21.21341,4.29285],[21.25744,4.33676],[21.55904,4.25553],[21.6405,4.317],[22.10721,4.20723],[22.27682,4.11347],[22.45504,4.13039],[22.5431,4.22041],[22.60915,4.48821],[22.6928,4.47285],[22.78526,4.71423],[22.84691,4.69887],[22.89094,4.79321],[22.94817,4.82392],[23.38847,4.60013],[24.46719,5.0915],[24.71816,4.90509],[25.31256,5.03668],[25.34558,5.29101],[25.53271,5.37431],[25.86073,5.19455],[26.13371,5.25594],[26.48595,5.04984],[26.74572,5.10685],[26.85579,5.03887],[26.93064,5.13535],[27.09575,5.22305],[27.44012,5.07349],[27.26886,5.25876],[27.23017,5.37167],[27.28621,5.56382],[27.22705,5.62889],[27.22705,5.71254],[26.51721,6.09655],[26.58259,6.1987],[26.32729,6.36272],[26.38022,6.63493],[25.90076,7.09549],[25.37461,7.33024],[25.35281,7.42595],[25.20337,7.50312],[25.20649,7.61115],[25.29214,7.66675],[25.25319,7.8487],[24.98855,7.96588],[24.85156,8.16933],[24.35965,8.26177],[24.13238,8.36959],[24.25691,8.69288],[23.51905,8.71749],[23.59065,8.99743],[23.44744,8.99128],[23.4848,9.16959],[23.56263,9.19418],[23.64358,9.28637],[23.64981,9.44303],[23.62179,9.53823],[23.69155,9.67566],[23.67164,9.86923],[23.3128,10.45214],[23.02221,10.69235],[22.87758,10.91915],[22.45889,11.00246],[21.72139,10.64136],[21.71479,10.29932],[21.63553,10.217],[21.52766,10.2105],[21.34934,9.95907],[21.26348,9.97642],[20.82979,9.44696],[20.36748,9.11019],[19.06421,9.00367],[18.86388,8.87971],[19.11044,8.68172],[18.79783,8.25929],[18.67455,8.22226],[18.62612,8.14163],[18.64153,8.08714],[18.6085,8.05009],[18.02731,8.01085],[17.93926,7.95853],[17.67288,7.98905],[16.8143,7.53971],[16.6668,7.67281],[16.658,7.75353],[16.59415,7.76444],[16.58315,7.88657],[16.41583,7.77971],[16.40703,7.68809],[15.79942,7.44149],[15.73118,7.52006],[15.49743,7.52179],[15.23397,7.25135],[15.04717,6.77085],[14.96311,6.75693],[14.79966,6.39043],[14.80122,6.34866],[14.74206,6.26356],[14.56149,6.18928],[14.43073,6.08867],[14.42917,6.00508]]]}},{"type":"Feature","properties":{"id":"CA"},"geometry":{"type":"Polygon","coordinates":[[[-141.00555,72.20369],[-141.00116,60.30648],[-140.5227,60.22077],[-140.45648,60.30919],[-139.98024,60.18027],[-139.68991,60.33693],[-139.05831,60.35205],[-139.20603,60.08896],[-139.05365,59.99655],[-138.71149,59.90728],[-138.62145,59.76431],[-137.60623,59.24465],[-137.4925,58.89415],[-136.82619,59.16198],[-136.52365,59.16752],[-136.47323,59.46617],[-136.33727,59.44466],[-136.22381,59.55526],[-136.31566,59.59083],[-135.48007,59.79937],[-135.03069,59.56208],[-135.00267,59.28745],[-134.7047,59.2458],[-134.55699,59.1297],[-134.48059,59.13231],[-134.27175,58.8634],[-133.84645,58.73543],[-133.38523,58.42773],[-131.8271,56.62247],[-130.77769,56.36185],[-130.33965,56.10849],[-130.10173,56.12178],[-130.00093,56.00325],[-130.00857,55.91344],[-130.15373,55.74895],[-129.97513,55.28029],[-130.08035,55.21556],[-130.18765,55.07744],[-130.27203,54.97174],[-130.44184,54.85377],[-130.64499,54.76912],[-130.61931,54.70835],[-133.92876,54.62289],[-133.36909,48.51151],[-125.03842,48.53282],[-123.50039,48.21223],[-123.15614,48.35395],[-123.26565,48.6959],[-123.0093,48.76586],[-123.0093,48.83186],[-123.32163,49.00419],[-117.03266,49.00056],[-116.04938,48.99999],[-114.0683,48.99885],[-110.0051,48.99901],[-104.05004,48.99925],[-101.36198,48.99935],[-97.24024,48.99952],[-95.15355,48.9996],[-95.15357,49.384],[-95.12903,49.37056],[-95.05825,49.35311],[-95.01419,49.35647],[-94.99532,49.36579],[-94.95681,49.37035],[-94.85381,49.32492],[-94.8159,49.32299],[-94.82487,49.29483],[-94.77355,49.11998],[-94.75017,49.09931],[-94.687,48.84077],[-94.70087,48.8339],[-94.70486,48.82365],[-94.69669,48.80918],[-94.69335,48.77883],[-94.58903,48.71803],[-94.54885,48.71543],[-94.53826,48.70216],[-94.44258,48.69223],[-94.4174,48.71049],[-94.27153,48.70232],[-94.25172,48.68404],[-94.25104,48.65729],[-94.23215,48.65202],[-93.85769,48.63284],[-93.83288,48.62745],[-93.80676,48.58232],[-93.80939,48.52439],[-93.79267,48.51631],[-93.66382,48.51845],[-93.47022,48.54357],[-93.44472,48.59147],[-93.40693,48.60948],[-93.39758,48.60364],[-93.3712,48.60599],[-93.33946,48.62787],[-93.25391,48.64266],[-92.94973,48.60866],[-92.7287,48.54005],[-92.6342,48.54133],[-92.62747,48.50278],[-92.69927,48.49573],[-92.71323,48.46081],[-92.65606,48.43471],[-92.50712,48.44921],[-92.45588,48.40624],[-92.48147,48.36609],[-92.37185,48.22259],[-92.27167,48.25046],[-92.30939,48.31251],[-92.26662,48.35651],[-92.202,48.35252],[-92.14732,48.36578],[-92.05339,48.35958],[-91.98929,48.25409],[-91.86125,48.21278],[-91.71231,48.19875],[-91.70451,48.11805],[-91.55649,48.10611],[-91.58025,48.04339],[-91.45829,48.07454],[-91.43248,48.04912],[-91.25025,48.08522],[-91.08016,48.18096],[-90.87588,48.2484],[-90.75045,48.09143],[-90.56444,48.12184],[-90.56312,48.09488],[-90.07418,48.11043],[-89.89974,47.98109],[-89.77248,48.02607],[-89.57972,48.00023],[-89.48837,48.01412],[-88.37033,48.30586],[-84.85871,46.88881],[-84.55635,46.45974],[-84.47607,46.45225],[-84.4481,46.48972],[-84.42101,46.49853],[-84.34174,46.50683],[-84.29893,46.49127],[-84.26351,46.49508],[-84.2264,46.53337],[-84.1945,46.54061],[-84.17723,46.52753],[-84.12885,46.53068],[-84.11196,46.50248],[-84.13451,46.39218],[-84.11254,46.32329],[-84.11615,46.2681],[-84.09756,46.25512],[-84.1096,46.23987],[-83.95399,46.05634],[-83.90453,46.05922],[-83.83329,46.12169],[-83.57017,46.105],[-83.43746,45.99749],[-83.59589,45.82131],[-82.48419,45.30225],[-82.42469,42.992],[-82.4146,42.97626],[-82.4253,42.95423],[-82.45331,42.93139],[-82.4826,42.8068],[-82.46613,42.76615],[-82.51063,42.66025],[-82.51858,42.611],[-82.57583,42.5718],[-82.58873,42.54984],[-82.64242,42.55594],[-82.82964,42.37355],[-83.02253,42.33045],[-83.07837,42.30978],[-83.09837,42.28877],[-83.12724,42.2376],[-83.14962,42.04089],[-83.11184,41.95671],[-82.67862,41.67615],[-80.53581,42.29896],[-79.77073,42.55308],[-78.93684,42.82887],[-78.90712,42.89733],[-78.90905,42.93022],[-78.93224,42.95229],[-78.96312,42.95509],[-78.98126,42.97],[-79.02074,42.98444],[-79.02424,43.01983],[-78.99941,43.05612],[-79.01055,43.06659],[-79.07486,43.07845],[-79.05671,43.10937],[-79.06881,43.12029],[-79.0427,43.13934],[-79.04652,43.16396],[-79.05384,43.17418],[-79.05002,43.20133],[-79.05544,43.21224],[-79.05512,43.25375],[-79.06921,43.26183],[-79.25796,43.54052],[-76.79706,43.63099],[-76.43859,44.09393],[-76.35324,44.13493],[-76.31222,44.19894],[-76.244,44.19643],[-76.1664,44.23051],[-76.16285,44.28262],[-76.00018,44.34896],[-75.95947,44.34463],[-75.8217,44.43176],[-75.76813,44.51537],[-75.41441,44.76614],[-75.2193,44.87821],[-75.01363,44.95608],[-74.99101,44.98051],[-74.8447,45.00606],[-74.66689,45.00646],[-74.32699,44.99029],[-73.35025,45.00942],[-71.50067,45.01357],[-71.48735,45.07784],[-71.42778,45.12624],[-71.40364,45.21382],[-71.44252,45.2361],[-71.37133,45.24624],[-71.29371,45.29996],[-71.22338,45.25184],[-71.19723,45.25438],[-71.14568,45.24128],[-71.08364,45.30623],[-71.01866,45.31573],[-71.0107,45.34819],[-70.95193,45.33895],[-70.91169,45.29849],[-70.89864,45.2398],[-70.84816,45.22698],[-70.80236,45.37444],[-70.82638,45.39828],[-70.78372,45.43269],[-70.65383,45.37592],[-70.62518,45.42286],[-70.72651,45.49771],[-70.68516,45.56964],[-70.54019,45.67291],[-70.38934,45.73215],[-70.41523,45.79497],[-70.25976,45.89675],[-70.24694,45.95138],[-70.31025,45.96424],[-70.23855,46.1453],[-70.29078,46.18832],[-70.18547,46.35357],[-70.05812,46.41768],[-69.99966,46.69543],[-69.22119,47.46461],[-69.05148,47.42012],[-69.05073,47.30076],[-69.05039,47.2456],[-68.89222,47.1807],[-68.70125,47.24399],[-68.60575,47.24659],[-68.57914,47.28431],[-68.38332,47.28723],[-68.37458,47.35851],[-68.23244,47.35712],[-67.94843,47.1925],[-67.87993,47.10377],[-67.78578,47.06473],[-67.78111,45.9392],[-67.75196,45.91814],[-67.80961,45.87531],[-67.75654,45.82324],[-67.80653,45.80022],[-67.80705,45.69528],[-67.6049,45.60725],[-67.43815,45.59162],[-67.42144,45.50584],[-67.50578,45.48971],[-67.42394,45.37969],[-67.48201,45.27351],[-67.34927,45.122],[-67.29754,45.14865],[-67.29748,45.18173],[-67.27039,45.1934],[-67.22751,45.16344],[-67.20349,45.1722],[-67.19603,45.16771],[-67.15965,45.16179],[-67.11316,45.11176],[-67.0216,44.95333],[-66.96824,44.90965],[-66.98249,44.87071],[-66.96824,44.83078],[-66.93432,44.82597],[-67.16117,44.20069],[-65.81187,42.91911],[-59.437,43.71683],[-57.60106,47.38123],[-56.67989,47.3339],[-56.25228,47.31192],[-55.8643,46.64935],[-51.16966,45.93987],[-45.64471,55.43944],[-53.68108,62.9266],[-74.12379,75.70014],[-73.91222,78.42484],[-67.48417,80.75493],[-63.1988,81.66522],[-59.93819,82.31398],[-62.36036,83.40597],[-85.36473,83.41631],[-110.08928,79.74266],[-141.00555,72.20369]]]}},{"type":"Feature","properties":{"id":"CN"},"geometry":{"type":"Polygon","coordinates":[[[73.5004,39.38402],[73.55396,39.3543],[73.54572,39.27567],[73.60638,39.24534],[73.75823,39.023],[73.81728,39.04007],[73.82964,38.91517],[73.7445,38.93867],[73.7033,38.84782],[73.80656,38.66449],[73.79806,38.61106],[73.97933,38.52945],[74.17022,38.65504],[74.51217,38.47034],[74.69619,38.42947],[74.69894,38.22155],[74.80331,38.19889],[74.82665,38.07359],[74.9063,38.03033],[74.92416,37.83428],[75.00935,37.77486],[74.8912,37.67576],[74.94338,37.55501],[75.06011,37.52779],[75.15899,37.41443],[75.09719,37.37297],[75.12328,37.31839],[74.88887,37.23275],[74.80605,37.21565],[74.49981,37.24518],[74.56453,37.03023],[75.13839,37.02622],[75.40481,36.95382],[75.45562,36.71971],[75.72737,36.7529],[75.92391,36.56986],[76.0324,36.41198],[76.00906,36.17511],[75.93028,36.13136],[76.15325,35.9264],[76.14913,35.82848],[76.33453,35.84296],[76.50961,35.8908],[76.77323,35.66062],[76.84539,35.67356],[76.96624,35.5932],[77.44277,35.46132],[77.70232,35.46244],[77.80532,35.52058],[78.11664,35.48022],[78.03466,35.3785],[78.00033,35.23954],[78.22692,34.88771],[78.18435,34.7998],[78.27781,34.61484],[78.54964,34.57283],[78.56475,34.50835],[78.74465,34.45174],[79.05364,34.32482],[78.99802,34.3027],[78.91769,34.15452],[78.66225,34.08858],[78.65657,34.03195],[78.73367,34.01121],[78.77349,33.73871],[78.67599,33.66445],[78.73636,33.56521],[79.15252,33.17156],[79.14016,33.02545],[79.46562,32.69668],[79.26768,32.53277],[79.13174,32.47766],[79.0979,32.38051],[78.99322,32.37948],[78.96713,32.33655],[78.7831,32.46873],[78.73916,32.69438],[78.38897,32.53938],[78.4645,32.45367],[78.49609,32.2762],[78.68754,32.10256],[78.74404,32.00384],[78.78036,31.99478],[78.69933,31.78723],[78.84516,31.60631],[78.71032,31.50197],[78.77898,31.31209],[79.01931,31.42817],[79.14016,31.43403],[79.22805,31.34963],[79.59884,30.93943],[79.93255,30.88288],[80.20721,30.58541],[80.54504,30.44936],[80.83343,30.32023],[81.03953,30.20059],[81.12842,30.01395],[81.24362,30.0126],[81.29032,30.08806],[81.2623,30.14596],[81.33355,30.15303],[81.39928,30.21862],[81.41018,30.42153],[81.62033,30.44703],[81.99082,30.33423],[82.10135,30.35439],[82.10757,30.23745],[82.19475,30.16884],[82.16984,30.0692],[82.38622,30.02608],[82.5341,29.9735],[82.73024,29.81695],[83.07116,29.61957],[83.28131,29.56813],[83.44787,29.30513],[83.63156,29.16249],[83.82303,29.30513],[83.97559,29.33091],[84.18107,29.23451],[84.24801,29.02783],[84.2231,28.89571],[84.47528,28.74023],[84.62317,28.73887],[84.85511,28.58041],[85.06059,28.68562],[85.19135,28.62825],[85.18668,28.54076],[85.10729,28.34092],[85.38127,28.28336],[85.4233,28.32996],[85.59765,28.30529],[85.60854,28.25045],[85.69105,28.38475],[85.71907,28.38064],[85.74864,28.23126],[85.84672,28.18187],[85.90743,28.05144],[85.97813,27.99023],[85.94946,27.9401],[86.06309,27.90021],[86.12069,27.93047],[86.08333,28.02121],[86.088,28.09264],[86.18607,28.17364],[86.22966,27.9786],[86.42736,27.91122],[86.51609,27.96623],[86.56265,28.09569],[86.74181,28.10638],[86.75582,28.04182],[87.03757,27.94835],[87.11696,27.84104],[87.56996,27.84517],[87.72718,27.80938],[87.82681,27.95248],[88.13378,27.88015],[88.1278,27.95417],[88.25332,27.9478],[88.54858,28.06057],[88.63235,28.12356],[88.83559,28.01936],[88.88091,27.85192],[88.77517,27.45415],[88.82981,27.38814],[88.91901,27.32483],[88.93678,27.33777],[88.96947,27.30319],[89.00216,27.32532],[88.95355,27.4106],[88.97213,27.51671],[89.0582,27.60985],[89.12825,27.62502],[89.59525,28.16433],[89.79762,28.23979],[90.13387,28.19178],[90.58842,28.02838],[90.69894,28.07784],[91.20019,27.98715],[91.25779,28.07509],[91.46327,28.0064],[91.48973,27.93903],[91.5629,27.84823],[91.6469,27.76358],[91.84722,27.76325],[91.87057,27.7195],[92.27432,27.89077],[92.32101,27.79363],[92.42538,27.80092],[92.7275,27.98662],[92.73025,28.05814],[92.65472,28.07632],[92.67486,28.15018],[92.93075,28.25671],[93.14635,28.37035],[93.18069,28.50319],[93.44621,28.67189],[93.72797,28.68821],[94.35897,29.01965],[94.2752,29.11687],[94.69318,29.31739],[94.81353,29.17804],[95.0978,29.14446],[95.11291,29.09527],[95.2214,29.10727],[95.26122,29.07727],[95.3038,29.13847],[95.41091,29.13007],[95.50842,29.13487],[95.72086,29.20797],[95.75149,29.32063],[95.84899,29.31464],[96.05361,29.38167],[96.31316,29.18643],[96.18682,29.11087],[96.20467,29.02325],[96.3626,29.10607],[96.61391,28.72742],[96.40929,28.51526],[96.48895,28.42955],[96.6455,28.61657],[96.85561,28.4875],[96.88445,28.39452],[96.98882,28.32564],[97.1289,28.3619],[97.34547,28.21385],[97.41729,28.29783],[97.47085,28.2688],[97.50518,28.49716],[97.56835,28.55628],[97.70705,28.5056],[97.79632,28.33168],[97.90069,28.3776],[98.15337,28.12114],[98.13964,27.9478],[98.32641,27.51385],[98.42529,27.55404],[98.43353,27.67086],[98.69582,27.56499],[98.7333,26.85615],[98.77547,26.61994],[98.72741,26.36183],[98.67797,26.24487],[98.7329,26.17218],[98.66884,26.09165],[98.63128,26.15492],[98.57085,26.11547],[98.60763,26.01512],[98.70818,25.86241],[98.63128,25.79937],[98.54064,25.85129],[98.40606,25.61129],[98.31268,25.55307],[98.25774,25.6051],[98.16848,25.62739],[98.18084,25.56298],[98.12591,25.50722],[98.14925,25.41547],[97.92541,25.20815],[97.83614,25.2715],[97.77023,25.11492],[97.72216,25.08508],[97.72903,24.91332],[97.79949,24.85655],[97.76481,24.8289],[97.73127,24.83015],[97.70181,24.84557],[97.64354,24.79171],[97.56648,24.76475],[97.56383,24.75535],[97.5542,24.74943],[97.54675,24.74202],[97.56525,24.72838],[97.56286,24.54535],[97.52757,24.43748],[97.60029,24.4401],[97.66998,24.45288],[97.7098,24.35658],[97.65624,24.33781],[97.66723,24.30027],[97.71941,24.29652],[97.76799,24.26365],[97.72998,24.2302],[97.72799,24.18883],[97.75305,24.16902],[97.72903,24.12606],[97.62363,24.00506],[97.5247,23.94032],[97.64667,23.84574],[97.72302,23.89288],[97.79456,23.94836],[97.79416,23.95663],[97.84328,23.97603],[97.86545,23.97723],[97.88811,23.97446],[97.8955,23.97758],[97.89676,23.97931],[97.89683,23.98389],[97.88814,23.98605],[97.88414,23.99405],[97.88616,24.00463],[97.90998,24.02094],[97.93951,24.01953],[97.98691,24.03897],[97.99583,24.04932],[98.04709,24.07616],[98.05302,24.07408],[98.05671,24.07961],[98.0607,24.07812],[98.06703,24.08028],[98.07806,24.07988],[98.20666,24.11406],[98.54476,24.13119],[98.59256,24.08371],[98.85319,24.13042],[98.87998,24.15624],[98.89632,24.10612],[98.67797,23.9644],[98.68209,23.80492],[98.79607,23.77947],[98.82933,23.72921],[98.81775,23.694],[98.88396,23.59555],[98.80294,23.5345],[98.82877,23.47908],[98.87683,23.48995],[98.92104,23.36946],[98.87573,23.33038],[98.93958,23.31414],[98.92515,23.29535],[98.88597,23.18656],[99.05975,23.16382],[99.04601,23.12215],[99.25741,23.09025],[99.34127,23.13099],[99.52214,23.08218],[99.54218,22.90014],[99.43537,22.94086],[99.45654,22.85726],[99.31243,22.73893],[99.38247,22.57544],[99.37972,22.50188],[99.28771,22.4105],[99.17318,22.18025],[99.19176,22.16983],[99.1552,22.15874],[99.33166,22.09656],[99.47585,22.13345],[99.85351,22.04183],[99.96612,22.05965],[99.99084,21.97053],[99.94003,21.82782],[99.98654,21.71064],[100.04956,21.66843],[100.12679,21.70539],[100.17486,21.65306],[100.10757,21.59945],[100.12542,21.50365],[100.1625,21.48704],[100.18447,21.51898],[100.25863,21.47043],[100.35201,21.53176],[100.42892,21.54325],[100.4811,21.46148],[100.57861,21.45637],[100.72143,21.51898],[100.87265,21.67396],[101.11744,21.77659],[101.15156,21.56129],[101.2124,21.56422],[101.19349,21.41959],[101.26912,21.36482],[101.2229,21.23271],[101.29326,21.17254],[101.54563,21.25668],[101.6068,21.23329],[101.59491,21.18621],[101.60886,21.17947],[101.66977,21.20004],[101.70548,21.14911],[101.7622,21.14813],[101.79266,21.19025],[101.76745,21.21571],[101.83887,21.20983],[101.84412,21.25291],[101.74014,21.30967],[101.74224,21.48276],[101.7727,21.51794],[101.7475,21.5873],[101.80001,21.57461],[101.83257,21.61562],[101.74555,21.72852],[101.7791,21.83019],[101.62566,21.96574],[101.57525,22.13026],[101.60675,22.13513],[101.53638,22.24794],[101.56789,22.28876],[101.61306,22.27515],[101.68973,22.46843],[101.7685,22.50337],[101.86828,22.38397],[101.90714,22.38688],[101.91344,22.44417],[101.98487,22.42766],[102.03633,22.46164],[102.1245,22.43372],[102.14099,22.40092],[102.16621,22.43336],[102.26428,22.41321],[102.25339,22.4607],[102.41061,22.64184],[102.38415,22.67919],[102.42618,22.69212],[102.46665,22.77108],[102.51802,22.77969],[102.57095,22.7036],[102.60675,22.73376],[102.8636,22.60735],[102.9321,22.48659],[103.0722,22.44775],[103.07843,22.50097],[103.17961,22.55705],[103.15782,22.59873],[103.18895,22.64471],[103.28079,22.68063],[103.32282,22.8127],[103.43179,22.75816],[103.43646,22.70648],[103.52675,22.59155],[103.57812,22.65764],[103.56255,22.69499],[103.64506,22.79979],[103.87904,22.56683],[103.93286,22.52703],[103.94513,22.52553],[103.95191,22.5134],[103.96352,22.50584],[103.96783,22.51173],[103.97384,22.50634],[103.99247,22.51958],[104.01088,22.51823],[104.03734,22.72945],[104.11384,22.80363],[104.27084,22.8457],[104.25683,22.76534],[104.35593,22.69353],[104.47225,22.75813],[104.58122,22.85571],[104.60457,22.81841],[104.65283,22.83419],[104.72755,22.81984],[104.77114,22.90017],[104.84942,22.93631],[104.86765,22.95178],[104.8334,23.01484],[104.79478,23.12934],[104.87382,23.12854],[104.87992,23.17141],[104.91435,23.18666],[104.9486,23.17235],[104.96532,23.20463],[104.98712,23.19176],[105.07002,23.26248],[105.11672,23.25247],[105.17276,23.28679],[105.22569,23.27249],[105.32376,23.39684],[105.40782,23.28107],[105.42805,23.30824],[105.49966,23.20669],[105.56037,23.16806],[105.57594,23.075],[105.72382,23.06641],[105.8726,22.92756],[105.90119,22.94168],[105.99568,22.94178],[106.00179,22.99049],[106.19705,22.98475],[106.27022,22.87722],[106.34961,22.86718],[106.49749,22.91164],[106.51306,22.94891],[106.55976,22.92311],[106.60179,22.92884],[106.6516,22.86862],[106.6734,22.89587],[106.71387,22.88296],[106.71128,22.85982],[106.78422,22.81532],[106.81271,22.8226],[106.83685,22.8098],[106.82404,22.7881],[106.76293,22.73491],[106.72321,22.63606],[106.71698,22.58432],[106.65316,22.5757],[106.61269,22.60301],[106.58395,22.474],[106.55665,22.46498],[106.57221,22.37],[106.55976,22.34841],[106.6516,22.33977],[106.69986,22.22309],[106.67495,22.1885],[106.6983,22.15102],[106.70142,22.02409],[106.68274,21.99811],[106.69276,21.96013],[106.72551,21.97923],[106.74345,22.00965],[106.81038,21.97934],[106.9178,21.97357],[106.92714,21.93459],[106.97228,21.92592],[106.99252,21.95191],[107.05634,21.92303],[107.06101,21.88982],[107.00964,21.85948],[107.02615,21.81981],[107.10771,21.79879],[107.20734,21.71493],[107.24625,21.7077],[107.29296,21.74674],[107.35834,21.6672],[107.35989,21.60063],[107.38636,21.59774],[107.41593,21.64839],[107.47197,21.6672],[107.49532,21.62958],[107.49065,21.59774],[107.54047,21.5934],[107.56537,21.61945],[107.66967,21.60787],[107.80355,21.66141],[107.86114,21.65128],[107.90006,21.5905],[107.92652,21.58906],[107.95232,21.5388],[107.96774,21.53601],[107.97074,21.54072],[107.97383,21.53961],[107.97932,21.54503],[108.02926,21.54997],[108.0569,21.53604],[108.10003,21.47338],[108.26073,20.07614],[107.44022,18.66249],[110.2534,15.19951],[112.88221,15.61902],[117.76968,23.10828],[118.41371,24.06775],[118.179,24.33015],[118.09488,24.38193],[118.28244,24.51231],[118.35291,24.51645],[118.42453,24.54644],[118.56434,24.49266],[120.49232,25.22863],[121.03532,26.8787],[123.5458,31.01942],[122.29378,31.76513],[122.80525,33.30571],[123.85601,37.49093],[123.90497,38.79949],[124.17532,39.8232],[124.23201,39.9248],[124.35029,39.95639],[124.37089,40.03004],[124.3322,40.05573],[124.38556,40.11047],[124.40719,40.13655],[124.86913,40.45387],[125.71172,40.85223],[125.76869,40.87908],[126.00335,40.92835],[126.242,41.15454],[126.53189,41.35206],[126.60631,41.65565],[126.90729,41.79955],[127.17841,41.59714],[127.29712,41.49473],[127.92943,41.44291],[128.02633,41.42103],[128.03311,41.39232],[128.12967,41.37931],[128.18546,41.41279],[128.20061,41.40895],[128.30716,41.60322],[128.15119,41.74568],[128.04487,42.01769],[128.94007,42.03537],[128.96068,42.06657],[129.15178,42.17224],[129.22285,42.26491],[129.22423,42.3553],[129.28541,42.41574],[129.42882,42.44702],[129.54701,42.37254],[129.60482,42.44461],[129.72541,42.43739],[129.75294,42.59409],[129.77183,42.69435],[129.7835,42.76521],[129.80719,42.79218],[129.83277,42.86746],[129.85261,42.96494],[129.8865,43.00395],[129.95082,43.01051],[129.96409,42.97306],[130.12957,42.98361],[130.09764,42.91425],[130.26095,42.9027],[130.23068,42.80125],[130.2385,42.71127],[130.41826,42.6011],[130.44361,42.54849],[130.50123,42.61636],[130.55143,42.52158],[130.62107,42.58413],[130.56576,42.68925],[130.40213,42.70788],[130.44361,42.76205],[130.66524,42.84753],[131.02438,42.86518],[131.02668,42.91246],[131.135,42.94114],[131.10274,43.04734],[131.20414,43.13654],[131.19031,43.21385],[131.30324,43.39498],[131.29402,43.46695],[131.19492,43.53047],[131.21105,43.82383],[131.26176,43.94011],[131.23583,43.96085],[131.25484,44.03131],[131.30365,44.04262],[131.1108,44.70266],[130.95639,44.85154],[131.48415,44.99513],[131.68466,45.12374],[131.66852,45.2196],[131.76532,45.22609],[131.86903,45.33636],[131.99417,45.2567],[132.83978,45.05916],[132.96373,45.0212],[133.12293,45.1332],[133.09279,45.25693],[133.19419,45.51913],[133.41083,45.57723],[133.48457,45.86203],[133.60442,45.90053],[133.67569,45.9759],[133.72695,46.05576],[133.68047,46.14697],[133.88097,46.25066],[133.91496,46.4274],[133.84104,46.46681],[134.03538,46.75668],[134.20016,47.33458],[134.50898,47.4812],[134.7671,47.72051],[134.55508,47.98651],[134.67098,48.1564],[134.75328,48.36763],[134.49516,48.42884],[132.66989,47.96491],[132.57309,47.71741],[131.90448,47.68011],[131.2635,47.73325],[131.09871,47.6852],[130.95985,47.6957],[130.90915,47.90623],[130.65103,48.10052],[130.84462,48.30942],[130.52147,48.61745],[130.66946,48.88251],[130.43232,48.90844],[130.2355,48.86741],[129.85416,49.11067],[129.67598,49.29596],[129.50685,49.42398],[129.40398,49.44194],[129.35317,49.3481],[129.23232,49.40353],[129.11153,49.36813],[128.72896,49.58676],[127.83476,49.5748],[127.53516,49.84306],[127.49299,50.01251],[127.60515,50.23503],[127.37384,50.28393],[127.36009,50.43787],[127.28765,50.46585],[127.36335,50.58306],[127.28165,50.72075],[127.14586,50.91152],[126.93135,51.0841],[126.90369,51.3238],[126.68349,51.70607],[126.44606,51.98254],[126.558,52.13738],[125.6131,53.07229],[125.17522,53.20225],[124.46078,53.21881],[123.86158,53.49391],[123.26989,53.54843],[122.85966,53.47395],[122.35063,53.49565],[121.39213,53.31888],[120.85633,53.28499],[120.0451,52.7359],[120.04049,52.58773],[120.46454,52.63811],[120.71673,52.54099],[120.61346,52.32447],[120.77337,52.20805],[120.65907,51.93544],[120.10963,51.671],[119.13553,50.37412],[119.38598,50.35162],[119.27996,50.13348],[119.11003,50.00276],[118.61623,49.93809],[117.82343,49.52696],[117.48208,49.62324],[117.27597,49.62544],[117.07142,49.68482],[116.71193,49.83813],[116.03781,48.87014],[116.06565,48.81716],[115.78876,48.51781],[115.811,48.25699],[115.52082,48.15367],[115.57128,47.91988],[115.94296,47.67741],[116.08431,47.80693],[116.2527,47.87766],[116.4465,47.83662],[116.67405,47.89039],[116.87527,47.88836],[117.08918,47.82242],[117.37875,47.63627],[117.50181,47.77216],[117.80196,48.01661],[118.03676,48.00982],[118.11009,48.04],[118.22677,48.03853],[118.29654,48.00246],[118.55766,47.99277],[118.7564,47.76947],[119.12343,47.66458],[119.13995,47.53997],[119.35892,47.48104],[119.31964,47.42617],[119.54918,47.29505],[119.56019,47.24874],[119.62403,47.24575],[119.71209,47.19192],[119.85518,46.92196],[119.91242,46.90091],[119.89261,46.66423],[119.80455,46.67631],[119.77373,46.62947],[119.68127,46.59015],[119.65265,46.62342],[119.42827,46.63783],[119.37306,46.61132],[119.30261,46.6083],[119.24978,46.64761],[119.10448,46.65516],[119.00541,46.74273],[118.92616,46.72765],[118.89974,46.77139],[118.8337,46.77742],[118.78747,46.68689],[118.30534,46.73519],[117.69554,46.50991],[117.60748,46.59771],[117.41782,46.57862],[117.36609,46.36335],[117.07252,46.35818],[116.83166,46.38637],[116.75551,46.33083],[116.58612,46.30211],[116.26678,45.96479],[116.24012,45.8778],[116.27366,45.78637],[116.16989,45.68603],[115.91898,45.6227],[115.69688,45.45761],[115.35757,45.39106],[114.94546,45.37377],[114.74612,45.43585],[114.54801,45.38337],[114.5166,45.27189],[114.08071,44.92847],[113.909,44.91444],[113.63821,44.74326],[112.74662,44.86297],[112.4164,45.06858],[111.98695,45.09074],[111.76275,44.98032],[111.40498,44.3461],[111.96289,43.81596],[111.93776,43.68709],[111.79758,43.6637],[111.59087,43.51207],[111.0149,43.3289],[110.4327,42.78293],[110.08401,42.6411],[109.89402,42.63111],[109.452,42.44842],[109.00679,42.45302],[108.84489,42.40246],[108.23156,42.45532],[107.57258,42.40898],[107.49681,42.46221],[107.29755,42.41395],[107.24774,42.36107],[106.76517,42.28741],[105.24708,41.7442],[105.01119,41.58382],[104.91272,41.64619],[104.51667,41.66113],[104.52258,41.8706],[103.92804,41.78246],[103.3685,41.89696],[102.72403,42.14675],[102.42826,42.15137],[102.07645,42.22519],[101.80515,42.50074],[101.28833,42.58524],[100.84979,42.67087],[100.33297,42.68231],[99.50671,42.56535],[97.1777,42.7964],[96.37926,42.72055],[96.35658,42.90363],[95.89543,43.2528],[95.52594,43.99353],[95.32891,44.02407],[95.39772,44.2805],[95.01191,44.25274],[94.71959,44.35284],[94.10003,44.71016],[93.51161,44.95964],[91.64048,45.07408],[90.89169,45.19667],[90.65114,45.49314],[90.70907,45.73437],[91.03026,46.04194],[90.99672,46.14207],[90.89639,46.30711],[91.07696,46.57315],[91.0147,46.58171],[91.03649,46.72916],[90.84035,46.99525],[90.76108,46.99399],[90.48542,47.30438],[90.48854,47.41826],[90.33598,47.68303],[90.10871,47.7375],[90.06512,47.88177],[89.76624,47.82745],[89.55453,48.0423],[89.0711,47.98528],[88.93186,48.10263],[88.8011,48.11302],[88.58316,48.21893],[88.58939,48.34531],[87.96361,48.58478],[88.0788,48.71436],[87.73822,48.89582],[87.88171,48.95853],[87.81333,49.17354],[87.48983,49.13794],[87.478,49.07403],[87.28386,49.11626],[86.87238,49.12432],[86.73568,48.99918],[86.75343,48.70331],[86.38069,48.46064],[85.73581,48.3939],[85.5169,48.05493],[85.61067,47.49753],[85.69696,47.2898],[85.54294,47.06171],[85.22443,47.04816],[84.93995,46.87399],[84.73077,47.01394],[83.92184,46.98912],[83.04622,47.19053],[82.21792,45.56619],[82.58474,45.40027],[82.51374,45.1755],[81.73278,45.3504],[80.11169,45.03352],[79.8987,44.89957],[80.38384,44.63073],[80.40229,44.23319],[80.40031,44.10986],[80.75156,43.44948],[80.69718,43.32589],[80.77771,43.30065],[80.78817,43.14235],[80.62913,43.141],[80.3735,43.01557],[80.58999,42.9011],[80.38169,42.83142],[80.26886,42.8366],[80.16892,42.61137],[80.26841,42.23797],[80.17807,42.21166],[80.17842,42.03211],[79.92977,42.04113],[78.3732,41.39603],[78.15757,41.38565],[78.12873,41.23091],[77.81287,41.14307],[77.76206,41.01574],[77.52723,41.00227],[77.3693,41.0375],[77.28004,41.0033],[76.99302,41.0696],[76.75681,40.95354],[76.5261,40.46114],[76.33659,40.3482],[75.96168,40.38064],[75.91361,40.2948],[75.69663,40.28642],[75.5854,40.66874],[75.22834,40.45382],[75.08243,40.43945],[74.82013,40.52197],[74.78168,40.44886],[74.85996,40.32857],[74.69875,40.34668],[74.35063,40.09742],[74.25533,40.13191],[73.97049,40.04378],[73.83006,39.76136],[73.9051,39.75073],[73.92354,39.69565],[73.94683,39.60733],[73.87018,39.47879],[73.59831,39.46425],[73.59241,39.40843],[73.5004,39.38402]]]}},{"type":"Feature","properties":{"id":"CL"},"geometry":{"type":"Polygon","coordinates":[[[-113.52687,-26.52828],[-68.11646,-58.14883],[-66.07313,-55.19618],[-67.11046,-54.94199],[-67.46182,-54.92205],[-68.01394,-54.8753],[-68.60733,-54.9125],[-68.60702,-52.65781],[-68.41683,-52.33516],[-69.97824,-52.00845],[-71.99889,-51.98018],[-72.33873,-51.59954],[-72.31343,-50.58411],[-73.15765,-50.78337],[-73.55259,-49.92488],[-73.45156,-49.79461],[-73.09655,-49.14342],[-72.56894,-48.81116],[-72.54042,-48.52392],[-72.27662,-48.28727],[-72.50478,-47.80586],[-71.94152,-47.13595],[-71.68577,-46.55385],[-71.75614,-45.61611],[-71.35687,-45.22075],[-72.06985,-44.81756],[-71.26418,-44.75684],[-71.16436,-44.46244],[-71.81318,-44.38097],[-71.64206,-43.64774],[-72.14828,-42.85321],[-72.15541,-42.15941],[-71.74901,-42.11711],[-71.92726,-40.72714],[-71.37826,-38.91474],[-70.89532,-38.6923],[-71.24279,-37.20264],[-70.95047,-36.4321],[-70.38008,-36.02375],[-70.49416,-35.24145],[-69.87386,-34.13344],[-69.88099,-33.34489],[-70.55832,-31.51559],[-70.14479,-30.36595],[-69.8596,-30.26131],[-69.99507,-29.28351],[-69.80969,-29.07185],[-69.66709,-28.44055],[-69.22504,-27.95042],[-68.77586,-27.16029],[-68.43363,-27.08414],[-68.27677,-26.90626],[-68.59048,-26.49861],[-68.56909,-26.28146],[-68.38372,-26.15353],[-68.57622,-25.32505],[-68.38372,-25.08636],[-68.56909,-24.69831],[-68.24825,-24.42596],[-67.33563,-24.04237],[-66.99632,-22.99839],[-67.18382,-22.81525],[-67.54284,-22.89771],[-67.85114,-22.87076],[-68.18816,-21.28614],[-68.40403,-20.94562],[-68.53957,-20.91542],[-68.55383,-20.7355],[-68.44023,-20.62701],[-68.7276,-20.46178],[-68.74273,-20.08817],[-68.57132,-20.03134],[-68.54611,-19.84651],[-68.66761,-19.72118],[-68.41218,-19.40499],[-68.61989,-19.27584],[-68.80602,-19.08355],[-68.87082,-19.06003],[-68.94987,-18.93302],[-69.07432,-18.28259],[-69.14807,-18.16893],[-69.07496,-18.03715],[-69.28671,-17.94844],[-69.34126,-17.72753],[-69.46623,-17.60518],[-69.46897,-17.4988],[-69.66483,-17.65083],[-69.79087,-17.65563],[-69.82868,-17.72048],[-69.75305,-17.94605],[-69.81607,-18.12582],[-69.96732,-18.25992],[-70.16394,-18.31737],[-70.31267,-18.31258],[-70.378,-18.3495],[-70.59118,-18.35072],[-73.98689,-20.10822],[-113.52687,-26.52828]]]}},{"type":"Feature","properties":{"id":"CI"},"geometry":{"type":"Polygon","coordinates":[[[-8.59456,6.50612],[-8.48652,6.43797],[-8.45666,6.49977],[-8.38453,6.35887],[-8.3298,6.36381],[-8.17557,6.28222],[-8.00642,6.31684],[-7.90692,6.27728],[-7.83478,6.20309],[-7.8497,6.08932],[-7.79747,6.07696],[-7.78254,5.99037],[-7.70294,5.90625],[-7.67309,5.94337],[-7.48155,5.80974],[-7.46165,5.84934],[-7.43677,5.84687],[-7.43926,5.74787],[-7.37209,5.61173],[-7.43428,5.42355],[-7.36463,5.32944],[-7.46165,5.26256],[-7.48901,5.14118],[-7.55369,5.08667],[-7.53876,4.94294],[-7.59349,4.8909],[-7.53259,4.35145],[-7.52774,3.7105],[-3.34019,4.17519],[-3.10675,5.08515],[-3.11073,5.12675],[-3.063,5.13665],[-2.96554,5.10397],[-2.95261,5.12477],[-2.75502,5.10657],[-2.73074,5.1364],[-2.77625,5.34621],[-2.72737,5.34789],[-2.76614,5.60963],[-2.85378,5.65156],[-2.93132,5.62137],[-2.96671,5.6415],[-2.95323,5.71865],[-3.01896,5.71697],[-3.25999,6.62521],[-3.21954,6.74407],[-3.23327,6.81744],[-2.95438,7.23737],[-2.97822,7.27165],[-2.92339,7.60847],[-2.79467,7.86002],[-2.78395,7.94974],[-2.74819,7.92613],[-2.67787,8.02055],[-2.61232,8.02645],[-2.62901,8.11495],[-2.49037,8.20872],[-2.58243,8.7789],[-2.66357,9.01771],[-2.77799,9.04949],[-2.69814,9.22717],[-2.68802,9.49343],[-2.76494,9.40778],[-2.93012,9.57403],[-3.00765,9.74019],[-3.16609,9.85147],[-3.19306,9.93781],[-3.27228,9.84981],[-3.31779,9.91125],[-3.69703,9.94279],[-4.25999,9.76012],[-4.31392,9.60062],[-4.6426,9.70696],[-4.96621,9.89132],[-4.96453,9.99923],[-5.12465,10.29788],[-5.39602,10.2929],[-5.51058,10.43177],[-5.65135,10.46767],[-5.78124,10.43952],[-5.99478,10.19694],[-6.18851,10.24244],[-6.1731,10.46983],[-6.24795,10.74248],[-6.325,10.68624],[-6.40646,10.69922],[-6.42847,10.5694],[-6.52974,10.59104],[-6.63541,10.66893],[-6.68164,10.35074],[-6.93921,10.35291],[-7.01186,10.25111],[-6.97444,10.21644],[-7.00966,10.15794],[-7.0603,10.14711],[-7.13331,10.24877],[-7.3707,10.24677],[-7.44555,10.44602],[-7.52261,10.4655],[-7.54462,10.40921],[-7.63048,10.46334],[-7.92107,10.15577],[-7.97971,10.17117],[-8.01225,10.1021],[-8.11921,10.04577],[-8.15652,9.94288],[-8.09434,9.86936],[-8.14657,9.55062],[-8.03463,9.39604],[-7.85056,9.41812],[-7.90777,9.20456],[-7.73862,9.08422],[-7.92518,8.99332],[-7.95503,8.81146],[-7.69882,8.66148],[-7.65653,8.36873],[-7.92518,8.50652],[-8.22991,8.48438],[-8.2411,8.24196],[-8.062,8.16071],[-7.98675,8.20134],[-7.99919,8.11023],[-7.94695,8.00925],[-8.06449,8.04989],[-8.13414,7.87991],[-8.09931,7.78626],[-8.21374,7.54466],[-8.4003,7.6285],[-8.47114,7.55676],[-8.41935,7.51203],[-8.37458,7.25794],[-8.29249,7.1691],[-8.31736,6.82837],[-8.59456,6.50612]]]}},{"type":"Feature","properties":{"id":"CM"},"geometry":{"type":"Polygon","coordinates":[[[8.34397,4.30689],[8.6479,4.06346],[9.22018,3.72052],[9.6225,2.44901],[9.81162,2.33797],[9.82123,2.35097],[9.83754,2.32428],[9.83238,2.29079],[9.84716,2.24676],[9.89012,2.20457],[9.90749,2.20049],[9.991,2.16561],[11.3561,2.17217],[11.37116,2.29975],[13.28534,2.25716],[13.29457,2.16106],[14.61145,2.17866],[15.00996,1.98887],[15.22634,2.03243],[15.34776,1.91264],[15.48942,1.98265],[16.02959,1.76483],[16.02647,1.65591],[16.14634,1.70259],[16.05294,1.9811],[16.08563,2.19733],[16.15568,2.18955],[16.19357,2.21537],[16.08252,2.45708],[16.05449,3.02306],[15.77725,3.26835],[15.73522,3.24348],[15.07686,4.01805],[15.17482,4.05131],[15.10644,4.1362],[15.08609,4.30282],[15.00825,4.41458],[14.73383,4.6135],[14.65489,5.21343],[14.57083,5.23979],[14.52724,5.28319],[14.62531,5.51411],[14.58951,5.59777],[14.62375,5.70466],[14.60974,5.91838],[14.49455,5.91683],[14.42917,6.00508],[14.43073,6.08867],[14.56149,6.18928],[14.74206,6.26356],[14.80122,6.34866],[14.79966,6.39043],[14.96311,6.75693],[15.04717,6.77085],[15.23397,7.25135],[15.49743,7.52179],[15.56964,7.58936],[15.59272,7.7696],[15.50743,7.79302],[15.20426,8.50892],[15.09484,8.65982],[14.83566,8.80557],[14.35707,9.19611],[14.37094,9.2954],[13.97544,9.6365],[14.01793,9.73169],[14.1317,9.82413],[14.20411,10.00055],[14.4673,10.00264],[14.80082,9.93818],[14.95722,9.97926],[15.05999,9.94845],[15.14043,9.99246],[15.24618,9.99246],[15.41408,9.92876],[15.68761,9.99344],[15.50535,10.1098],[15.30874,10.31063],[15.23724,10.47764],[15.14936,10.53915],[15.15532,10.62846],[15.06737,10.80921],[15.09127,10.87431],[15.04957,11.02347],[15.10021,11.04101],[15.0585,11.40481],[15.13149,11.5537],[15.06595,11.71126],[15.11579,11.79313],[15.04808,11.8731],[15.05786,12.0608],[15.0349,12.10698],[15.00146,12.1223],[14.96952,12.0925],[14.89019,12.16593],[14.90827,12.3269],[14.83314,12.62963],[14.55058,12.78256],[14.56101,12.91036],[14.46881,13.08259],[14.08251,13.0797],[14.20204,12.53405],[14.17523,12.41916],[14.22215,12.36533],[14.4843,12.35223],[14.6474,12.17466],[14.61612,11.7798],[14.55207,11.72001],[14.64591,11.66166],[14.6124,11.51283],[14.17821,11.23831],[13.97489,11.30258],[13.78945,11.00154],[13.7403,11.00593],[13.70753,10.94451],[13.73434,10.9255],[13.54964,10.61236],[13.5705,10.53183],[13.43644,10.13326],[13.34111,10.12299],[13.25025,10.03647],[13.25323,10.00127],[13.286,9.9822],[13.27409,9.93232],[13.24132,9.91031],[13.25025,9.86042],[13.29941,9.8296],[13.25472,9.76795],[13.22642,9.57266],[13.02385,9.49334],[12.85628,9.36698],[12.91958,9.33905],[12.90022,9.11411],[12.81085,8.91992],[12.79,8.75361],[12.71701,8.7595],[12.68722,8.65938],[12.44146,8.6152],[12.4489,8.52536],[12.26123,8.43696],[12.24782,8.17904],[12.19271,8.10826],[12.20909,7.97553],[11.99908,7.67302],[12.01844,7.52981],[11.93205,7.47812],[11.84864,7.26098],[11.87396,7.09398],[11.63117,6.9905],[11.55818,6.86186],[11.57755,6.74059],[11.51499,6.60892],[11.42264,6.5882],[11.42041,6.53789],[11.09495,6.51717],[11.09644,6.68437],[10.94302,6.69325],[10.8179,6.83377],[10.83727,6.9358],[10.60789,7.06885],[10.59746,7.14719],[10.57214,7.16345],[10.53639,6.93432],[10.21466,6.88996],[10.15135,7.03781],[9.86314,6.77756],[9.77824,6.79088],[9.70674,6.51717],[9.51757,6.43874],[8.84209,5.82562],[8.88156,5.78857],[8.83687,5.68483],[8.92029,5.58403],[8.78027,5.1243],[8.60302,4.87353],[8.34397,4.30689]]]}},{"type":"Feature","properties":{"id":"IN-TG"},"geometry":{"type":"Polygon","coordinates":[[[77.2344,16.47658],[77.28847,16.40595],[77.48451,16.38223],[77.59523,16.34336],[77.60364,16.29657],[77.49893,16.26906],[77.51197,15.92864],[77.65754,15.88869],[78.03108,15.90421],[78.12,15.82892],[78.41766,16.08012],[78.68408,16.04581],[79.21623,16.21665],[79.21485,16.48251],[79.31304,16.57302],[79.79507,16.72137],[79.94476,16.62731],[80.02544,16.71249],[80.0735,16.81242],[79.99248,16.8604],[80.04432,16.96256],[80.18199,17.04628],[80.26027,17.0082],[80.31864,16.87387],[80.40138,16.85613],[80.45391,16.81702],[80.45871,16.7881],[80.58711,16.772],[80.57544,16.91855],[80.36189,16.96683],[80.38867,17.08139],[80.4467,17.01772],[80.50369,17.1083],[80.61733,17.13226],[80.68771,17.06794],[80.83671,17.02494],[80.91499,17.20081],[81.40594,17.3585],[81.77398,17.88596],[81.39221,17.81014],[81.05026,17.77615],[80.51055,18.62802],[80.34782,18.59158],[80.27503,18.72104],[80.11196,18.6869],[79.9094,18.82474],[79.96261,18.86145],[79.97051,19.35358],[79.93858,19.47792],[79.86991,19.5009],[79.804,19.60054],[79.63165,19.57887],[79.47578,19.49928],[79.23408,19.61219],[79.19048,19.46044],[78.94432,19.54943],[78.95839,19.66037],[78.84613,19.65778],[78.82793,19.75749],[78.27449,19.90476],[78.35861,19.75604],[78.26694,19.69544],[78.3011,19.46885],[78.19141,19.42903],[78.14695,19.22898],[78.00653,19.30158],[77.86422,19.3207],[77.84379,19.18359],[77.74251,19.02577],[77.94353,18.82604],[77.85684,18.81905],[77.74251,18.68267],[77.73994,18.55204],[77.60965,18.5522],[77.56785,18.28849],[77.61377,18.10033],[77.56296,18.04925],[77.66166,17.9686],[77.50837,17.79298],[77.57823,17.74378],[77.45841,17.70568],[77.44005,17.57693],[77.52571,17.57693],[77.67608,17.52751],[77.62716,17.44335],[77.53583,17.44007],[77.52244,17.35178],[77.45155,17.3721],[77.46202,17.28607],[77.36623,17.15883],[77.46356,17.11454],[77.50099,17.01657],[77.45532,16.92068],[77.47489,16.77956],[77.44279,16.78712],[77.427,16.72252],[77.47095,16.71216],[77.47198,16.587],[77.37636,16.48481],[77.32366,16.48942],[77.2344,16.47658]]]}},{"type":"Feature","properties":{"id":"CD"},"geometry":{"type":"Polygon","coordinates":[[[11.95767,-5.94705],[12.42245,-6.07585],[13.04371,-5.87078],[16.55507,-5.85631],[16.96282,-7.21787],[17.5828,-8.13784],[18.33635,-8.00126],[19.33698,-7.99743],[19.5469,-7.00195],[20.30218,-6.98955],[20.31846,-6.91953],[20.61689,-6.90876],[20.56263,-7.28566],[21.79824,-7.29628],[21.84856,-9.59871],[22.19039,-9.94628],[22.32604,-10.76291],[22.17954,-10.85884],[22.25951,-11.24911],[22.54205,-11.05784],[23.16602,-11.10577],[23.45631,-10.946],[23.86868,-11.02856],[24.00027,-10.89356],[24.34528,-11.06816],[24.42612,-11.44975],[25.34069,-11.19707],[25.33058,-11.65767],[26.01777,-11.91488],[26.88687,-12.01868],[27.04351,-11.61312],[27.22541,-11.60323],[27.21025,-11.76157],[27.59932,-12.22123],[28.33199,-12.41375],[29.01918,-13.41353],[29.60531,-13.21685],[29.65078,-13.41844],[29.81551,-13.44683],[29.8139,-12.14898],[29.48404,-12.23604],[29.4992,-12.43843],[29.18592,-12.37921],[28.48357,-11.87532],[28.37241,-11.57848],[28.65032,-10.65133],[28.62795,-9.92942],[28.68532,-9.78],[28.56208,-9.49122],[28.51627,-9.44726],[28.52636,-9.35379],[28.36562,-9.30091],[28.38526,-9.23393],[28.9711,-8.66935],[28.88917,-8.4831],[30.79243,-8.27382],[30.2567,-7.14121],[29.52552,-6.2731],[29.43673,-4.44845],[29.23708,-3.75856],[29.21463,-3.3514],[29.25633,-3.05471],[29.17258,-2.99385],[29.16037,-2.95457],[29.09797,-2.91935],[29.09119,-2.87871],[29.0505,-2.81774],[29.00404,-2.81978],[29.00167,-2.78523],[29.04081,-2.7416],[29.00357,-2.70596],[28.94346,-2.69124],[28.89793,-2.66111],[28.90226,-2.62385],[28.89288,-2.55848],[28.87943,-2.55165],[28.86193,-2.53185],[28.86209,-2.5231],[28.87497,-2.50887],[28.88846,-2.50493],[28.89342,-2.49017],[28.89132,-2.47557],[28.86846,-2.44866],[28.86826,-2.41888],[28.89601,-2.37321],[28.95642,-2.37321],[29.00051,-2.29001],[29.105,-2.27043],[29.17562,-2.12278],[29.11847,-1.90576],[29.24458,-1.69663],[29.24323,-1.66826],[29.36322,-1.50887],[29.45038,-1.5054],[29.53062,-1.40499],[29.59061,-1.39016],[29.58388,-0.89821],[29.63006,-0.8997],[29.62708,-0.71055],[29.67176,-0.55714],[29.67474,-0.47969],[29.65091,-0.46777],[29.72687,-0.08051],[29.7224,0.07291],[29.77454,0.16675],[29.81922,0.16824],[29.87284,0.39166],[29.97413,0.52124],[29.95477,0.64486],[29.98307,0.84295],[30.1484,0.89805],[30.22139,0.99635],[30.24671,1.14974],[30.48503,1.21675],[31.30127,2.11006],[31.28042,2.17853],[31.20148,2.2217],[31.1985,2.29462],[31.12104,2.27676],[31.07934,2.30207],[31.06593,2.35862],[30.96911,2.41071],[30.91102,2.33332],[30.83059,2.42559],[30.74271,2.43601],[30.75612,2.5863],[30.8857,2.83923],[30.8574,2.9508],[30.77101,3.04897],[30.84251,3.26908],[30.93486,3.40737],[30.94081,3.50847],[30.85153,3.48867],[30.85997,3.5743],[30.80713,3.60506],[30.78512,3.67097],[30.56277,3.62703],[30.57378,3.74567],[30.55396,3.84451],[30.47691,3.83353],[30.27658,3.95653],[30.22374,3.93896],[30.1621,4.10586],[30.06964,4.13221],[29.79666,4.37809],[29.82087,4.56246],[29.49726,4.7007],[29.43341,4.50101],[29.22207,4.34297],[29.03054,4.48784],[28.8126,4.48784],[28.6651,4.42638],[28.20719,4.35614],[27.79551,4.59976],[27.76469,4.79284],[27.65462,4.89375],[27.56656,4.89375],[27.44012,5.07349],[27.09575,5.22305],[26.93064,5.13535],[26.85579,5.03887],[26.74572,5.10685],[26.48595,5.04984],[26.13371,5.25594],[25.86073,5.19455],[25.53271,5.37431],[25.34558,5.29101],[25.31256,5.03668],[24.71816,4.90509],[24.46719,5.0915],[23.38847,4.60013],[22.94817,4.82392],[22.89094,4.79321],[22.84691,4.69887],[22.78526,4.71423],[22.6928,4.47285],[22.60915,4.48821],[22.5431,4.22041],[22.45504,4.13039],[22.27682,4.11347],[22.10721,4.20723],[21.6405,4.317],[21.55904,4.25553],[21.25744,4.33676],[21.21341,4.29285],[21.11214,4.33895],[21.08793,4.39603],[20.90383,4.44877],[20.60184,4.42394],[18.62755,3.47564],[18.63857,3.19342],[18.10683,2.26876],[18.08034,1.58553],[17.85887,1.04327],[17.86989,0.58873],[17.95255,0.48128],[17.93877,0.32424],[17.81204,0.23884],[17.66051,-0.26535],[17.72112,-0.52707],[17.32438,-0.99265],[16.97999,-1.12762],[16.70724,-1.45815],[16.50336,-1.8795],[16.16173,-2.16586],[16.22785,-2.59528],[16.1755,-3.25014],[16.21407,-3.2969],[15.89448,-3.9513],[15.53081,-4.042],[15.48121,-4.22062],[15.41785,-4.28381],[15.32693,-4.27282],[15.25411,-4.31121],[15.1978,-4.32388],[14.83101,-4.80838],[14.67948,-4.92093],[14.5059,-4.84956],[14.41499,-4.8825],[14.37366,-4.56125],[14.47284,-4.42941],[14.3957,-4.36623],[14.40672,-4.28381],[13.9108,-4.50906],[13.81162,-4.41842],[13.71794,-4.44864],[13.70417,-4.72601],[13.50305,-4.77818],[13.41764,-4.89897],[13.11182,-4.5942],[13.09648,-4.63739],[13.11195,-4.67745],[12.8733,-4.74346],[12.70868,-4.95505],[12.63465,-4.94632],[12.60251,-5.01715],[12.46297,-5.09408],[12.49815,-5.14058],[12.51589,-5.1332],[12.53586,-5.14658],[12.53599,-5.1618],[12.52301,-5.17481],[12.52318,-5.74353],[12.26557,-5.74031],[12.20376,-5.76338],[11.95767,-5.94705]]]}},{"type":"Feature","properties":{"id":"CG"},"geometry":{"type":"Polygon","coordinates":[[[10.75913,-4.39519],[11.50888,-5.33417],[12.00924,-5.02627],[12.16068,-4.90089],[12.20901,-4.75642],[12.25587,-4.79437],[12.32324,-4.78415],[12.40964,-4.60609],[12.64835,-4.55937],[12.76844,-4.38709],[12.87096,-4.40315],[12.91489,-4.47907],[13.09648,-4.63739],[13.11182,-4.5942],[13.41764,-4.89897],[13.50305,-4.77818],[13.70417,-4.72601],[13.71794,-4.44864],[13.81162,-4.41842],[13.9108,-4.50906],[14.40672,-4.28381],[14.3957,-4.36623],[14.47284,-4.42941],[14.37366,-4.56125],[14.41499,-4.8825],[14.5059,-4.84956],[14.67948,-4.92093],[14.83101,-4.80838],[15.1978,-4.32388],[15.25411,-4.31121],[15.32693,-4.27282],[15.41785,-4.28381],[15.48121,-4.22062],[15.53081,-4.042],[15.89448,-3.9513],[16.21407,-3.2969],[16.1755,-3.25014],[16.22785,-2.59528],[16.16173,-2.16586],[16.50336,-1.8795],[16.70724,-1.45815],[16.97999,-1.12762],[17.32438,-0.99265],[17.72112,-0.52707],[17.66051,-0.26535],[17.81204,0.23884],[17.93877,0.32424],[17.95255,0.48128],[17.86989,0.58873],[17.85887,1.04327],[18.08034,1.58553],[18.10683,2.26876],[18.63857,3.19342],[18.62755,3.47564],[18.58711,3.49423],[18.49245,3.63924],[18.39558,3.58212],[18.2723,3.57992],[18.24148,3.50302],[18.17323,3.47665],[18.14902,3.54476],[18.05656,3.56893],[17.85842,3.53378],[17.83421,3.61068],[17.60966,3.63705],[17.46876,3.70515],[17.35649,3.63045],[17.01746,3.55136],[16.68283,3.54257],[16.57598,3.47999],[16.46701,2.92512],[16.50126,2.84739],[16.19357,2.21537],[16.15568,2.18955],[16.08563,2.19733],[16.05294,1.9811],[16.14634,1.70259],[16.02647,1.65591],[16.02959,1.76483],[15.48942,1.98265],[15.34776,1.91264],[15.22634,2.03243],[15.00996,1.98887],[14.61145,2.17866],[13.29457,2.16106],[13.13461,1.57238],[13.25447,1.32339],[13.15519,1.23368],[13.89582,1.4261],[14.25186,1.39842],[14.48179,0.9152],[14.26066,0.57255],[14.10909,0.58563],[13.88648,0.26652],[13.90632,-0.2287],[14.06862,-0.20826],[14.2165,-0.38261],[14.41887,-0.44799],[14.52569,-0.57818],[14.41838,-1.89412],[14.25932,-1.97624],[14.23518,-2.15671],[14.16202,-2.23916],[14.23829,-2.33715],[14.10442,-2.49268],[13.85846,-2.46935],[13.92073,-2.35581],[13.75884,-2.09293],[13.47977,-2.43224],[13.02759,-2.33098],[12.82172,-1.91091],[12.61312,-1.8129],[12.44656,-1.92025],[12.47925,-2.32626],[12.04895,-2.41704],[11.96866,-2.33559],[11.74605,-2.39936],[11.57637,-2.33379],[11.64487,-2.61865],[11.5359,-2.85654],[11.64798,-2.81146],[11.80365,-3.00424],[11.70558,-3.0773],[11.70227,-3.17465],[11.96554,-3.30267],[11.8318,-3.5812],[11.92719,-3.62768],[11.87083,-3.71571],[11.68608,-3.68942],[11.57949,-3.52798],[11.48764,-3.51089],[11.22301,-3.69888],[11.12647,-3.94169],[10.75913,-4.39519]]]}},{"type":"Feature","properties":{"id":"CO"},"geometry":{"type":"Polygon","coordinates":[[[-82.56142,11.91792],[-78.79327,9.93766],[-77.58292,9.22278],[-77.32389,8.81247],[-77.45064,8.49991],[-77.17257,7.97422],[-77.57185,7.51147],[-77.72514,7.72348],[-77.72157,7.47612],[-77.81426,7.48319],[-77.89178,7.22681],[-78.06168,7.07793],[-82.12561,4.00341],[-78.87137,1.47457],[-78.42749,1.15389],[-77.85677,0.80197],[-77.7148,0.85003],[-77.68613,0.83029],[-77.66416,0.81604],[-77.67815,0.73863],[-77.49984,0.64476],[-77.52001,0.40782],[-76.89177,0.24736],[-76.4094,0.24015],[-76.41215,0.38228],[-76.23441,0.42294],[-75.82927,0.09578],[-75.25764,-0.11943],[-75.18513,-0.0308],[-74.42701,-0.50218],[-74.26675,-0.97229],[-73.65312,-1.26222],[-72.92587,-2.44514],[-71.75223,-2.15058],[-70.94377,-2.23142],[-70.04609,-2.73906],[-70.71396,-3.7921],[-70.52393,-3.87553],[-70.3374,-3.79505],[-69.94708,-4.2431],[-69.43395,-1.42219],[-69.4215,-1.01853],[-69.59796,-0.75136],[-69.603,-0.51947],[-70.03658,-0.19681],[-70.04162,0.55437],[-69.47696,0.71065],[-69.20976,0.57958],[-69.14422,0.84172],[-69.26017,1.06856],[-69.82987,1.07864],[-69.83491,1.69353],[-69.53746,1.76408],[-69.38621,1.70865],[-68.18128,1.72881],[-68.26699,1.83463],[-68.18632,2.00091],[-67.9292,1.82455],[-67.40488,2.22258],[-67.299,1.87494],[-67.15784,1.80439],[-67.08222,1.17441],[-66.85795,1.22998],[-67.21967,2.35778],[-67.65696,2.81691],[-67.85862,2.79173],[-67.85862,2.86727],[-67.30945,3.38393],[-67.50067,3.75812],[-67.62671,3.74303],[-67.85358,4.53249],[-67.83341,5.31104],[-67.59141,5.5369],[-67.63914,5.64963],[-67.58558,5.84537],[-67.43513,5.98835],[-67.4625,6.20625],[-67.60654,6.2891],[-69.41843,6.1072],[-70.10716,6.96516],[-70.7596,7.09799],[-71.03941,6.98163],[-71.37234,7.01588],[-71.42212,7.03854],[-71.44118,7.02116],[-71.82441,7.04314],[-72.04895,7.03837],[-72.19437,7.37034],[-72.43132,7.40034],[-72.47415,7.48928],[-72.45321,7.57232],[-72.47827,7.65604],[-72.46763,7.79518],[-72.44454,7.86031],[-72.46183,7.90682],[-72.45806,7.91141],[-72.47042,7.92306],[-72.48183,7.92909],[-72.48801,7.94329],[-72.47213,7.96106],[-72.39137,8.03534],[-72.35163,8.01163],[-72.36987,8.19976],[-72.4042,8.36513],[-72.65474,8.61428],[-72.77415,9.10165],[-72.94052,9.10663],[-73.02119,9.27584],[-73.36905,9.16636],[-72.98085,9.85253],[-72.88002,10.44309],[-72.4767,11.1117],[-72.24983,11.14138],[-71.9675,11.65536],[-71.3275,11.85],[-70.92579,11.96275],[-71.19849,12.65801],[-78.66279,16.62373],[-81.80642,15.86201],[-82.06974,14.49418],[-82.56142,11.91792]]]}},{"type":"Feature","properties":{"id":"CR"},"geometry":{"type":"Polygon","coordinates":[[[-87.41779,5.02401],[-82.94503,7.93865],[-82.89978,8.04083],[-82.89137,8.05755],[-82.88641,8.10219],[-82.9388,8.26634],[-83.05209,8.33394],[-82.93056,8.43465],[-82.8679,8.44042],[-82.8382,8.48117],[-82.83322,8.52464],[-82.83975,8.54755],[-82.82739,8.60153],[-82.8794,8.6981],[-82.92068,8.74832],[-82.91377,8.774],[-82.88253,8.83331],[-82.72126,8.97125],[-82.93516,9.07687],[-82.93516,9.46741],[-82.84871,9.4973],[-82.87919,9.62645],[-82.77206,9.59573],[-82.66667,9.49746],[-82.61345,9.49881],[-82.56507,9.57279],[-82.51044,9.65379],[-83.54024,10.96805],[-83.68276,11.01562],[-83.66597,10.79916],[-83.90838,10.71161],[-84.68197,11.07568],[-84.92439,10.9497],[-85.60529,11.22607],[-85.71223,11.06868],[-86.14524,11.09059],[-87.41779,5.02401]]]}},{"type":"Feature","properties":{"id":"CZ"},"geometry":{"type":"Polygon","coordinates":[[[12.09287,50.25032],[12.19335,50.19997],[12.21484,50.16399],[12.1917,50.13434],[12.2073,50.10315],[12.23709,50.10213],[12.27433,50.0771],[12.26111,50.06331],[12.30798,50.05719],[12.49908,49.97305],[12.47264,49.94222],[12.55197,49.92094],[12.48256,49.83575],[12.46603,49.78882],[12.40489,49.76321],[12.4462,49.70233],[12.52553,49.68415],[12.53544,49.61888],[12.56188,49.6146],[12.60155,49.52887],[12.64782,49.52565],[12.64121,49.47628],[12.669,49.42935],[12.71227,49.42363],[12.75854,49.3989],[12.78168,49.34618],[12.88414,49.33541],[12.88249,49.35479],[12.94859,49.34079],[13.03618,49.30417],[13.02957,49.27399],[13.05883,49.26259],[13.17665,49.16713],[13.17019,49.14339],[13.20405,49.12303],[13.23689,49.11412],[13.28242,49.1228],[13.39479,49.04812],[13.40802,48.98851],[13.50221,48.93752],[13.50552,48.97441],[13.58319,48.96899],[13.61624,48.9462],[13.67739,48.87886],[13.73854,48.88538],[13.76994,48.83537],[13.78977,48.83319],[13.8096,48.77877],[13.84023,48.76988],[14.06151,48.66873],[14.01482,48.63788],[14.09104,48.5943],[14.20691,48.5898],[14.33909,48.55852],[14.43076,48.58855],[14.4587,48.64695],[14.56139,48.60429],[14.60808,48.62881],[14.66762,48.58215],[14.71794,48.59794],[14.72756,48.69502],[14.80584,48.73489],[14.80821,48.77711],[14.81545,48.7874],[14.94773,48.76268],[14.95641,48.75915],[14.9758,48.76857],[14.98112,48.77524],[14.9782,48.7766],[14.98032,48.77959],[14.95072,48.79101],[14.98917,48.90082],[14.97612,48.96983],[14.99878,49.01444],[15.15534,48.99056],[15.16358,48.94278],[15.26177,48.95766],[15.28305,48.98831],[15.34823,48.98444],[15.48027,48.94481],[15.51357,48.91549],[15.61622,48.89541],[15.6921,48.85973],[15.75341,48.8516],[15.78087,48.87644],[15.84404,48.86921],[16.06034,48.75436],[16.37345,48.729],[16.40915,48.74576],[16.46134,48.80865],[16.67008,48.77699],[16.68518,48.7281],[16.71883,48.73806],[16.79779,48.70998],[16.90354,48.71541],[16.93955,48.60371],[17.00215,48.70887],[17.11202,48.82925],[17.19355,48.87602],[17.29054,48.85546],[17.3853,48.80936],[17.45671,48.85004],[17.5295,48.81117],[17.7094,48.86721],[17.73126,48.87885],[17.77944,48.92318],[17.87831,48.92679],[17.91814,49.01784],[18.06885,49.03157],[18.1104,49.08624],[18.15022,49.24518],[18.18456,49.28909],[18.36446,49.3267],[18.4139,49.36517],[18.4084,49.40003],[18.44686,49.39467],[18.54848,49.47059],[18.53063,49.49022],[18.57183,49.51162],[18.6144,49.49824],[18.67757,49.50895],[18.74761,49.492],[18.84521,49.51672],[18.84786,49.5446],[18.80479,49.6815],[18.72838,49.68163],[18.69817,49.70473],[18.62676,49.71983],[18.62943,49.74603],[18.62645,49.75002],[18.61368,49.75426],[18.61278,49.7618],[18.57183,49.83334],[18.60341,49.86256],[18.57045,49.87849],[18.57697,49.91565],[18.54299,49.92537],[18.54495,49.9079],[18.53423,49.89906],[18.41604,49.93498],[18.33562,49.94747],[18.33278,49.92415],[18.31914,49.91565],[18.27794,49.93863],[18.27107,49.96779],[18.21752,49.97309],[18.20241,49.99958],[18.10628,50.00223],[18.07898,50.04535],[18.03212,50.06574],[18.00396,50.04954],[18.04585,50.03311],[18.04585,50.01194],[18.00191,50.01723],[17.86886,49.97452],[17.77669,50.02253],[17.7506,50.07896],[17.6888,50.12037],[17.66683,50.10275],[17.59404,50.16437],[17.70528,50.18812],[17.76296,50.23382],[17.72176,50.25665],[17.74648,50.29966],[17.69292,50.32859],[17.67764,50.28977],[17.58889,50.27837],[17.3702,50.28123],[17.34548,50.2628],[17.34273,50.32947],[17.27681,50.32246],[17.19991,50.3654],[17.19579,50.38817],[17.14498,50.38117],[17.1224,50.39494],[16.89229,50.45117],[16.85933,50.41093],[16.90877,50.38642],[16.94448,50.31281],[16.99803,50.30316],[17.02138,50.27772],[16.99803,50.25753],[17.02825,50.23118],[17.00353,50.21449],[16.98018,50.24172],[16.8456,50.20834],[16.7014,50.09659],[16.63137,50.1142],[16.55446,50.16613],[16.56407,50.21009],[16.42674,50.32509],[16.39379,50.3207],[16.3622,50.34875],[16.36495,50.37679],[16.30289,50.38292],[16.28118,50.36891],[16.22821,50.41054],[16.21585,50.40627],[16.19526,50.43291],[16.31413,50.50274],[16.34572,50.49575],[16.44597,50.58041],[16.33611,50.66579],[16.23174,50.67101],[16.20839,50.63096],[16.10265,50.66405],[16.02437,50.60046],[15.98317,50.61528],[16.0175,50.63009],[15.97219,50.69799],[15.87331,50.67188],[15.81683,50.75666],[15.73186,50.73885],[15.43798,50.80833],[15.3803,50.77187],[15.36656,50.83956],[15.2773,50.8907],[15.27043,50.97724],[15.2361,50.99886],[15.1743,50.9833],[15.16744,51.01959],[15.11937,50.99021],[15.10152,51.01095],[15.06218,51.02269],[15.03895,51.0123],[15.02433,51.0242],[14.96419,50.99108],[15.01088,50.97984],[14.99852,50.86817],[14.82803,50.86966],[14.79139,50.81438],[14.70661,50.84096],[14.61993,50.86049],[14.63434,50.8883],[14.65259,50.90513],[14.64802,50.93241],[14.58024,50.91443],[14.56374,50.922],[14.59702,50.96148],[14.59908,50.98685],[14.58215,50.99306],[14.56432,51.01008],[14.53438,51.00374],[14.53321,51.01679],[14.49873,51.02242],[14.50809,51.0427],[14.49991,51.04692],[14.49154,51.04382],[14.49202,51.02286],[14.45827,51.03712],[14.41335,51.02086],[14.30098,51.05515],[14.25665,50.98935],[14.28776,50.97718],[14.32353,50.98556],[14.32793,50.97379],[14.30251,50.96606],[14.31422,50.95243],[14.39848,50.93866],[14.38691,50.89907],[14.30098,50.88448],[14.27123,50.89386],[14.24314,50.88761],[14.22331,50.86049],[14.02982,50.80662],[13.98864,50.8177],[13.89113,50.78533],[13.89444,50.74142],[13.82942,50.7251],[13.76316,50.73487],[13.70204,50.71771],[13.65977,50.73096],[13.52474,50.70394],[13.53748,50.67654],[13.5226,50.64721],[13.49742,50.63133],[13.46413,50.60102],[13.42189,50.61243],[13.37485,50.64931],[13.37805,50.627],[13.32264,50.60317],[13.32594,50.58009],[13.29454,50.57904],[13.25158,50.59268],[13.19043,50.50237],[13.13424,50.51709],[13.08301,50.50132],[13.0312,50.50944],[13.02038,50.4734],[13.02147,50.44763],[12.98433,50.42016],[12.94058,50.40944],[12.82465,50.45738],[12.73476,50.43237],[12.73044,50.42268],[12.70731,50.39948],[12.67261,50.41949],[12.51356,50.39694],[12.48747,50.37278],[12.49214,50.35228],[12.48256,50.34784],[12.46643,50.35527],[12.43722,50.33774],[12.43371,50.32506],[12.39924,50.32302],[12.40158,50.29521],[12.36594,50.28289],[12.35425,50.23993],[12.33263,50.24367],[12.32445,50.20442],[12.33847,50.19432],[12.32596,50.17146],[12.29232,50.17524],[12.28063,50.19544],[12.28755,50.22429],[12.23943,50.24594],[12.24791,50.25525],[12.26953,50.25189],[12.25119,50.27079],[12.20823,50.2729],[12.18013,50.32146],[12.10907,50.32041],[12.13716,50.27396],[12.09287,50.25032]]]}},{"type":"Feature","properties":{"id":"IN-MZ"},"geometry":{"type":"Polygon","coordinates":[[[92.26541,23.70392],[92.38214,23.28705],[92.37665,22.9435],[92.5181,22.71441],[92.60029,22.1522],[92.56616,22.13554],[92.60949,21.97638],[92.67532,22.03547],[92.70416,22.16017],[92.86208,22.05456],[92.89504,21.95143],[92.93899,22.02656],[92.99804,21.98964],[92.99255,22.05965],[93.04885,22.20595],[93.15734,22.18687],[93.14224,22.24535],[93.19991,22.25425],[93.18206,22.43716],[93.13537,22.45873],[93.11477,22.54374],[93.134,22.59573],[93.09417,22.69459],[93.134,22.92498],[93.12988,23.05772],[93.2878,23.00464],[93.38478,23.13698],[93.36862,23.35426],[93.38781,23.36139],[93.39981,23.38828],[93.38805,23.4728],[93.43475,23.68299],[93.3908,23.7622],[93.3908,23.92925],[93.36059,23.93176],[93.32351,24.04468],[92.98416,24.11354],[93.02329,24.40026],[92.8379,24.39588],[92.76168,24.52088],[92.52685,24.16617],[92.44239,24.14299],[92.42317,24.25635],[92.29819,24.25406],[92.31777,23.86543],[92.26541,23.70392]]]}},{"type":"Feature","properties":{"id":"CN-FJ"},"geometry":{"type":"Polygon","coordinates":[[[115.8625,25.22544],[115.88859,24.94123],[115.96618,24.92193],[116.05201,24.85528],[116.20754,24.84718],[116.29886,24.79857],[116.34761,24.86899],[116.40975,24.84313],[116.37062,24.80231],[116.48735,24.67977],[116.51859,24.60332],[116.81076,24.68352],[116.7517,24.5415],[116.89693,24.40025],[116.93023,24.23256],[116.99512,24.18402],[116.91478,24.09097],[116.97933,24.00099],[116.97589,23.95739],[116.96353,23.90655],[117.05863,23.73884],[117.76968,23.10828],[118.41371,24.06775],[118.179,24.33015],[118.09488,24.38193],[118.28244,24.51231],[118.35291,24.51645],[118.42453,24.54644],[118.56434,24.49266],[120.49232,25.22863],[121.03532,26.8787],[120.43281,27.17219],[120.40603,27.20273],[120.42011,27.26531],[120.25909,27.43089],[120.13275,27.42053],[120.03662,27.34371],[119.76985,27.30741],[119.65999,27.5381],[119.61845,27.67988],[119.25247,27.43425],[119.12132,27.44186],[118.89541,27.47294],[118.89678,27.64643],[118.79722,27.94164],[118.71002,27.98773],[118.79035,28.24572],[118.53973,28.28594],[118.47759,28.23876],[118.42121,28.29239],[118.31245,28.22878],[118.36875,28.19369],[118.35296,28.09409],[118.16413,28.05743],[118.08792,27.99167],[117.85274,27.94891],[117.7518,27.83027],[117.67249,27.82268],[117.56332,27.96408],[117.28523,27.87671],[117.31269,27.76801],[117.01469,27.66163],[117.16918,27.2943],[117.05451,27.1062],[116.60476,26.92513],[116.51412,26.70145],[116.64321,26.49024],[116.38572,26.23368],[116.50039,26.15728],[116.36444,25.971],[116.14265,25.87899],[116.00601,25.32789],[115.8625,25.22544]]]}},{"type":"Feature","properties":{"id":"DJ"},"geometry":{"type":"Polygon","coordinates":[[[41.77727,11.49902],[41.8096,11.33606],[41.80056,10.97127],[42.06302,10.92599],[42.13691,10.97586],[42.42669,10.98493],[42.62989,11.09711],[42.75111,11.06992],[42.79037,10.98493],[42.95776,10.98533],[43.42425,11.70983],[43.90659,12.3823],[43.32909,12.59711],[43.29075,12.79154],[42.86195,12.58747],[42.7996,12.42629],[42.6957,12.36201],[42.46941,12.52661],[42.4037,12.46478],[41.95461,11.81157],[41.82878,11.72361],[41.77727,11.49902]]]}},{"type":"Feature","properties":{"id":"DM"},"geometry":{"type":"Polygon","coordinates":[[[-61.81728,15.58058],[-61.51867,14.96709],[-60.69955,15.22234],[-60.95725,15.70997],[-61.44899,15.79571],[-61.81728,15.58058]]]}},{"type":"Feature","properties":{"id":"DO"},"geometry":{"type":"Polygon","coordinates":[[[-72.29523,17.48026],[-71.87936,17.20162],[-68.20301,17.83927],[-67.99519,18.97186],[-70.39828,20.32236],[-72.17094,20.08703],[-71.77419,19.73128],[-71.75865,19.70231],[-71.7429,19.58445],[-71.71449,19.55364],[-71.71268,19.53374],[-71.6802,19.45008],[-71.69448,19.37866],[-71.77766,19.33823],[-71.73229,19.26686],[-71.62642,19.21212],[-71.65337,19.11759],[-71.69938,19.10916],[-71.71088,19.08353],[-71.74088,19.0437],[-71.88102,18.95007],[-71.77766,18.95007],[-71.72624,18.87802],[-71.71885,18.78423],[-71.82556,18.62551],[-71.95412,18.64939],[-72.00201,18.62312],[-71.88102,18.50125],[-71.90875,18.45821],[-71.69952,18.34101],[-71.78271,18.18302],[-71.75465,18.14405],[-71.74994,18.11115],[-71.73783,18.07177],[-71.75671,18.03456],[-72.29523,17.48026]]]}},{"type":"Feature","properties":{"id":"DZ"},"geometry":{"type":"Polygon","coordinates":[[[-8.6715,28.71194],[-8.66879,27.6666],[-8.66674,27.31569],[-4.83423,24.99935],[1.15698,21.12843],[1.20992,20.73533],[3.24648,19.81703],[3.12501,19.1366],[3.36082,18.9745],[4.26651,19.14224],[5.8153,19.45101],[7.38361,20.79165],[7.48273,20.87258],[11.96886,23.51735],[11.62498,24.26669],[11.41061,24.21456],[10.85323,24.5595],[10.33159,24.5465],[10.02432,24.98124],[10.03146,25.35635],[9.38834,26.19288],[9.51696,26.39148],[9.89569,26.57696],[9.78136,29.40961],[9.3876,30.16738],[9.55544,30.23971],[9.07483,32.07865],[8.35999,32.50101],[8.31895,32.83483],[8.1179,33.05086],[8.11433,33.10175],[7.83028,33.18851],[7.73687,33.42114],[7.54088,33.7726],[7.52851,34.06493],[7.66174,34.20167],[7.74207,34.16492],[7.81242,34.21841],[7.86264,34.3987],[8.20482,34.57575],[8.29655,34.72798],[8.25189,34.92009],[8.30727,34.95378],[8.3555,35.10007],[8.47318,35.23376],[8.30329,35.29884],[8.36086,35.47774],[8.35371,35.66373],[8.26472,35.73669],[8.2626,35.91733],[8.40731,36.42208],[8.18936,36.44939],[8.16167,36.48817],[8.47609,36.66607],[8.46537,36.7706],[8.57613,36.78062],[8.67706,36.8364],[8.62972,36.86499],[8.64044,36.9401],[8.59123,37.14286],[2.46645,37.97429],[-2.27707,35.35051],[-2.21248,35.08532],[-2.21445,35.04378],[-2.04734,34.93218],[-1.97833,34.93218],[-1.97469,34.886],[-1.73707,34.74226],[-1.84569,34.61907],[-1.69788,34.48056],[-1.78042,34.39018],[-1.64666,34.10405],[-1.73494,33.71721],[-1.59508,33.59929],[-1.67067,33.27084],[-1.46249,33.0499],[-1.54244,32.95499],[-1.37794,32.73628],[-0.9912,32.52467],[-1.24998,32.32993],[-1.24453,32.1917],[-1.15735,32.12096],[-1.22829,32.07832],[-2.46166,32.16603],[-2.93873,32.06557],[-2.82784,31.79459],[-3.66314,31.6339],[-3.66386,31.39202],[-3.77647,31.31912],[-3.77103,31.14984],[-3.54944,31.0503],[-3.65418,30.85566],[-3.64735,30.67539],[-4.31774,30.53229],[-4.6058,30.28343],[-5.21671,29.95253],[-5.58831,29.48103],[-5.72121,29.52322],[-5.75616,29.61407],[-6.69965,29.51623],[-6.78351,29.44634],[-6.95824,29.50924],[-7.61585,29.36252],[-8.6715,28.71194]]]}},{"type":"Feature","properties":{"id":"CN-TJ"},"geometry":{"type":"Polygon","coordinates":[[[116.70364,38.93751],[116.75514,38.74176],[116.85539,38.74658],[116.87187,38.68658],[117.02567,38.69837],[117.09297,38.58682],[117.21931,38.63189],[117.24128,38.56373],[117.42736,38.61204],[117.59559,38.61472],[118.33648,38.49229],[118.35983,38.73266],[118.02577,39.21789],[118.06079,39.25166],[117.96363,39.31331],[117.84072,39.32712],[117.84656,39.35421],[117.79197,39.36668],[117.85274,39.38685],[117.92861,39.57499],[117.76245,39.59828],[117.71026,39.52787],[117.65945,39.63848],[117.58872,39.73993],[117.54753,39.7584],[117.50427,39.88971],[117.53517,39.98711],[117.78648,39.96659],[117.75558,40.05389],[117.5537,40.22607],[117.34291,40.23131],[117.40608,40.17362],[117.19425,40.07281],[117.13897,39.8797],[117.26154,39.83385],[117.14755,39.81723],[117.19493,39.75999],[117.14137,39.61203],[116.95083,39.65434],[116.89075,39.69714],[116.80732,39.61415],[116.77848,39.46005],[116.87049,39.43354],[116.81899,39.34491],[116.87942,39.34385],[116.85539,39.16467],[116.91581,39.13112],[116.85882,39.05598],[116.75926,39.04372],[116.70364,38.93751]]]}},{"type":"Feature","properties":{"id":"EC"},"geometry":{"type":"Polygon","coordinates":[[[-93.12365,2.64343],[-92.46744,-2.52874],[-84.52388,-3.36941],[-80.30602,-3.39149],[-80.20647,-3.431],[-80.24123,-3.46124],[-80.24475,-3.47846],[-80.24586,-3.48677],[-80.23651,-3.48652],[-80.22629,-3.501],[-80.20535,-3.51667],[-80.21642,-3.5888],[-80.19848,-3.59249],[-80.18741,-3.63994],[-80.19926,-3.68894],[-80.13232,-3.90317],[-80.46386,-4.01342],[-80.4822,-4.05477],[-80.45023,-4.20938],[-80.32114,-4.21323],[-80.46386,-4.41516],[-80.39256,-4.48269],[-80.13945,-4.29786],[-79.79722,-4.47558],[-79.59402,-4.46848],[-79.26248,-4.95167],[-79.1162,-4.97774],[-79.01659,-5.01481],[-78.85149,-4.66795],[-78.68394,-4.60754],[-78.34362,-3.38633],[-78.24589,-3.39907],[-78.22642,-3.51113],[-78.14324,-3.47653],[-78.19369,-3.36431],[-77.94147,-3.05454],[-76.6324,-2.58397],[-76.05203,-2.12179],[-75.57429,-1.55961],[-75.3872,-0.9374],[-75.22862,-0.95588],[-75.22862,-0.60048],[-75.53615,-0.19213],[-75.60169,-0.18708],[-75.61997,-0.10012],[-75.40192,-0.17196],[-75.25764,-0.11943],[-75.82927,0.09578],[-76.23441,0.42294],[-76.41215,0.38228],[-76.4094,0.24015],[-76.89177,0.24736],[-77.52001,0.40782],[-77.49984,0.64476],[-77.67815,0.73863],[-77.66416,0.81604],[-77.68613,0.83029],[-77.7148,0.85003],[-77.85677,0.80197],[-78.42749,1.15389],[-78.87137,1.47457],[-93.12365,2.64343]]]}},{"type":"Feature","properties":{"id":"EG"},"geometry":{"type":"Polygon","coordinates":[[[24.71117,30.17441],[24.99968,29.24574],[24.99885,21.99535],[33.17563,22.00405],[34.0765,22.00501],[37.8565,22.00903],[34.51305,27.70027],[34.46254,27.99552],[34.88293,29.37455],[34.92298,29.45305],[34.26742,31.21998],[34.24012,31.29591],[34.23572,31.2966],[34.21853,31.32363],[34.052,31.46619],[33.62659,31.82938],[25.63787,31.9359],[25.14001,31.67534],[25.06041,31.57937],[24.83101,31.31921],[25.01077,30.73861],[24.71117,30.17441]]]}},{"type":"Feature","properties":{"id":"CN-JL"},"geometry":{"type":"Polygon","coordinates":[[[121.64337,45.73877],[121.74568,45.68267],[121.95098,45.71097],[121.99836,45.6366],[122.01965,45.48324],[122.16522,45.41484],[122.1453,45.2971],[122.08213,44.91327],[122.11715,44.57237],[122.33001,44.22256],[123.143,44.52294],[123.1327,44.34938],[123.37715,44.15215],[123.3229,44.06045],[123.53301,43.64203],[123.32153,43.48979],[123.68682,43.3756],[123.79806,43.49627],[124.14619,43.2412],[124.28077,43.21268],[124.28832,43.14608],[124.41261,43.06738],[124.35836,42.88854],[124.45793,42.81907],[124.85961,43.17563],[124.84863,42.79086],[124.97909,42.77574],[125.18096,42.29813],[125.25443,42.31387],[125.41854,42.09159],[125.28671,41.9554],[125.32241,41.67034],[125.44738,41.67393],[125.49167,41.53222],[125.54283,41.39767],[125.57544,41.39174],[125.63449,41.33325],[125.63724,41.26877],[125.75225,41.23005],[125.78487,41.16185],[125.63552,40.95086],[125.56892,40.89327],[125.66574,40.91403],[125.71172,40.85223],[125.76869,40.87908],[126.00335,40.92835],[126.242,41.15454],[126.53189,41.35206],[126.60631,41.65565],[126.90729,41.79955],[127.17841,41.59714],[127.29712,41.49473],[127.92943,41.44291],[128.02633,41.42103],[128.03311,41.39232],[128.12967,41.37931],[128.18546,41.41279],[128.20061,41.40895],[128.30716,41.60322],[128.15119,41.74568],[128.04487,42.01769],[128.94007,42.03537],[128.96068,42.06657],[129.15178,42.17224],[129.22285,42.26491],[129.22423,42.3553],[129.28541,42.41574],[129.42882,42.44702],[129.54701,42.37254],[129.60482,42.44461],[129.72541,42.43739],[129.75294,42.59409],[129.77183,42.69435],[129.7835,42.76521],[129.80719,42.79218],[129.83277,42.86746],[129.85261,42.96494],[129.8865,43.00395],[129.95082,43.01051],[129.96409,42.97306],[130.12957,42.98361],[130.09764,42.91425],[130.26095,42.9027],[130.23068,42.80125],[130.2385,42.71127],[130.41826,42.6011],[130.44361,42.54849],[130.50123,42.61636],[130.55143,42.52158],[130.62107,42.58413],[130.56576,42.68925],[130.40213,42.70788],[130.44361,42.76205],[130.66524,42.84753],[131.02438,42.86518],[131.02668,42.91246],[131.135,42.94114],[131.10274,43.04734],[131.20414,43.13654],[131.19031,43.21385],[131.30324,43.39498],[131.29402,43.46695],[130.44616,43.63905],[130.38574,44.03824],[130.07537,43.8048],[129.94903,44.06193],[128.88198,43.50374],[128.43429,44.50825],[127.71331,44.03429],[127.53753,44.55525],[127.03765,44.59242],[127.08846,44.93369],[126.65313,45.23621],[126.00768,45.12296],[125.68771,45.50346],[124.55612,45.41002],[124.13452,45.61692],[123.90655,46.28812],[123.17046,46.2283],[122.79418,45.94064],[122.72003,45.70474],[122.52433,45.7771],[122.39868,45.9139],[122.2586,45.79434],[121.8727,46.04083],[121.75735,45.99505],[121.82121,45.8728],[121.64337,45.73877]]]}},{"type":"Feature","properties":{"id":"ER"},"geometry":{"type":"Polygon","coordinates":[[[36.44337,15.14963],[36.54376,14.25597],[36.56536,14.26177],[36.55659,14.28237],[36.63364,14.31172],[36.85787,14.32201],[37.01622,14.2561],[37.09486,14.27155],[37.13206,14.40746],[37.3106,14.44657],[37.47319,14.2149],[37.528,14.18413],[37.91287,14.89447],[38.0364,14.72745],[38.25562,14.67287],[38.3533,14.51323],[38.45748,14.41445],[38.78306,14.4754],[38.98058,14.54895],[39.02834,14.63717],[39.16074,14.65187],[39.14772,14.61827],[39.19547,14.56996],[39.23888,14.56365],[39.26927,14.48801],[39.2302,14.44598],[39.2519,14.40393],[39.37685,14.54402],[39.52756,14.49011],[39.50585,14.55735],[39.58182,14.60987],[39.76632,14.54264],[39.9443,14.41024],[40.07236,14.54264],[40.14649,14.53969],[40.21128,14.39342],[40.25686,14.41445],[40.9167,14.11152],[41.25097,13.60787],[41.62864,13.38626],[42.05841,12.80912],[42.21469,12.75832],[42.2798,12.6355],[42.4037,12.46478],[42.46941,12.52661],[42.6957,12.36201],[42.7996,12.42629],[42.86195,12.58747],[43.29075,12.79154],[42.63806,13.58268],[41.29956,15.565],[41.37609,16.19728],[39.63762,18.37348],[38.57727,17.98125],[38.45916,17.87167],[38.37133,17.66269],[38.13362,17.53906],[37.50967,17.32199],[37.42694,17.04041],[36.99777,17.07172],[36.92193,16.23451],[36.76371,15.80831],[36.69761,15.75323],[36.54276,15.23478],[36.44337,15.14963]]]}},{"type":"Feature","properties":{"id":"EE"},"geometry":{"type":"Polygon","coordinates":[[[19.84909,57.57876],[22.80496,57.87798],[23.20055,57.56697],[24.26221,57.91787],[24.3579,57.87471],[25.19484,58.0831],[25.28237,57.98539],[25.29581,58.08288],[25.73499,57.90193],[26.05949,57.84744],[26.0324,57.79037],[26.02456,57.78342],[26.027,57.78158],[26.0266,57.77441],[26.02069,57.77169],[26.02415,57.76865],[26.03332,57.7718],[26.0543,57.76105],[26.08098,57.76619],[26.2029,57.7206],[26.1866,57.6849],[26.29253,57.59244],[26.46527,57.56885],[26.54675,57.51813],[26.90364,57.62823],[27.34698,57.52242],[27.31919,57.57672],[27.40393,57.62125],[27.3746,57.66834],[27.52615,57.72843],[27.50171,57.78842],[27.56689,57.83356],[27.78526,57.83963],[27.81841,57.89244],[27.67282,57.92627],[27.62393,58.09462],[27.48541,58.22615],[27.55489,58.39525],[27.36366,58.78381],[27.74429,58.98351],[27.80482,59.1116],[27.87978,59.18097],[27.90911,59.24353],[28.00689,59.28351],[28.14215,59.28934],[28.19284,59.35791],[28.20537,59.36491],[28.21137,59.38058],[28.19061,59.39962],[28.04187,59.47017],[27.85643,59.58538],[26.90044,59.63819],[26.32936,60.00121],[20.5104,59.15546],[19.84909,57.57876]]]}},{"type":"Feature","properties":{"id":"ET"},"geometry":{"type":"Polygon","coordinates":[[[33.0006,7.90333],[33.04944,7.78989],[33.24637,7.77939],[33.32531,7.71297],[33.44745,7.7543],[33.71407,7.65983],[33.87642,7.5491],[34.02984,7.36449],[34.03878,7.27437],[34.01495,7.25664],[34.19369,7.12807],[34.19369,7.04382],[34.35753,6.91963],[34.47669,6.91076],[34.53925,6.82794],[34.53776,6.74808],[34.65096,6.72589],[34.77459,6.5957],[34.87736,6.60161],[35.01738,6.46991],[34.96227,6.26415],[35.00546,5.89387],[35.12611,5.68937],[35.13058,5.62118],[35.31188,5.50106],[35.29938,5.34042],[35.50792,5.42431],[35.8576,5.33413],[35.81968,5.10757],[35.82118,4.77382],[35.9419,4.61933],[35.95449,4.53244],[36.03924,4.44406],[36.84474,4.44518],[37.07724,4.33503],[38.14168,3.62487],[38.45812,3.60445],[38.52336,3.62551],[38.91938,3.51198],[39.07736,3.5267],[39.19954,3.47834],[39.49444,3.45521],[39.51551,3.40895],[39.55132,3.39634],[39.58339,3.47434],[39.76808,3.67058],[39.86043,3.86974],[40.77498,4.27683],[41.1754,3.94079],[41.89488,3.97375],[42.07619,4.17667],[42.55853,4.20518],[42.84526,4.28357],[42.97746,4.44032],[43.04177,4.57923],[43.40263,4.79289],[44.02436,4.9451],[44.98104,4.91821],[47.97917,8.00124],[47.92477,8.00111],[46.99339,7.9989],[44.19222,8.93028],[43.32613,9.59205],[43.23518,9.84605],[43.0937,9.90579],[42.87643,10.18441],[42.69452,10.62672],[42.95776,10.98533],[42.79037,10.98493],[42.75111,11.06992],[42.62989,11.09711],[42.42669,10.98493],[42.13691,10.97586],[42.06302,10.92599],[41.80056,10.97127],[41.8096,11.33606],[41.77727,11.49902],[41.82878,11.72361],[41.95461,11.81157],[42.4037,12.46478],[42.2798,12.6355],[42.21469,12.75832],[42.05841,12.80912],[41.62864,13.38626],[41.25097,13.60787],[40.9167,14.11152],[40.25686,14.41445],[40.21128,14.39342],[40.14649,14.53969],[40.07236,14.54264],[39.9443,14.41024],[39.76632,14.54264],[39.58182,14.60987],[39.50585,14.55735],[39.52756,14.49011],[39.37685,14.54402],[39.2519,14.40393],[39.2302,14.44598],[39.26927,14.48801],[39.23888,14.56365],[39.19547,14.56996],[39.14772,14.61827],[39.16074,14.65187],[39.02834,14.63717],[38.98058,14.54895],[38.78306,14.4754],[38.45748,14.41445],[38.3533,14.51323],[38.25562,14.67287],[38.0364,14.72745],[37.91287,14.89447],[37.528,14.18413],[37.47319,14.2149],[37.3106,14.44657],[37.13206,14.40746],[37.09486,14.27155],[37.01622,14.2561],[36.85787,14.32201],[36.63364,14.31172],[36.55659,14.28237],[36.56536,14.26177],[36.54376,14.25597],[36.44653,13.95666],[36.48824,13.83954],[36.38993,13.56459],[36.24545,13.36759],[36.13374,12.92665],[36.16651,12.88019],[36.14268,12.70879],[36.01458,12.72478],[35.70476,12.67101],[35.24302,11.91132],[35.11492,11.85156],[35.05832,11.71158],[35.09556,11.56278],[34.95704,11.24448],[35.01215,11.19626],[34.93172,10.95946],[34.97789,10.91559],[34.97491,10.86147],[34.86916,10.78832],[34.86618,10.74588],[34.77532,10.69027],[34.77383,10.74588],[34.59062,10.89072],[34.4372,10.781],[34.2823,10.53508],[34.34783,10.23914],[34.32102,10.11599],[34.22718,10.02506],[34.20484,9.9033],[34.13186,9.7492],[34.08717,9.55243],[34.10229,9.50238],[34.14304,9.04654],[34.14453,8.60204],[34.01346,8.50041],[33.89579,8.4842],[33.87195,8.41938],[33.71407,8.3678],[33.66938,8.44442],[33.54575,8.47094],[33.3119,8.45474],[33.19721,8.40317],[33.1853,8.29264],[33.18083,8.13047],[33.08401,8.05822],[33.0006,7.90333]]]}},{"type":"Feature","properties":{"id":"FI"},"geometry":{"type":"Polygon","coordinates":[[[19.08191,60.19152],[20.5104,59.15546],[26.32936,60.00121],[27.44953,60.22766],[27.71177,60.3893],[27.77352,60.52722],[28.47974,60.93365],[28.82816,61.1233],[29.01829,61.17448],[31.10136,62.43042],[31.38369,62.66284],[31.58535,62.91642],[31.29294,63.09035],[31.23244,63.22239],[30.49637,63.46666],[29.98213,63.75795],[30.25437,63.83364],[30.55687,64.09036],[30.4762,64.25728],[30.06279,64.35782],[30.01238,64.57513],[30.12329,64.64862],[30.05271,64.79072],[29.68972,64.80789],[29.61914,65.05993],[29.84096,65.1109],[29.8813,65.22101],[29.61914,65.23791],[29.68972,65.31803],[29.84096,65.56945],[29.74013,65.64025],[29.97205,65.70256],[30.16363,65.66935],[29.91155,66.13863],[28.9839,66.94139],[29.91155,67.51507],[30.02041,67.67523],[29.66955,67.79872],[29.34179,68.06655],[28.62982,68.19816],[28.43941,68.53366],[28.78224,68.86696],[28.45957,68.91417],[28.91738,69.04774],[28.81248,69.11997],[28.8629,69.22395],[29.31664,69.47994],[29.12697,69.69193],[28.36883,69.81658],[28.32849,69.88605],[27.97558,69.99671],[27.95542,70.0965],[27.57226,70.06215],[27.05802,69.92069],[26.64461,69.96565],[26.40261,69.91377],[25.96904,69.68397],[25.69679,69.27039],[25.75729,68.99383],[25.61613,68.89602],[25.42455,68.90328],[25.12206,68.78684],[25.10189,68.63307],[24.93048,68.61102],[24.90023,68.55579],[24.74898,68.65143],[24.18432,68.73936],[24.02299,68.81601],[23.781,68.84514],[23.68017,68.70276],[23.13064,68.64684],[22.53321,68.74393],[22.38367,68.71561],[22.27276,68.89514],[21.63833,69.27485],[21.27827,69.31281],[21.00732,69.22755],[20.98641,69.18809],[21.11099,69.10291],[21.05775,69.0356],[20.72171,69.11874],[20.55258,69.06069],[20.78802,69.03087],[20.91658,68.96764],[20.85104,68.93142],[20.90649,68.89696],[21.03001,68.88969],[22.00429,68.50692],[22.73028,68.40881],[23.10336,68.26551],[23.15377,68.14759],[23.26469,68.15134],[23.40081,68.05545],[23.65793,67.9497],[23.45627,67.85297],[23.54701,67.59306],[23.39577,67.46974],[23.75372,67.43688],[23.75372,67.29914],[23.54701,67.25435],[23.58735,67.20752],[23.56214,67.17038],[23.98563,66.84149],[23.98059,66.79585],[23.89488,66.772],[23.85959,66.56434],[23.63776,66.43568],[23.67591,66.3862],[23.64982,66.30603],[23.71339,66.21299],[23.90497,66.15802],[24.15791,65.85385],[24.14798,65.83466],[24.15107,65.81427],[24.14112,65.39731],[20.15877,63.06556],[19.23413,60.61414],[19.08191,60.19152]]]}},{"type":"Feature","properties":{"id":"NC"},"geometry":{"type":"Polygon","coordinates":[[[157.46481,-18.93777],[158.4748,-21.86428],[166.93331,-23.49588],[173.07304,-22.54607],[162.93363,-17.28904],[157.46481,-18.93777]]]}},{"type":"Feature","properties":{"id":"GF"},"geometry":{"type":"Polygon","coordinates":[[[-54.6084,2.32856],[-54.16286,2.10779],[-53.78743,2.34412],[-52.96539,2.1881],[-52.6906,2.37298],[-52.31787,3.17896],[-51.85573,3.83427],[-51.82312,3.85825],[-51.79599,3.89336],[-51.61983,4.14596],[-51.63798,4.51394],[-51.35485,4.8383],[-53.7094,6.2264],[-54.01074,5.68785],[-54.01877,5.52789],[-54.26916,5.26909],[-54.4717,4.91964],[-54.38444,4.13222],[-54.19367,3.84387],[-54.05128,3.63557],[-53.98914,3.627],[-53.9849,3.58697],[-54.28534,2.67798],[-54.42864,2.42442],[-54.6084,2.32856]]]}},{"type":"Feature","properties":{"id":"MQ"},"geometry":{"type":"Polygon","coordinates":[[[-61.51867,14.96709],[-61.26561,14.25664],[-60.5958,14.23076],[-60.69955,15.22234],[-61.51867,14.96709]]]}},{"type":"Feature","properties":{"id":"GP"},"geometry":{"type":"Polygon","coordinates":[[[-62.17275,16.35721],[-61.81728,15.58058],[-61.44899,15.79571],[-60.95725,15.70997],[-60.71337,16.48911],[-61.44461,16.81958],[-61.83929,16.66647],[-62.17275,16.35721]]]}},{"type":"Feature","properties":{"id":"FX"},"geometry":{"type":"Polygon","coordinates":[[[-5.81385,48.52441],[-1.81005,43.59738],[-1.77289,43.38957],[-1.79319,43.37497],[-1.78332,43.36399],[-1.78714,43.35476],[-1.77068,43.34396],[-1.75334,43.34107],[-1.75079,43.3317],[-1.7397,43.32979],[-1.73074,43.29481],[-1.69407,43.31378],[-1.62481,43.30726],[-1.63052,43.28591],[-1.61341,43.25269],[-1.57674,43.25269],[-1.55963,43.28828],[-1.50992,43.29481],[-1.45289,43.27049],[-1.40942,43.27272],[-1.3758,43.24511],[-1.41562,43.12815],[-1.47555,43.08372],[-1.44067,43.047],[-1.35272,43.02658],[-1.34419,43.09665],[-1.32209,43.1127],[-1.27118,43.11961],[-1.30052,43.09581],[-1.30531,43.06859],[-1.25244,43.04164],[-1.22881,43.05534],[-1.10333,43.0059],[-1.00963,42.99279],[-0.97133,42.96239],[-0.81652,42.95166],[-0.75478,42.96916],[-0.72037,42.92541],[-0.73422,42.91228],[-0.72608,42.89318],[-0.69837,42.87945],[-0.67637,42.88303],[-0.55497,42.77846],[-0.50863,42.82713],[-0.44334,42.79939],[-0.41319,42.80776],[-0.38833,42.80132],[-0.3122,42.84788],[-0.17939,42.78974],[-0.16141,42.79535],[-0.10519,42.72761],[-0.02468,42.68513],[0.17569,42.73424],[0.25336,42.7174],[0.29407,42.67431],[0.36251,42.72282],[0.40214,42.69779],[0.67873,42.69458],[0.65421,42.75872],[0.66121,42.84021],[0.711,42.86372],[0.93089,42.79154],[0.96166,42.80629],[0.98292,42.78754],[1.0804,42.78569],[1.15928,42.71407],[1.35562,42.71944],[1.44197,42.60217],[1.47986,42.61346],[1.46718,42.63296],[1.48043,42.65203],[1.50867,42.64483],[1.55418,42.65669],[1.60085,42.62703],[1.63485,42.62957],[1.6625,42.61982],[1.68267,42.62533],[1.73452,42.61515],[1.72588,42.59098],[1.7858,42.57698],[1.73683,42.55492],[1.72515,42.50338],[1.76335,42.48863],[1.83037,42.48395],[1.88853,42.4501],[1.93663,42.45439],[1.94292,42.44316],[1.94061,42.43333],[1.94084,42.43039],[1.9574,42.42401],[1.96482,42.37787],[2.00488,42.35399],[2.06241,42.35906],[2.11621,42.38393],[2.12789,42.41291],[2.16599,42.42314],[2.20578,42.41633],[2.25551,42.43757],[2.38504,42.39977],[2.43299,42.39423],[2.43508,42.37568],[2.48457,42.33933],[2.54382,42.33406],[2.55516,42.35351],[2.57934,42.35808],[2.6747,42.33974],[2.65311,42.38771],[2.72056,42.42298],[2.75497,42.42578],[2.77464,42.41046],[2.84335,42.45724],[2.85675,42.45444],[2.86983,42.46843],[2.88413,42.45938],[2.92107,42.4573],[2.94283,42.48174],[2.96518,42.46692],[3.03734,42.47363],[3.08167,42.42748],[3.10027,42.42621],[3.11379,42.43646],[3.17156,42.43545],[3.4481,42.4358],[7.52234,41.54558],[8.80584,41.26173],[9.28609,41.32097],[9.62656,41.44198],[9.86526,42.21008],[9.56115,43.20816],[7.50102,43.51859],[7.41855,43.72479],[7.40903,43.7296],[7.41113,43.73156],[7.41291,43.73168],[7.41298,43.73311],[7.41233,43.73439],[7.42062,43.73977],[7.42299,43.74176],[7.42443,43.74087],[7.42809,43.74396],[7.43013,43.74895],[7.43624,43.75014],[7.43708,43.75197],[7.4389,43.75151],[7.4379,43.74963],[7.45448,43.7432],[7.53358,43.53609],[7.63035,43.57419],[7.5289,43.78887],[7.50423,43.84345],[7.49355,43.86551],[7.51162,43.88301],[7.56075,43.89932],[7.56858,43.94506],[7.60771,43.95772],[7.65266,43.9763],[7.66848,43.99943],[7.6597,44.03009],[7.72508,44.07578],[7.66878,44.12795],[7.68694,44.17487],[7.63245,44.17877],[7.62155,44.14881],[7.36364,44.11882],[7.34547,44.14359],[7.27827,44.1462],[7.16929,44.20352],[7.00764,44.23736],[6.98221,44.28289],[6.89171,44.36637],[6.88784,44.42043],[6.94504,44.43112],[6.86233,44.49834],[6.85507,44.53072],[6.96042,44.62129],[6.95133,44.66264],[7.00582,44.69364],[7.07484,44.68073],[7.00401,44.78782],[7.02217,44.82519],[6.93499,44.8664],[6.90774,44.84322],[6.75518,44.89915],[6.74519,44.93661],[6.74791,45.01939],[6.66981,45.02324],[6.62803,45.11175],[6.7697,45.16044],[6.85144,45.13226],[6.96706,45.20841],[7.07074,45.21228],[7.13115,45.25386],[7.10572,45.32924],[7.18019,45.40071],[7.00037,45.509],[6.98948,45.63869],[6.80785,45.71864],[6.80785,45.83265],[6.95315,45.85163],[7.04151,45.92435],[7.00946,45.9944],[6.93862,46.06502],[6.87868,46.03855],[6.89321,46.12548],[6.78968,46.14058],[6.86052,46.28512],[6.77152,46.34784],[6.8024,46.39171],[6.82312,46.42661],[6.53358,46.45431],[6.25432,46.3632],[6.21981,46.31304],[6.24826,46.30175],[6.25137,46.29014],[6.23775,46.27822],[6.24952,46.26255],[6.26749,46.24745],[6.29474,46.26221],[6.31041,46.24417],[6.29663,46.22688],[6.27694,46.21566],[6.26007,46.21165],[6.24821,46.20531],[6.23913,46.20511],[6.23544,46.20714],[6.22175,46.20045],[6.22222,46.19888],[6.21844,46.19837],[6.21603,46.19507],[6.21273,46.19409],[6.21114,46.1927],[6.20539,46.19163],[6.19807,46.18369],[6.19552,46.18401],[6.18707,46.17999],[6.18871,46.16644],[6.18116,46.16187],[6.15305,46.15194],[6.13397,46.1406],[6.09926,46.14373],[6.09199,46.15191],[6.07491,46.14879],[6.05203,46.15191],[6.04564,46.14031],[6.03614,46.13712],[6.01791,46.14228],[5.9871,46.14499],[5.97893,46.13303],[5.95781,46.12925],[5.9641,46.14412],[5.97508,46.15863],[5.98188,46.17392],[5.98846,46.17046],[5.99573,46.18587],[5.96515,46.19638],[5.97542,46.21525],[6.02461,46.23313],[6.03342,46.2383],[6.04602,46.23127],[6.05029,46.23518],[6.0633,46.24583],[6.07072,46.24085],[6.08563,46.24651],[6.10071,46.23772],[6.12446,46.25059],[6.11926,46.2634],[6.1013,46.28512],[6.11697,46.29547],[6.1198,46.31157],[6.13876,46.33844],[6.15738,46.3491],[6.16987,46.36759],[6.15985,46.37721],[6.15016,46.3778],[6.09926,46.40768],[6.06407,46.41676],[6.08427,46.44305],[6.07269,46.46244],[6.1567,46.54402],[6.11084,46.57649],[6.27135,46.68251],[6.38351,46.73171],[6.45209,46.77502],[6.43216,46.80336],[6.46456,46.88865],[6.43341,46.92703],[6.71531,47.0494],[6.68823,47.06616],[6.76788,47.1208],[6.8489,47.15933],[6.9508,47.24338],[6.95108,47.26428],[6.94316,47.28747],[7.05305,47.33304],[7.0564,47.35134],[7.03125,47.36996],[6.87959,47.35335],[6.88542,47.37262],[6.93744,47.40714],[6.93953,47.43388],[7.0024,47.45264],[6.98425,47.49432],[7.0231,47.50522],[7.07425,47.48863],[7.12781,47.50371],[7.16249,47.49025],[7.19583,47.49455],[7.17026,47.44312],[7.24669,47.4205],[7.33526,47.44186],[7.35603,47.43432],[7.40308,47.43638],[7.43088,47.45846],[7.4462,47.46264],[7.4583,47.47216],[7.42923,47.48628],[7.43356,47.49712],[7.47534,47.47932],[7.51076,47.49651],[7.49804,47.51798],[7.5229,47.51644],[7.53199,47.5284],[7.51904,47.53515],[7.50588,47.52856],[7.49691,47.53821],[7.50873,47.54546],[7.51723,47.54578],[7.52831,47.55347],[7.53634,47.55553],[7.55652,47.56779],[7.55689,47.57232],[7.56548,47.57617],[7.56684,47.57785],[7.58386,47.57536],[7.58945,47.59017],[7.59301,47.60058],[7.58851,47.60794],[7.57423,47.61628],[7.5591,47.63849],[7.53384,47.65115],[7.52067,47.66437],[7.51915,47.68335],[7.51266,47.70197],[7.53722,47.71635],[7.54761,47.72912],[7.52921,47.77747],[7.55673,47.87371],[7.62302,47.97898],[7.56966,48.03265],[7.57137,48.12292],[7.6648,48.22219],[7.69022,48.30018],[7.74562,48.32736],[7.73109,48.39192],[7.76833,48.48945],[7.80647,48.51239],[7.80167,48.54758],[7.80057,48.5857],[7.84098,48.64217],[7.89002,48.66317],[7.96812,48.72491],[7.96994,48.75606],[8.01534,48.76085],[8.0326,48.79017],[8.06802,48.78957],[8.10253,48.81829],[8.12813,48.87985],[8.19989,48.95825],[8.20031,48.95856],[8.22604,48.97352],[8.14189,48.97833],[7.97783,49.03161],[7.93641,49.05544],[7.86386,49.03499],[7.79557,49.06583],[7.75948,49.04562],[7.63618,49.05428],[7.62575,49.07654],[7.56416,49.08136],[7.53012,49.09818],[7.49172,49.13915],[7.49473,49.17],[7.44455,49.16765],[7.44052,49.18354],[7.3662,49.17308],[7.35995,49.14399],[7.3195,49.14231],[7.29514,49.11426],[7.23473,49.12971],[7.1593,49.1204],[7.1358,49.1282],[7.12504,49.14253],[7.10384,49.13787],[7.10715,49.15631],[7.07859,49.15031],[7.09007,49.13094],[7.07162,49.1255],[7.06642,49.11415],[7.05548,49.11185],[7.04843,49.11422],[7.04409,49.12123],[7.04662,49.13724],[7.03178,49.15734],[7.0274,49.17042],[7.03459,49.19096],[7.01318,49.19018],[6.97273,49.2099],[6.95963,49.203],[6.94028,49.21641],[6.93831,49.2223],[6.91875,49.22261],[6.89298,49.20863],[6.85939,49.22376],[6.83555,49.21249],[6.85119,49.20038],[6.85016,49.19354],[6.86225,49.18185],[6.84703,49.15734],[6.83385,49.15162],[6.78265,49.16793],[6.73765,49.16375],[6.71137,49.18808],[6.73256,49.20486],[6.71843,49.2208],[6.69274,49.21661],[6.66583,49.28065],[6.60186,49.31055],[6.572,49.35027],[6.58807,49.35358],[6.60091,49.36864],[6.533,49.40748],[6.55404,49.42464],[6.42432,49.47683],[6.40274,49.46546],[6.39168,49.4667],[6.38352,49.46463],[6.36778,49.46937],[6.3687,49.4593],[6.28818,49.48465],[6.27875,49.503],[6.25029,49.50609],[6.2409,49.51408],[6.19543,49.50536],[6.17386,49.50934],[6.15366,49.50226],[6.16115,49.49297],[6.14321,49.48796],[6.12814,49.49365],[6.12346,49.4735],[6.10325,49.4707],[6.09845,49.46351],[6.10072,49.45268],[6.08373,49.45594],[6.07887,49.46399],[6.05553,49.46663],[6.04176,49.44801],[6.02743,49.44845],[6.02648,49.45451],[5.97693,49.45513],[5.96876,49.49053],[5.94224,49.49608],[5.94128,49.50034],[5.86571,49.50015],[5.83389,49.52152],[5.83467,49.52717],[5.84466,49.53027],[5.83648,49.5425],[5.81664,49.53775],[5.80871,49.5425],[5.81838,49.54777],[5.79195,49.55228],[5.77435,49.56298],[5.7577,49.55915],[5.75649,49.54321],[5.64505,49.55146],[5.60909,49.51228],[5.55001,49.52729],[5.46541,49.49825],[5.46734,49.52648],[5.43713,49.5707],[5.3974,49.61596],[5.34837,49.62889],[5.33851,49.61599],[5.3137,49.61225],[5.30214,49.63055],[5.33039,49.6555],[5.31465,49.66846],[5.26232,49.69456],[5.14545,49.70287],[5.09249,49.76193],[4.96714,49.79872],[4.85464,49.78995],[4.86965,49.82271],[4.85134,49.86457],[4.88529,49.9236],[4.78827,49.95609],[4.8382,50.06738],[4.88602,50.15182],[4.83279,50.15331],[4.82438,50.16878],[4.75237,50.11314],[4.70064,50.09384],[4.68695,49.99685],[4.5414,49.96911],[4.51098,49.94659],[4.43488,49.94122],[4.35051,49.95315],[4.31963,49.97043],[4.20532,49.95803],[4.14239,49.98034],[4.13508,50.01976],[4.16294,50.04719],[4.23101,50.06945],[4.20147,50.13535],[4.13561,50.13078],[4.16014,50.19239],[4.15524,50.21103],[4.21945,50.25539],[4.20651,50.27333],[4.17861,50.27443],[4.17347,50.28838],[4.15524,50.2833],[4.16808,50.25786],[4.13665,50.25609],[4.11954,50.30425],[4.10957,50.30234],[4.10237,50.31247],[4.0689,50.3254],[4.0268,50.35793],[3.96771,50.34989],[3.90781,50.32814],[3.84314,50.35219],[3.73911,50.34809],[3.70987,50.3191],[3.71009,50.30305],[3.66976,50.34563],[3.65709,50.36873],[3.67262,50.38663],[3.67494,50.40239],[3.66153,50.45165],[3.64426,50.46275],[3.61014,50.49568],[3.58361,50.49049],[3.5683,50.50192],[3.49509,50.48885],[3.51564,50.5256],[3.47385,50.53397],[3.44629,50.51009],[3.37693,50.49538],[3.28575,50.52724],[3.2729,50.60718],[3.23951,50.6585],[3.264,50.67668],[3.2536,50.68977],[3.26141,50.69151],[3.26063,50.70086],[3.24593,50.71389],[3.22042,50.71019],[3.20845,50.71662],[3.19017,50.72569],[3.20064,50.73547],[3.18811,50.74025],[3.18339,50.74981],[3.16476,50.76843],[3.15017,50.79031],[3.1257,50.78603],[3.11987,50.79188],[3.11206,50.79416],[3.10614,50.78303],[3.09163,50.77717],[3.04314,50.77674],[3.00537,50.76588],[2.96778,50.75242],[2.95019,50.75138],[2.90873,50.702],[2.91036,50.6939],[2.90069,50.69263],[2.88504,50.70656],[2.87937,50.70298],[2.86985,50.7033],[2.8483,50.72276],[2.81056,50.71773],[2.71165,50.81295],[2.63331,50.81457],[2.59093,50.91751],[2.63074,50.94746],[2.57551,51.00326],[2.55904,51.07014],[2.18458,51.52087],[1.17405,50.74239],[-2.02963,49.91866],[-2.09454,49.46288],[-1.83944,49.23037],[-2.00491,48.86706],[-2.5234,48.91595],[-2.56423,49.22209],[-2.9511,49.31141],[-5.81385,48.52441]]]}},{"type":"Feature","properties":{"id":"GA"},"geometry":{"type":"Polygon","coordinates":[[[7.24416,-0.64092],[10.75913,-4.39519],[11.12647,-3.94169],[11.22301,-3.69888],[11.48764,-3.51089],[11.57949,-3.52798],[11.68608,-3.68942],[11.87083,-3.71571],[11.92719,-3.62768],[11.8318,-3.5812],[11.96554,-3.30267],[11.70227,-3.17465],[11.70558,-3.0773],[11.80365,-3.00424],[11.64798,-2.81146],[11.5359,-2.85654],[11.64487,-2.61865],[11.57637,-2.33379],[11.74605,-2.39936],[11.96866,-2.33559],[12.04895,-2.41704],[12.47925,-2.32626],[12.44656,-1.92025],[12.61312,-1.8129],[12.82172,-1.91091],[13.02759,-2.33098],[13.47977,-2.43224],[13.75884,-2.09293],[13.92073,-2.35581],[13.85846,-2.46935],[14.10442,-2.49268],[14.23829,-2.33715],[14.16202,-2.23916],[14.23518,-2.15671],[14.25932,-1.97624],[14.41838,-1.89412],[14.52569,-0.57818],[14.41887,-0.44799],[14.2165,-0.38261],[14.06862,-0.20826],[13.90632,-0.2287],[13.88648,0.26652],[14.10909,0.58563],[14.26066,0.57255],[14.48179,0.9152],[14.25186,1.39842],[13.89582,1.4261],[13.15519,1.23368],[13.25447,1.32339],[13.13461,1.57238],[13.29457,2.16106],[13.28534,2.25716],[11.37116,2.29975],[11.3561,2.17217],[11.35307,1.00251],[9.79648,1.0019],[9.78058,1.03996],[9.76085,1.05949],[9.73014,1.06721],[9.68638,1.06836],[9.66092,1.05865],[9.62096,1.03039],[9.54793,1.0185],[9.51998,0.96418],[9.35563,0.84865],[9.00916,0.69445],[7.24416,-0.64092]]]}},{"type":"Feature","properties":{"id":"MS"},"geometry":{"type":"Polygon","coordinates":[[[-62.52079,16.69392],[-62.17275,16.35721],[-61.83929,16.66647],[-62.14123,17.02632],[-62.52079,16.69392]]]}},{"type":"Feature","properties":{"id":"BM"},"geometry":{"type":"Polygon","coordinates":[[[-65.23529,32.66274],[-65.22652,31.98296],[-64.37503,31.99084],[-64.3838,32.67056],[-65.23529,32.66274]]]}},{"type":"Feature","properties":{"id":"GI"},"geometry":{"type":"Polygon","coordinates":[[[-5.40134,36.14896],[-5.39074,36.10278],[-5.36503,36.06205],[-5.32837,36.05935],[-5.3004,36.07439],[-5.28217,36.09907],[-5.27801,36.14942],[-5.33822,36.15272],[-5.34536,36.15501],[-5.36494,36.15496],[-5.38545,36.15481],[-5.40134,36.14896]]]}},{"type":"Feature","properties":{"id":"GE"},"geometry":{"type":"Polygon","coordinates":[[[39.81147,43.06294],[40.89217,41.72528],[41.54366,41.52185],[41.7148,41.4932],[41.7124,41.47417],[41.81939,41.43621],[41.95134,41.52466],[42.26387,41.49346],[42.51772,41.43606],[42.59202,41.58183],[42.72794,41.59714],[42.84471,41.58912],[42.78995,41.50126],[42.84899,41.47265],[42.8785,41.50516],[43.02956,41.37891],[43.21707,41.30331],[43.13373,41.25503],[43.1945,41.25242],[43.23096,41.17536],[43.36118,41.2028],[43.44973,41.17666],[43.4717,41.12611],[43.67712,41.13398],[43.74717,41.1117],[43.84835,41.16329],[44.16591,41.19141],[44.18148,41.24644],[44.32139,41.2079],[44.34337,41.20312],[44.34417,41.2382],[44.46791,41.18204],[44.59322,41.1933],[44.61462,41.24018],[44.72814,41.20338],[44.82084,41.21513],[44.87887,41.20195],[44.89911,41.21366],[44.84358,41.23088],[44.81749,41.23488],[44.80053,41.25949],[44.81437,41.30371],[44.93493,41.25685],[45.0133,41.29747],[45.09867,41.34065],[45.1797,41.42231],[45.26285,41.46433],[45.31352,41.47168],[45.4006,41.42402],[45.45973,41.45898],[45.68389,41.3539],[45.71035,41.36208],[45.75705,41.35157],[45.69946,41.29545],[45.80842,41.2229],[45.95786,41.17956],[46.13221,41.19479],[46.27698,41.19011],[46.37661,41.10805],[46.456,41.09984],[46.48558,41.0576],[46.55096,41.1104],[46.63969,41.09515],[46.66148,41.20533],[46.72375,41.28609],[46.63658,41.37727],[46.4669,41.43331],[46.40307,41.48464],[46.33925,41.4963],[46.29794,41.5724],[46.34126,41.57454],[46.34039,41.5947],[46.3253,41.60912],[46.28182,41.60089],[46.26531,41.63339],[46.24429,41.59883],[46.19759,41.62327],[46.17891,41.72094],[46.20538,41.77205],[46.23962,41.75811],[46.30863,41.79133],[46.3984,41.84399],[46.42738,41.91323],[45.61676,42.20768],[45.78692,42.48358],[45.36501,42.55268],[45.15318,42.70598],[44.88754,42.74934],[44.80941,42.61277],[44.70002,42.74679],[44.54202,42.75699],[43.95517,42.55396],[43.73119,42.62043],[43.81453,42.74297],[43.0419,43.02413],[43.03322,43.08883],[42.75889,43.19651],[42.66667,43.13917],[42.40563,43.23226],[41.64935,43.22331],[40.65957,43.56212],[40.10657,43.57344],[40.04445,43.47776],[40.03312,43.44262],[40.01007,43.42411],[40.01552,43.42025],[40.00853,43.40578],[40.0078,43.38551],[39.81147,43.06294]]]}},{"type":"Feature","properties":{"id":"GH"},"geometry":{"type":"Polygon","coordinates":[[[-3.34019,4.17519],[1.07031,5.15655],[1.27574,5.93551],[1.19771,6.11522],[1.19966,6.17069],[1.09187,6.17074],[1.05969,6.22998],[1.03108,6.24064],[0.99652,6.33779],[0.89283,6.33779],[0.71048,6.53083],[0.74862,6.56517],[0.63659,6.63857],[0.6497,6.73682],[0.58176,6.76049],[0.57406,6.80348],[0.52853,6.82921],[0.56508,6.92971],[0.52098,6.94391],[0.52217,6.9723],[0.59606,7.01252],[0.65327,7.31643],[0.62943,7.41099],[0.57223,7.39326],[0.52455,7.45354],[0.51979,7.58706],[0.58295,7.62368],[0.62943,7.85751],[0.58891,8.12779],[0.6056,8.13959],[0.61156,8.18324],[0.5913,8.19622],[0.63897,8.25873],[0.73432,8.29529],[0.64731,8.48866],[0.47211,8.59945],[0.37319,8.75262],[0.52455,8.87746],[0.45424,9.04581],[0.56388,9.40697],[0.49118,9.48339],[0.36485,9.49749],[0.33148,9.44812],[0.25758,9.42696],[0.2254,9.47869],[0.31241,9.50337],[0.30406,9.521],[0.2409,9.52335],[0.23851,9.57389],[0.38153,9.58682],[0.36008,9.6256],[0.29334,9.59387],[0.26712,9.66437],[0.28261,9.69022],[0.32313,9.6491],[0.34816,9.66907],[0.34816,9.71607],[0.32075,9.72781],[0.36366,10.03309],[0.41252,10.02018],[0.41371,10.06361],[0.35293,10.09412],[0.39584,10.31112],[0.33028,10.30408],[0.29453,10.41546],[0.18846,10.4096],[0.12886,10.53149],[-0.05945,10.63458],[-0.09141,10.7147],[-0.07327,10.71845],[-0.07183,10.76794],[-0.0228,10.81916],[-0.02685,10.8783],[-0.00908,10.91644],[-0.0063,10.96417],[0.03355,10.9807],[0.02395,11.06229],[0.00342,11.08317],[-0.00514,11.10763],[-0.0275,11.11202],[-0.05733,11.08628],[-0.14462,11.10811],[-0.13493,11.14075],[-0.27374,11.17157],[-0.28566,11.12713],[-0.35955,11.07801],[-0.38219,11.12596],[-0.42391,11.11661],[-0.44298,11.04292],[-0.61937,10.91305],[-0.67143,10.99811],[-2.83373,11.0067],[-2.94232,10.64281],[-2.83108,10.40252],[-2.74174,9.83172],[-2.76534,9.56589],[-2.68802,9.49343],[-2.69814,9.22717],[-2.77799,9.04949],[-2.66357,9.01771],[-2.58243,8.7789],[-2.49037,8.20872],[-2.62901,8.11495],[-2.61232,8.02645],[-2.67787,8.02055],[-2.74819,7.92613],[-2.78395,7.94974],[-2.79467,7.86002],[-2.92339,7.60847],[-2.97822,7.27165],[-2.95438,7.23737],[-3.23327,6.81744],[-3.21954,6.74407],[-3.25999,6.62521],[-3.01896,5.71697],[-2.95323,5.71865],[-2.96671,5.6415],[-2.93132,5.62137],[-2.85378,5.65156],[-2.76614,5.60963],[-2.72737,5.34789],[-2.77625,5.34621],[-2.73074,5.1364],[-2.75502,5.10657],[-2.95261,5.12477],[-2.96554,5.10397],[-3.063,5.13665],[-3.11073,5.12675],[-3.10675,5.08515],[-3.34019,4.17519]]]}},{"type":"Feature","properties":{"id":"GN"},"geometry":{"type":"Polygon","coordinates":[[[-15.96748,10.162],[-14.36218,8.64107],[-13.29911,9.04245],[-13.18586,9.0925],[-13.08953,9.0409],[-12.94095,9.26335],[-12.76788,9.3133],[-12.47254,9.86834],[-12.24262,9.92386],[-12.12634,9.87203],[-11.91023,9.93927],[-11.89624,9.99763],[-11.2118,10.00098],[-10.6534,9.29919],[-10.74484,9.07998],[-10.5783,9.06386],[-10.56197,8.81225],[-10.47707,8.67669],[-10.61422,8.5314],[-10.70565,8.29235],[-10.63934,8.35326],[-10.54891,8.31174],[-10.37257,8.48941],[-10.27575,8.48711],[-10.203,8.47991],[-10.14579,8.52665],[-10.05375,8.50697],[-10.05873,8.42578],[-9.77763,8.54633],[-9.47415,8.35195],[-9.50898,8.18455],[-9.41445,8.02448],[-9.44928,7.9284],[-9.35724,7.74111],[-9.37465,7.62032],[-9.48161,7.37122],[-9.41943,7.41809],[-9.305,7.42056],[-9.20798,7.38109],[-9.18311,7.30461],[-9.09107,7.1985],[-8.93435,7.2824],[-8.85724,7.26019],[-8.8448,7.35149],[-8.72789,7.51429],[-8.67814,7.69428],[-8.55874,7.70167],[-8.55874,7.62525],[-8.47114,7.55676],[-8.4003,7.6285],[-8.21374,7.54466],[-8.09931,7.78626],[-8.13414,7.87991],[-8.06449,8.04989],[-7.94695,8.00925],[-7.99919,8.11023],[-7.98675,8.20134],[-8.062,8.16071],[-8.2411,8.24196],[-8.22991,8.48438],[-7.92518,8.50652],[-7.65653,8.36873],[-7.69882,8.66148],[-7.95503,8.81146],[-7.92518,8.99332],[-7.73862,9.08422],[-7.90777,9.20456],[-7.85056,9.41812],[-8.03463,9.39604],[-8.14657,9.55062],[-8.09434,9.86936],[-8.15652,9.94288],[-8.11921,10.04577],[-8.01225,10.1021],[-7.97971,10.17117],[-7.9578,10.2703],[-8.10207,10.44649],[-8.22711,10.41722],[-8.32614,10.69273],[-8.2667,10.91762],[-8.35083,11.06234],[-8.66923,10.99397],[-8.40058,11.37466],[-8.80854,11.66715],[-8.94784,12.34842],[-9.13689,12.50875],[-9.38067,12.48446],[-9.32097,12.29009],[-9.63938,12.18312],[-9.714,12.0226],[-10.30604,12.24634],[-10.71897,11.91552],[-10.80355,12.1053],[-10.99758,12.24634],[-11.24136,12.01286],[-11.50006,12.17826],[-11.37536,12.40788],[-11.46267,12.44559],[-11.91331,12.42008],[-12.35415,12.32758],[-12.87336,12.51892],[-13.06603,12.49342],[-13.05296,12.64003],[-13.70523,12.68013],[-13.7039,12.60313],[-13.65089,12.49515],[-13.64168,12.42764],[-13.70851,12.24978],[-13.92745,12.24077],[-13.94589,12.16869],[-13.7039,12.00869],[-13.7039,11.70195],[-14.09799,11.63649],[-14.26623,11.67486],[-14.31513,11.60713],[-14.51173,11.49708],[-14.66677,11.51188],[-14.77786,11.36323],[-14.95993,10.99244],[-15.07174,10.89557],[-15.96748,10.162]]]}},{"type":"Feature","properties":{"id":"GM"},"geometry":{"type":"Polygon","coordinates":[[[-17.43966,13.04579],[-16.74676,13.06025],[-16.69343,13.16791],[-15.80355,13.16729],[-15.80478,13.34832],[-15.26908,13.37768],[-15.14917,13.57989],[-14.36795,13.23033],[-13.79409,13.34472],[-13.8955,13.59126],[-14.34721,13.46578],[-14.93719,13.80173],[-15.36504,13.79313],[-15.47902,13.58758],[-17.43598,13.59273],[-17.43966,13.04579]]]}},{"type":"Feature","properties":{"id":"GW"},"geometry":{"type":"Polygon","coordinates":[[[-17.4623,11.92379],[-15.96748,10.162],[-15.07174,10.89557],[-14.95993,10.99244],[-14.77786,11.36323],[-14.66677,11.51188],[-14.51173,11.49708],[-14.31513,11.60713],[-14.26623,11.67486],[-14.09799,11.63649],[-13.7039,11.70195],[-13.7039,12.00869],[-13.94589,12.16869],[-13.92745,12.24077],[-13.70851,12.24978],[-13.64168,12.42764],[-13.65089,12.49515],[-13.7039,12.60313],[-13.70523,12.68013],[-15.17582,12.6847],[-15.67302,12.42974],[-16.20591,12.46157],[-16.38191,12.36449],[-16.70562,12.34803],[-17.4623,11.92379]]]}},{"type":"Feature","properties":{"id":"IN-UP"},"geometry":{"type":"Polygon","coordinates":[[[77.10411,29.50415],[77.14187,29.09577],[77.22358,28.89939],[77.21157,28.8573],[77.2229,28.82091],[77.20985,28.81429],[77.20418,28.80527],[77.20659,28.78451],[77.23839,28.75908],[77.24886,28.75524],[77.25512,28.7558],[77.26006,28.75039],[77.25658,28.7444],[77.25547,28.73861],[77.26057,28.73564],[77.27667,28.73564],[77.28688,28.7248],[77.29083,28.72273],[77.2865,28.7143],[77.29036,28.70602],[77.29628,28.70526],[77.29971,28.71008],[77.3134,28.71366],[77.33207,28.71317],[77.32409,28.69864],[77.33345,28.68147],[77.32997,28.67861],[77.32551,28.67827],[77.32027,28.66234],[77.31988,28.65188],[77.31594,28.64152],[77.34087,28.62299],[77.3419,28.60524],[77.33645,28.60174],[77.33066,28.60098],[77.3246,28.59789],[77.31332,28.59661],[77.31049,28.59085],[77.30422,28.58587],[77.29916,28.58772],[77.29293,28.57634],[77.29886,28.55723],[77.34615,28.51651],[77.37447,28.47035],[77.38752,28.46506],[77.41447,28.47465],[77.42477,28.46122],[77.41584,28.44039],[77.43086,28.4259],[77.45704,28.43586],[77.47361,28.41918],[77.46811,28.3997],[77.4979,28.40891],[77.48828,28.35515],[77.46528,28.30831],[77.53532,28.23725],[77.48794,28.19792],[77.54699,28.17613],[77.47283,28.08409],[77.53635,27.99167],[77.52468,27.9204],[77.28126,27.80689],[77.44331,27.3931],[77.6493,27.24303],[77.67127,27.17341],[77.49412,27.08297],[77.75848,27.00469],[77.41722,26.85776],[77.45704,26.74315],[77.78045,26.92819],[78.04275,26.87001],[78.14575,26.94961],[78.26042,26.92941],[78.21029,26.82407],[78.74999,26.77994],[79.01779,26.63518],[79.12902,26.32665],[78.88458,25.90864],[78.74914,25.73589],[78.82003,25.6375],[78.52752,25.57031],[78.44066,25.56133],[78.30642,25.3707],[78.44306,25.12787],[78.34178,25.08155],[78.30642,24.97454],[78.16635,24.87647],[78.28033,24.5671],[78.2666,24.45277],[78.38882,24.26511],[78.51516,24.39025],[78.80767,24.15677],[78.9759,24.35397],[78.98483,24.44527],[78.87634,24.64483],[78.75686,24.60519],[78.77471,24.86027],[78.65558,24.91197],[78.63395,25.0887],[78.55464,25.26984],[78.42109,25.28164],[78.5464,25.3133],[78.52306,25.36419],[78.57868,25.34961],[78.56975,25.39676],[78.71051,25.45102],[78.66588,25.38807],[78.75789,25.33968],[78.81214,25.43056],[78.73146,25.47086],[78.92681,25.56753],[79.04182,25.1459],[79.31373,25.14404],[79.2794,25.35395],[79.47303,25.27512],[79.39269,25.11731],[79.86442,25.1086],[79.86854,25.23786],[80.29014,25.42281],[80.41648,25.1633],[80.25649,25.02401],[80.61561,25.10425],[80.77972,25.05823],[80.71723,25.14342],[80.88958,25.19375],[80.84426,24.99788],[80.80375,24.94154],[81.22741,24.95431],[81.27822,25.16703],[81.4904,25.07938],[81.56112,25.19748],[81.69605,25.03863],[81.73587,25.05574],[81.79389,25.00783],[81.90376,24.99943],[81.90101,24.88768],[81.95869,24.83301],[82.20691,24.78392],[82.30854,24.60831],[82.42561,24.59458],[82.41634,24.70504],[82.55195,24.65575],[82.69374,24.69755],[82.80361,24.55274],[82.71846,24.52713],[82.71606,24.37305],[82.76618,24.3754],[82.77854,24.29312],[82.71915,24.13876],[82.6752,24.16695],[82.66216,24.12513],[82.70267,24.09693],[82.79708,24.00319],[82.80876,23.96492],[82.95226,23.87642],[83.15963,23.90467],[83.32134,24.10131],[83.4027,24.26793],[83.37387,24.3155],[83.46347,24.36992],[83.39275,24.50027],[83.49883,24.52651],[83.5033,24.73747],[83.39824,24.78735],[83.34125,25.0125],[83.34897,25.17744],[83.41335,25.24966],[83.4621,25.25152],[83.49077,25.28614],[83.64852,25.34464],[83.65917,25.36745],[83.74242,25.40792],[83.76422,25.3859],[83.84593,25.43645],[83.81452,25.45459],[83.871,25.49333],[83.88061,25.51765],[84.05845,25.64833],[84.0921,25.72908],[84.32899,25.70588],[84.38804,25.76959],[84.4749,25.68485],[84.66613,25.74022],[84.54082,25.85737],[84.27955,25.94538],[84.15183,26.03334],[84.02309,26.13848],[84.01245,26.23676],[84.17106,26.26139],[84.17243,26.37495],[83.90258,26.44905],[83.87992,26.5225],[84.05296,26.54891],[84.1254,26.6318],[84.39594,26.61554],[84.30599,26.75082],[84.23973,26.86511],[84.02687,27.08847],[83.83048,27.29552],[83.85595,27.35797],[83.61288,27.47013],[83.39495,27.4798],[83.38872,27.39276],[83.35136,27.33885],[83.29999,27.32778],[83.2673,27.36235],[83.27197,27.38309],[83.19413,27.45632],[82.94938,27.46036],[82.93261,27.50328],[82.74119,27.49838],[82.70378,27.72122],[82.46405,27.6716],[82.06554,27.92222],[81.97214,27.93322],[81.91223,27.84995],[81.47867,28.08303],[81.48179,28.12148],[81.38683,28.17638],[81.32923,28.13521],[81.19847,28.36284],[81.08507,28.38346],[80.89648,28.47237],[80.55142,28.69182],[80.50575,28.6706],[80.52443,28.54897],[80.44504,28.63098],[80.37188,28.63371],[80.12125,28.82346],[80.06957,28.82763],[79.96673,28.70233],[79.79026,28.88736],[79.66529,28.85369],[79.41089,28.85339],[79.40711,28.92854],[79.29416,28.95858],[79.0686,29.15276],[79.00268,29.12127],[78.71429,29.32053],[78.91067,29.45335],[78.52683,29.6248],[78.49044,29.73874],[78.33732,29.79536],[77.98507,29.5418],[77.94181,29.71489],[77.80792,29.67075],[77.70629,29.8722],[77.92945,30.24661],[77.5542,30.40278],[77.58613,30.31006],[77.34443,30.06315],[77.12882,29.75305],[77.10411,29.50415]]]}},{"type":"Feature","properties":{"id":"GQ"},"geometry":{"type":"Polygon","coordinates":[[[5.37613,-1.68343],[5.85762,-1.69667],[7.24416,-0.64092],[9.00916,0.69445],[9.35563,0.84865],[9.51998,0.96418],[9.54793,1.0185],[9.62096,1.03039],[9.66092,1.05865],[9.68638,1.06836],[9.73014,1.06721],[9.76085,1.05949],[9.78058,1.03996],[9.79648,1.0019],[11.35307,1.00251],[11.3561,2.17217],[9.991,2.16561],[9.90749,2.20049],[9.89012,2.20457],[9.84716,2.24676],[9.83238,2.29079],[9.83754,2.32428],[9.82123,2.35097],[9.81162,2.33797],[9.6225,2.44901],[9.22018,3.72052],[8.6479,4.06346],[8.05799,3.48284],[8.0168,1.79377],[6.69416,-0.53945],[5.87114,-1.20569],[5.38965,-1.19244],[5.37613,-1.68343]]]}},{"type":"Feature","properties":{"id":"GT"},"geometry":{"type":"Polygon","coordinates":[[[-92.37213,14.39277],[-90.55276,12.8866],[-90.11344,13.73679],[-90.10505,13.85104],[-89.88937,14.0396],[-89.81807,14.07073],[-89.76103,14.02923],[-89.73251,14.04133],[-89.75569,14.07073],[-89.70756,14.1537],[-89.61844,14.21937],[-89.52397,14.22628],[-89.50614,14.26084],[-89.58814,14.33165],[-89.57441,14.41637],[-89.39028,14.44561],[-89.34776,14.43013],[-89.35189,14.47553],[-89.23719,14.58046],[-89.15653,14.57802],[-89.13132,14.71582],[-89.23467,14.85596],[-89.15149,14.97775],[-89.18048,14.99967],[-89.15149,15.07392],[-88.97343,15.14039],[-88.32467,15.63665],[-88.31459,15.66942],[-88.24022,15.69247],[-88.22552,15.72294],[-88.20359,16.03858],[-88.40779,16.09624],[-88.95358,15.88698],[-89.02415,15.9063],[-89.17418,15.90898],[-89.22683,15.88619],[-89.15025,17.04813],[-89.14985,17.81563],[-90.98678,17.81655],[-90.99199,17.25192],[-91.43809,17.25373],[-91.04436,16.92175],[-90.69064,16.70697],[-90.61212,16.49832],[-90.40499,16.40524],[-90.44567,16.07573],[-91.73182,16.07371],[-92.20983,15.26077],[-92.0621,15.07406],[-92.1454,14.98143],[-92.1423,14.88647],[-92.18161,14.84147],[-92.1454,14.6804],[-92.2261,14.53423],[-92.37213,14.39277]]]}},{"type":"Feature","properties":{"id":"GY"},"geometry":{"type":"Polygon","coordinates":[[[-61.4041,5.95304],[-60.73204,5.20931],[-60.32352,5.21299],[-60.20944,5.28754],[-59.98129,5.07097],[-60.04189,4.69801],[-60.15953,4.53456],[-59.78878,4.45637],[-59.69361,4.34069],[-59.73353,4.20399],[-59.51963,3.91951],[-59.86899,3.57089],[-59.79769,3.37162],[-59.99733,2.92312],[-59.91177,2.36759],[-59.7264,2.27497],[-59.74066,1.87596],[-59.25583,1.40559],[-58.92072,1.31293],[-58.84229,1.17749],[-58.53571,1.29154],[-58.4858,1.48399],[-58.33887,1.58014],[-58.01873,1.51966],[-57.97336,1.64566],[-57.77281,1.73344],[-57.55743,1.69605],[-57.35073,1.98327],[-57.23981,1.95808],[-57.09109,2.01854],[-57.07092,1.95304],[-56.7659,1.89509],[-56.47045,1.95135],[-56.55439,2.02003],[-56.70519,2.02964],[-57.35891,3.32121],[-58.0307,3.95513],[-57.8699,4.89394],[-57.37442,5.0208],[-57.22536,5.15605],[-57.31629,5.33714],[-56.84822,6.73257],[-59.54058,8.6862],[-59.98508,8.53046],[-59.85562,8.35213],[-59.80661,8.28906],[-59.83156,8.23261],[-59.97059,8.20791],[-60.02407,8.04557],[-60.38056,7.8302],[-60.51959,7.83373],[-60.64793,7.56877],[-60.71923,7.55817],[-60.59802,7.33194],[-60.63367,7.25061],[-60.54098,7.14804],[-60.44116,7.20817],[-60.28074,7.1162],[-60.39419,6.94847],[-60.54873,6.8631],[-61.13632,6.70922],[-61.20762,6.58174],[-61.15058,6.19558],[-61.4041,5.95304]]]}},{"type":"Feature","properties":{"id":"HN"},"geometry":{"type":"Polygon","coordinates":[[[-89.35189,14.47553],[-89.34776,14.43013],[-89.04187,14.33644],[-88.94608,14.20207],[-88.85785,14.17763],[-88.815,14.11652],[-88.73182,14.10919],[-88.70661,14.04317],[-88.49738,13.97224],[-88.48982,13.86458],[-88.25791,13.91108],[-88.23018,13.99915],[-88.07641,13.98447],[-88.00331,13.86948],[-87.7966,13.91353],[-87.68821,13.80829],[-87.73106,13.75443],[-87.78148,13.52906],[-87.71657,13.50577],[-87.72115,13.46083],[-87.73841,13.44169],[-87.77354,13.45767],[-87.83467,13.44655],[-87.84675,13.41078],[-87.80177,13.35689],[-87.73714,13.32715],[-87.69751,13.25228],[-87.55124,13.12523],[-87.37107,12.98646],[-87.06306,13.00892],[-87.03785,12.98682],[-86.93197,13.05313],[-86.93383,13.18677],[-86.87066,13.30641],[-86.71267,13.30348],[-86.76812,13.79605],[-86.35219,13.77157],[-86.14801,14.04317],[-86.00685,14.08474],[-86.03458,13.99181],[-85.75477,13.8499],[-85.73964,13.9698],[-85.45762,14.11304],[-85.32149,14.2562],[-85.18602,14.24929],[-85.1575,14.53934],[-84.90082,14.80489],[-84.82596,14.82212],[-84.70119,14.68078],[-84.48373,14.63249],[-84.10584,14.76353],[-83.89551,14.76697],[-83.62101,14.89448],[-83.49268,15.01158],[-83.13724,15.00002],[-83.04763,15.03256],[-82.06974,14.49418],[-81.80642,15.86201],[-83.86109,17.73736],[-88.20359,16.03858],[-88.22552,15.72294],[-88.24022,15.69247],[-88.31459,15.66942],[-88.32467,15.63665],[-88.97343,15.14039],[-89.15149,15.07392],[-89.18048,14.99967],[-89.15149,14.97775],[-89.23467,14.85596],[-89.13132,14.71582],[-89.15653,14.57802],[-89.23719,14.58046],[-89.35189,14.47553]]]}},{"type":"Feature","properties":{"id":"HR"},"geometry":{"type":"Polygon","coordinates":[[[13.05142,45.33128],[13.12821,44.48877],[16.15283,42.18525],[18.45131,42.21682],[18.54128,42.39171],[18.52152,42.42302],[18.43588,42.48556],[18.44307,42.51077],[18.43735,42.55921],[18.36197,42.61423],[18.24318,42.6112],[17.88201,42.83668],[17.80854,42.9182],[17.7948,42.89556],[17.68151,42.92725],[17.6444,42.88641],[17.5392,42.92787],[17.70879,42.97223],[17.64268,43.08595],[17.46986,43.16559],[17.286,43.33065],[17.25579,43.40353],[17.29699,43.44542],[17.24411,43.49376],[17.15828,43.49376],[17.00585,43.58037],[16.80736,43.76011],[16.75316,43.77157],[16.70922,43.84887],[16.55472,43.95326],[16.50528,44.0244],[16.43629,44.02826],[16.43662,44.07523],[16.36864,44.08263],[16.18688,44.27012],[16.21346,44.35231],[16.12969,44.38275],[16.16814,44.40679],[16.10566,44.52586],[16.03012,44.55572],[16.00884,44.58605],[16.05828,44.61538],[15.89348,44.74964],[15.8255,44.71501],[15.72584,44.82334],[15.79472,44.8455],[15.76096,44.87045],[15.74723,44.96818],[15.78568,44.97401],[15.74585,45.0638],[15.78842,45.11519],[15.76371,45.16508],[15.83512,45.22459],[15.98412,45.23088],[16.12153,45.09616],[16.29036,44.99732],[16.35404,45.00241],[16.35863,45.03529],[16.3749,45.05206],[16.38219,45.05139],[16.38309,45.05955],[16.40023,45.1147],[16.4634,45.14522],[16.49155,45.21153],[16.52982,45.22713],[16.5501,45.2212],[16.56559,45.22307],[16.60194,45.23042],[16.64962,45.20714],[16.74845,45.20393],[16.78219,45.19002],[16.81137,45.18434],[16.83804,45.18951],[16.92405,45.27607],[16.9385,45.22742],[17.0415,45.20759],[17.18438,45.14764],[17.24325,45.146],[17.25131,45.14957],[17.26815,45.18444],[17.32092,45.16246],[17.33573,45.14521],[17.41229,45.13335],[17.4498,45.16119],[17.45615,45.12523],[17.47589,45.12656],[17.51469,45.10791],[17.59104,45.10816],[17.66571,45.13408],[17.84826,45.04489],[17.87148,45.04645],[17.93706,45.08016],[17.97336,45.12245],[17.97834,45.13831],[17.99479,45.14958],[18.01594,45.15163],[18.03121,45.12632],[18.1624,45.07654],[18.24387,45.13699],[18.32077,45.1021],[18.41896,45.11083],[18.47939,45.05871],[18.65723,45.07544],[18.78357,44.97741],[18.80661,44.93561],[18.76369,44.93707],[18.76347,44.90669],[18.8704,44.85097],[19.01994,44.85493],[18.98957,44.90645],[19.02871,44.92541],[19.06853,44.89915],[19.15573,44.95409],[19.05205,44.97692],[19.1011,45.01191],[19.07952,45.14668],[19.14063,45.12972],[19.19144,45.17863],[19.43589,45.17137],[19.41941,45.23475],[19.28208,45.23813],[19.10774,45.29547],[18.97446,45.37528],[18.99918,45.49333],[19.08364,45.48804],[19.07471,45.53086],[18.94562,45.53712],[18.88776,45.57253],[18.96691,45.66731],[18.90305,45.71863],[18.85783,45.85493],[18.81394,45.91329],[18.80211,45.87995],[18.6792,45.92057],[18.57483,45.80772],[18.44368,45.73972],[18.12439,45.78905],[18.08869,45.76511],[17.99805,45.79671],[17.87377,45.78522],[17.66545,45.84207],[17.56821,45.93728],[17.35672,45.95209],[17.14592,46.16697],[16.8903,46.28122],[16.8541,46.36255],[16.7154,46.39523],[16.6639,46.45203],[16.59527,46.47524],[16.52604,46.47831],[16.5007,46.49644],[16.44036,46.5171],[16.38771,46.53608],[16.37193,46.55008],[16.29793,46.5121],[16.26733,46.51505],[16.26759,46.50566],[16.23961,46.49653],[16.25124,46.48067],[16.27398,46.42875],[16.27329,46.41467],[16.30162,46.40437],[16.30233,46.37837],[16.18824,46.38282],[16.14859,46.40547],[16.05281,46.39141],[16.05065,46.3833],[16.07314,46.36458],[16.07616,46.3463],[15.97965,46.30652],[15.79284,46.25811],[15.78817,46.21719],[15.75479,46.20336],[15.75436,46.21969],[15.67395,46.22478],[15.6434,46.21396],[15.64904,46.19229],[15.59909,46.14761],[15.6083,46.11992],[15.62317,46.09103],[15.72977,46.04682],[15.71246,46.01196],[15.70327,46.00015],[15.70636,45.92116],[15.67967,45.90455],[15.68383,45.88867],[15.68232,45.86819],[15.70411,45.8465],[15.66662,45.84085],[15.64185,45.82915],[15.57952,45.84953],[15.52234,45.82195],[15.47325,45.8253],[15.47531,45.79802],[15.40836,45.79491],[15.25423,45.72275],[15.30872,45.69014],[15.34919,45.71623],[15.4057,45.64727],[15.38952,45.63682],[15.34214,45.64702],[15.34695,45.63382],[15.31027,45.6303],[15.27747,45.60504],[15.29837,45.5841],[15.30249,45.53224],[15.38188,45.48752],[15.33051,45.45258],[15.27758,45.46678],[15.16862,45.42309],[15.05187,45.49079],[15.02385,45.48533],[14.92266,45.52788],[14.90554,45.47769],[14.81992,45.45913],[14.80124,45.49515],[14.71718,45.53442],[14.68605,45.53006],[14.69694,45.57366],[14.59576,45.62812],[14.60977,45.66403],[14.57397,45.67165],[14.53816,45.6205],[14.5008,45.60852],[14.49769,45.54424],[14.36693,45.48642],[14.32487,45.47142],[14.27681,45.4902],[14.26611,45.48239],[14.24239,45.50607],[14.22371,45.50388],[14.20348,45.46896],[14.07116,45.48752],[14.00578,45.52352],[13.96063,45.50825],[13.99488,45.47551],[13.97309,45.45258],[13.90771,45.45149],[13.88124,45.42637],[13.81742,45.43729],[13.7785,45.46787],[13.67398,45.4436],[13.62902,45.45898],[13.56979,45.4895],[13.45644,45.59464],[13.05142,45.33128]]]}},{"type":"Feature","properties":{"id":"HU"},"geometry":{"type":"Polygon","coordinates":[[[16.10983,46.867],[16.14365,46.8547],[16.15711,46.85434],[16.21892,46.86961],[16.2365,46.87775],[16.2941,46.87137],[16.34547,46.83836],[16.3408,46.80641],[16.31303,46.79838],[16.30966,46.7787],[16.37816,46.69975],[16.42641,46.69228],[16.41863,46.66238],[16.38594,46.6549],[16.39217,46.63673],[16.50139,46.56684],[16.52885,46.53303],[16.52604,46.5051],[16.59527,46.47524],[16.6639,46.45203],[16.7154,46.39523],[16.8541,46.36255],[16.8903,46.28122],[17.14592,46.16697],[17.35672,45.95209],[17.56821,45.93728],[17.66545,45.84207],[17.87377,45.78522],[17.99805,45.79671],[18.08869,45.76511],[18.12439,45.78905],[18.44368,45.73972],[18.57483,45.80772],[18.6792,45.92057],[18.80211,45.87995],[18.81394,45.91329],[18.99712,45.93537],[19.01284,45.96529],[19.0791,45.96458],[19.10388,46.04015],[19.14543,45.9998],[19.28826,45.99694],[19.52473,46.1171],[19.56113,46.16824],[19.66007,46.19005],[19.81491,46.1313],[19.93508,46.17553],[20.01816,46.17696],[20.03533,46.14509],[20.09713,46.17315],[20.26068,46.12332],[20.28324,46.1438],[20.35573,46.16629],[20.45377,46.14405],[20.49718,46.18721],[20.63863,46.12728],[20.76085,46.21002],[20.74574,46.25467],[20.86797,46.28884],[21.06572,46.24897],[21.16872,46.30118],[21.28061,46.44941],[21.26929,46.4993],[21.33214,46.63035],[21.43926,46.65109],[21.5151,46.72147],[21.48935,46.7577],[21.52028,46.84118],[21.59307,46.86935],[21.59581,46.91628],[21.68645,46.99595],[21.648,47.03902],[21.78395,47.11104],[21.94463,47.38046],[22.01055,47.37767],[22.03389,47.42508],[22.00917,47.50492],[22.31816,47.76126],[22.41979,47.7391],[22.46559,47.76583],[22.67247,47.7871],[22.76617,47.8417],[22.77991,47.87211],[22.89849,47.95851],[22.84276,47.98602],[22.87847,48.04665],[22.81804,48.11363],[22.73427,48.12005],[22.66835,48.09162],[22.58733,48.10813],[22.59007,48.15121],[22.49806,48.25189],[22.38133,48.23726],[22.2083,48.42534],[22.14689,48.4005],[21.83339,48.36242],[21.8279,48.33321],[21.72525,48.34628],[21.67134,48.3989],[21.6068,48.50365],[21.44063,48.58456],[21.11516,48.49546],[20.83248,48.5824],[20.5215,48.53336],[20.29943,48.26104],[20.24312,48.2784],[19.92452,48.1283],[19.63338,48.25006],[19.52489,48.19791],[19.47957,48.09437],[19.28182,48.08336],[19.23924,48.0595],[19.01952,48.07052],[18.82176,48.04206],[18.76134,47.97499],[18.76821,47.87469],[18.8506,47.82308],[18.74074,47.8157],[18.66521,47.76772],[18.56496,47.76588],[18.29305,47.73541],[18.02938,47.75665],[17.71215,47.7548],[17.23699,48.02094],[17.16001,48.00636],[17.09786,47.97336],[17.11022,47.92461],[17.08275,47.87719],[17.00997,47.86245],[17.07039,47.81129],[17.05048,47.79377],[17.08893,47.70928],[16.87538,47.68895],[16.86509,47.72268],[16.82938,47.68432],[16.7511,47.67878],[16.72089,47.73469],[16.65679,47.74197],[16.61183,47.76171],[16.54779,47.75074],[16.53514,47.73837],[16.55129,47.72268],[16.4222,47.66537],[16.58699,47.61772],[16.64193,47.63114],[16.71059,47.52692],[16.64821,47.50155],[16.6718,47.46139],[16.57152,47.40868],[16.52414,47.41007],[16.49908,47.39416],[16.45104,47.41181],[16.47782,47.25918],[16.44142,47.25079],[16.43663,47.21127],[16.41739,47.20649],[16.42801,47.18422],[16.4523,47.18812],[16.46442,47.16845],[16.44932,47.14418],[16.52863,47.13974],[16.46134,47.09395],[16.52176,47.05747],[16.43936,47.03548],[16.51369,47.00084],[16.28202,47.00159],[16.27594,46.9643],[16.22403,46.939],[16.19904,46.94134],[16.10983,46.867]]]}},{"type":"Feature","properties":{"id":"IN"},"geometry":{"type":"Polygon","coordinates":[[[68.11329,23.53945],[68.83233,21.42207],[71.96044,11.88348],[72.15131,7.6285],[74.03744,7.70688],[76.72283,7.82138],[77.66782,7.85769],[79.37385,8.98767],[79.45362,9.159],[79.42124,9.80115],[80.48418,10.20786],[93.82619,5.95573],[94.98735,6.60903],[94.6395,14.00732],[93.69443,13.6468],[92.61282,13.95915],[89.13606,21.42955],[89.13927,21.60785],[89.03553,21.77397],[89.07114,22.15335],[88.9367,22.58527],[88.94614,22.66941],[88.9151,22.75228],[88.96713,22.83346],[88.87063,22.95235],[88.88327,23.03885],[88.86377,23.08759],[88.99148,23.21134],[88.71133,23.2492],[88.79254,23.46028],[88.79351,23.50535],[88.74841,23.47361],[88.56507,23.64044],[88.58087,23.87105],[88.66189,23.87607],[88.73743,23.91751],[88.6976,24.14703],[88.74841,24.1959],[88.68801,24.31464],[88.50934,24.32474],[88.12296,24.51301],[88.08786,24.63232],[88.00683,24.66477],[88.15515,24.85806],[88.14004,24.93529],[88.21832,24.96642],[88.27325,24.88796],[88.33917,24.86803],[88.46277,25.07468],[88.44766,25.20149],[88.94067,25.18534],[89.00463,25.26583],[89.01105,25.30303],[88.85278,25.34679],[88.81296,25.51546],[88.677,25.46959],[88.4559,25.59227],[88.45103,25.66245],[88.242,25.80811],[88.13138,25.78773],[88.08804,25.91334],[88.16581,26.0238],[88.1844,26.14417],[88.34757,26.22216],[88.35153,26.29123],[88.51649,26.35923],[88.48749,26.45855],[88.36938,26.48683],[88.35153,26.45241],[88.33093,26.48929],[88.41196,26.63837],[88.4298,26.54489],[88.62144,26.46783],[88.69485,26.38353],[88.67837,26.26291],[88.78961,26.31093],[88.85004,26.23211],[89.05328,26.2469],[88.91321,26.37984],[88.92357,26.40711],[88.95612,26.4564],[89.08899,26.38845],[89.15869,26.13708],[89.35953,26.0077],[89.53515,26.00382],[89.57101,25.9682],[89.63968,26.22595],[89.70201,26.15138],[89.73581,26.15818],[89.77865,26.08387],[89.77728,26.04254],[89.86592,25.93115],[89.80585,25.82489],[89.84388,25.70042],[89.86129,25.61714],[89.81208,25.37244],[89.84086,25.31854],[89.83371,25.29548],[89.87629,25.28337],[89.90478,25.31038],[90.1155,25.22686],[90.40034,25.1534],[90.65042,25.17788],[90.87427,25.15799],[91.25517,25.20677],[91.63648,25.12846],[92.0316,25.1834],[92.33957,25.07593],[92.39147,25.01471],[92.49887,24.88796],[92.38626,24.86055],[92.25854,24.9191],[92.15796,24.54435],[92.11662,24.38997],[91.96603,24.3799],[91.89258,24.14674],[91.82596,24.22345],[91.76004,24.23848],[91.73257,24.14703],[91.65292,24.22095],[91.63782,24.1132],[91.55542,24.08687],[91.37414,24.10693],[91.35741,23.99072],[91.29587,24.0041],[91.22308,23.89616],[91.25192,23.83463],[91.15579,23.6599],[91.28293,23.37538],[91.36453,23.06612],[91.40848,23.07117],[91.4035,23.27522],[91.46615,23.2328],[91.54993,23.01051],[91.61571,22.93929],[91.7324,23.00043],[91.81634,23.08001],[91.76417,23.26619],[91.84789,23.42235],[91.95642,23.47361],[91.95093,23.73284],[92.04706,23.64229],[92.15417,23.73409],[92.26541,23.70392],[92.38214,23.28705],[92.37665,22.9435],[92.5181,22.71441],[92.60029,22.1522],[92.56616,22.13554],[92.60949,21.97638],[92.67532,22.03547],[92.70416,22.16017],[92.86208,22.05456],[92.89504,21.95143],[92.93899,22.02656],[92.99804,21.98964],[92.99255,22.05965],[93.04885,22.20595],[93.15734,22.18687],[93.14224,22.24535],[93.19991,22.25425],[93.18206,22.43716],[93.13537,22.45873],[93.11477,22.54374],[93.134,22.59573],[93.09417,22.69459],[93.134,22.92498],[93.12988,23.05772],[93.2878,23.00464],[93.38478,23.13698],[93.36862,23.35426],[93.38781,23.36139],[93.39981,23.38828],[93.38805,23.4728],[93.43475,23.68299],[93.3908,23.7622],[93.3908,23.92925],[93.36059,23.93176],[93.32351,24.04468],[93.34735,24.10151],[93.41415,24.07854],[93.46633,23.97067],[93.50616,23.94432],[93.62871,24.00922],[93.75952,24.0003],[93.80279,23.92549],[93.92089,23.95812],[94.14081,23.83333],[94.30215,24.23752],[94.32362,24.27692],[94.45279,24.56656],[94.50729,24.59281],[94.5526,24.70764],[94.60204,24.70889],[94.73937,25.00545],[94.74212,25.13606],[94.57458,25.20318],[94.68032,25.47003],[94.80117,25.49359],[95.18556,26.07338],[95.11428,26.1019],[95.12801,26.38397],[95.05798,26.45408],[95.23513,26.68499],[95.30339,26.65372],[95.437,26.7083],[95.81603,27.01335],[95.93002,27.04149],[96.04949,27.19428],[96.15591,27.24572],[96.40779,27.29818],[96.55761,27.29928],[96.73888,27.36638],[96.88445,27.25046],[96.85287,27.2065],[96.89132,27.17474],[97.14675,27.09041],[97.17422,27.14052],[96.91431,27.45752],[96.90112,27.62149],[97.29919,27.92233],[97.35824,27.87256],[97.38845,28.01329],[97.35412,28.06663],[97.31292,28.06784],[97.34547,28.21385],[97.1289,28.3619],[96.98882,28.32564],[96.88445,28.39452],[96.85561,28.4875],[96.6455,28.61657],[96.48895,28.42955],[96.40929,28.51526],[96.61391,28.72742],[96.3626,29.10607],[96.20467,29.02325],[96.18682,29.11087],[96.31316,29.18643],[96.05361,29.38167],[95.84899,29.31464],[95.75149,29.32063],[95.72086,29.20797],[95.50842,29.13487],[95.41091,29.13007],[95.3038,29.13847],[95.26122,29.07727],[95.2214,29.10727],[95.11291,29.09527],[95.0978,29.14446],[94.81353,29.17804],[94.69318,29.31739],[94.2752,29.11687],[94.35897,29.01965],[93.72797,28.68821],[93.44621,28.67189],[93.18069,28.50319],[93.14635,28.37035],[92.93075,28.25671],[92.67486,28.15018],[92.65472,28.07632],[92.73025,28.05814],[92.7275,27.98662],[92.42538,27.80092],[92.32101,27.79363],[92.27432,27.89077],[91.87057,27.7195],[91.84722,27.76325],[91.6469,27.76358],[91.55819,27.6144],[91.65007,27.48287],[92.01132,27.47352],[92.12019,27.27829],[92.04702,27.26861],[92.03457,27.07334],[92.11863,26.893],[92.05523,26.8692],[91.83181,26.87318],[91.50067,26.79223],[90.67715,26.77215],[90.48504,26.8594],[90.39271,26.90704],[90.30402,26.85098],[90.04535,26.72422],[89.86124,26.73307],[89.63369,26.74402],[89.42349,26.83727],[89.3901,26.84225],[89.38319,26.85963],[89.37913,26.86224],[89.1926,26.81329],[89.12825,26.81661],[89.09554,26.89089],[88.95807,26.92668],[88.92301,26.99286],[88.8714,26.97488],[88.86984,27.10937],[88.74219,27.144],[88.91901,27.32483],[88.82981,27.38814],[88.77517,27.45415],[88.88091,27.85192],[88.83559,28.01936],[88.63235,28.12356],[88.54858,28.06057],[88.25332,27.9478],[88.1278,27.95417],[88.13378,27.88015],[88.1973,27.85067],[88.19107,27.79285],[88.04008,27.49223],[88.07277,27.43007],[88.01646,27.21612],[88.01587,27.21388],[87.9887,27.11045],[88.11719,26.98758],[88.13422,26.98705],[88.12302,26.95324],[88.19107,26.75516],[88.1659,26.68177],[88.16452,26.64111],[88.09963,26.54195],[88.09414,26.43732],[88.00895,26.36029],[87.90115,26.44923],[87.89085,26.48565],[87.84193,26.43663],[87.7918,26.46737],[87.76004,26.40711],[87.67893,26.43501],[87.66803,26.40294],[87.59175,26.38342],[87.55274,26.40596],[87.51571,26.43106],[87.46566,26.44058],[87.37314,26.40815],[87.34568,26.34787],[87.26568,26.37294],[87.26587,26.40592],[87.24682,26.4143],[87.18863,26.40558],[87.14751,26.40542],[87.09147,26.45039],[87.0707,26.58571],[87.04691,26.58685],[87.01559,26.53228],[86.95912,26.52076],[86.94543,26.52076],[86.82898,26.43919],[86.76797,26.45892],[86.74025,26.42386],[86.69124,26.45169],[86.62686,26.46891],[86.61313,26.48658],[86.57073,26.49825],[86.54258,26.53819],[86.49726,26.54218],[86.31564,26.61925],[86.26235,26.61886],[86.22513,26.58863],[86.13596,26.60651],[86.02729,26.66756],[85.8492,26.56667],[85.85126,26.60866],[85.83126,26.61134],[85.76907,26.63076],[85.72315,26.67471],[85.73483,26.79613],[85.66239,26.84822],[85.61621,26.86721],[85.59461,26.85161],[85.5757,26.85955],[85.56471,26.84133],[85.47752,26.79292],[85.34302,26.74954],[85.21159,26.75933],[85.18046,26.80519],[85.19291,26.86909],[85.15883,26.86966],[85.02635,26.85381],[85.05592,26.88991],[85.00536,26.89523],[84.97186,26.9149],[84.96687,26.95599],[84.85754,26.98984],[84.82913,27.01989],[84.793,26.9968],[84.64496,27.04669],[84.69166,27.21294],[84.62161,27.33885],[84.29315,27.39],[84.25735,27.44941],[84.21376,27.45218],[84.10791,27.52399],[84.02229,27.43836],[83.93306,27.44939],[83.86182,27.4241],[83.85595,27.35797],[83.61288,27.47013],[83.39495,27.4798],[83.38872,27.39276],[83.35136,27.33885],[83.29999,27.32778],[83.2673,27.36235],[83.27197,27.38309],[83.19413,27.45632],[82.94938,27.46036],[82.93261,27.50328],[82.74119,27.49838],[82.70378,27.72122],[82.46405,27.6716],[82.06554,27.92222],[81.97214,27.93322],[81.91223,27.84995],[81.47867,28.08303],[81.48179,28.12148],[81.38683,28.17638],[81.32923,28.13521],[81.19847,28.36284],[81.08507,28.38346],[80.89648,28.47237],[80.55142,28.69182],[80.50575,28.6706],[80.52443,28.54897],[80.44504,28.63098],[80.37188,28.63371],[80.12125,28.82346],[80.06957,28.82763],[80.05743,28.91479],[80.18085,29.13649],[80.23178,29.11626],[80.26602,29.13938],[80.24112,29.21414],[80.28626,29.20327],[80.31428,29.30784],[80.24322,29.44299],[80.37939,29.57098],[80.41858,29.63581],[80.38428,29.68513],[80.36803,29.73865],[80.41554,29.79451],[80.43458,29.80466],[80.48997,29.79566],[80.56247,29.86661],[80.56957,29.88176],[80.60226,29.95732],[80.67076,29.95732],[80.8778,30.13384],[80.93695,30.18229],[81.03953,30.20059],[80.83343,30.32023],[80.54504,30.44936],[80.20721,30.58541],[79.93255,30.88288],[79.59884,30.93943],[79.22805,31.34963],[79.14016,31.43403],[79.01931,31.42817],[78.77898,31.31209],[78.71032,31.50197],[78.84516,31.60631],[78.69933,31.78723],[78.78036,31.99478],[78.74404,32.00384],[78.68754,32.10256],[78.49609,32.2762],[78.4645,32.45367],[78.38897,32.53938],[78.73916,32.69438],[78.7831,32.46873],[78.96713,32.33655],[78.99322,32.37948],[79.0979,32.38051],[79.13174,32.47766],[79.26768,32.53277],[79.46562,32.69668],[79.14016,33.02545],[79.15252,33.17156],[78.73636,33.56521],[78.67599,33.66445],[78.77349,33.73871],[78.73367,34.01121],[78.65657,34.03195],[78.66225,34.08858],[78.91769,34.15452],[78.99802,34.3027],[79.05364,34.32482],[78.74465,34.45174],[78.56475,34.50835],[78.54964,34.57283],[78.27781,34.61484],[78.18435,34.7998],[78.22692,34.88771],[78.00033,35.23954],[78.03466,35.3785],[78.11664,35.48022],[77.80532,35.52058],[77.70232,35.46244],[77.44277,35.46132],[76.96624,35.5932],[76.84539,35.67356],[76.77323,35.66062],[76.75475,35.52617],[76.85088,35.39754],[76.93465,35.39866],[77.11796,35.05419],[76.99251,34.93349],[76.87193,34.96906],[76.74514,34.92488],[76.74377,34.84039],[76.67648,34.76371],[76.47186,34.78965],[76.15463,34.6429],[76.04614,34.67566],[75.75438,34.51827],[75.38009,34.55021],[75.01479,34.64629],[74.6663,34.703],[74.58083,34.77386],[74.31239,34.79626],[74.12897,34.70073],[73.96423,34.68244],[73.93401,34.63386],[73.93951,34.57169],[73.89419,34.54568],[73.88732,34.48911],[73.74999,34.3781],[73.74862,34.34183],[73.8475,34.32935],[73.90517,34.35317],[73.98208,34.2522],[73.90677,34.10504],[73.88732,34.05105],[73.91341,34.01235],[74.21554,34.03853],[74.25262,34.01577],[74.26086,33.92237],[74.14001,33.83002],[74.05898,33.82089],[74.00891,33.75437],[73.96423,33.73071],[73.98968,33.66155],[73.97367,33.64061],[74.03576,33.56718],[74.10115,33.56392],[74.18121,33.4745],[74.17983,33.3679],[74.08782,33.26232],[74.01366,33.25199],[74.02144,33.18908],[74.15374,33.13477],[74.17571,33.07495],[74.31854,33.02891],[74.34875,32.97823],[74.31227,32.92795],[74.41467,32.90563],[74.45312,32.77755],[74.6289,32.75561],[74.64675,32.82604],[74.7113,32.84219],[74.65345,32.71225],[74.69542,32.66792],[74.64424,32.60985],[74.65251,32.56416],[74.67431,32.56676],[74.68362,32.49298],[74.84725,32.49075],[74.97634,32.45367],[75.03265,32.49538],[75.28259,32.36556],[75.38046,32.26836],[75.25649,32.10187],[75.00793,32.03786],[74.9269,32.0658],[74.86236,32.04485],[74.79919,31.95983],[74.58907,31.87824],[74.47771,31.72227],[74.57498,31.60382],[74.61517,31.55698],[74.59319,31.50197],[74.64713,31.45605],[74.59773,31.4136],[74.53223,31.30321],[74.51629,31.13829],[74.56023,31.08303],[74.60281,31.10419],[74.60006,31.13711],[74.6852,31.12771],[74.67971,31.05479],[74.5616,31.04153],[73.88993,30.36305],[73.95736,30.28466],[73.97225,30.19829],[73.80299,30.06969],[73.58665,30.01848],[73.3962,29.94707],[73.28094,29.56646],[73.05886,29.1878],[73.01337,29.16422],[72.94272,29.02487],[72.40402,28.78283],[72.29495,28.66367],[72.20329,28.3869],[71.9244,28.11555],[71.89921,27.96035],[70.79054,27.68423],[70.60927,28.02178],[70.37307,28.01208],[70.12502,27.8057],[70.03136,27.56627],[69.58519,27.18109],[69.50904,26.74892],[69.88555,26.56836],[70.05584,26.60398],[70.17532,26.55362],[70.17532,26.24118],[70.08193,26.08094],[70.0985,25.93238],[70.2687,25.71156],[70.37444,25.67443],[70.53649,25.68928],[70.60378,25.71898],[70.67382,25.68186],[70.66695,25.39314],[70.89148,25.15064],[70.94002,24.92843],[71.09405,24.69017],[70.97594,24.60904],[71.00341,24.46038],[71.12838,24.42662],[71.04461,24.34657],[70.94985,24.3791],[70.85784,24.30903],[70.88393,24.27398],[70.71502,24.23517],[70.57906,24.27774],[70.5667,24.43787],[70.11712,24.30915],[70.03428,24.172],[69.73335,24.17007],[69.59579,24.29777],[69.29778,24.28712],[69.19341,24.25646],[69.07806,24.29777],[68.97781,24.26021],[68.90914,24.33156],[68.7416,24.31904],[68.74643,23.97027],[68.39339,23.96838],[68.20763,23.85849],[68.11329,23.53945]]]}},{"type":"Feature","properties":{"id":"IR"},"geometry":{"type":"Polygon","coordinates":[[[44.03667,39.39223],[44.1043,39.19842],[44.20946,39.13975],[44.18863,38.93881],[44.30322,38.81581],[44.26155,38.71427],[44.28065,38.6465],[44.32058,38.62752],[44.3207,38.49799],[44.3119,38.37887],[44.38309,38.36117],[44.44386,38.38295],[44.50115,38.33939],[44.42476,38.25763],[44.22509,37.88859],[44.3883,37.85433],[44.45948,37.77065],[44.55498,37.783],[44.62096,37.71985],[44.56887,37.6429],[44.61401,37.60165],[44.58449,37.45018],[44.81021,37.2915],[44.75986,37.21549],[44.7868,37.16644],[44.78319,37.1431],[44.75229,37.11958],[44.81611,37.04383],[44.89862,37.01897],[44.91199,36.91468],[44.90173,36.86096],[44.83479,36.81362],[44.84725,36.77622],[45.01537,36.75128],[45.06985,36.6814],[45.06985,36.62645],[45.00759,36.5402],[45.11811,36.40751],[45.23953,36.43257],[45.27394,36.35846],[45.26261,36.3001],[45.30038,36.27769],[45.32235,36.17383],[45.37312,36.09917],[45.37652,36.06222],[45.33916,35.99424],[45.38275,35.97156],[45.46594,36.00042],[45.55245,35.99943],[45.60018,35.96069],[45.6645,35.92872],[45.76145,35.79898],[45.81442,35.82107],[45.89784,35.83708],[45.94711,35.82218],[46.08325,35.8581],[46.17198,35.8013],[46.32921,35.82655],[46.34166,35.78363],[46.23736,35.71414],[46.01631,35.69139],[46.0117,35.65059],[45.99452,35.63574],[46.0165,35.61501],[46.01307,35.59756],[46.03028,35.57416],[45.97584,35.58132],[46.01518,35.52012],[45.98453,35.49848],[46.05358,35.38568],[46.13152,35.32548],[46.15474,35.2883],[46.11367,35.23729],[46.18457,35.22561],[46.19738,35.18536],[46.16229,35.16984],[46.15642,35.1268],[46.19116,35.11097],[46.11763,35.07551],[46.07747,35.0838],[46.06508,35.03699],[45.94756,35.09188],[45.93108,35.08148],[45.92203,35.09538],[45.92173,35.0465],[45.87864,35.03441],[45.89477,34.95805],[45.86532,34.89858],[45.78904,34.91135],[45.79682,34.85133],[45.73641,34.83975],[45.70031,34.82322],[45.68284,34.76624],[45.65672,34.7222],[45.70031,34.69277],[45.73923,34.54416],[45.60224,34.55057],[45.59074,34.55558],[45.53219,34.60441],[45.51883,34.47692],[45.43879,34.45949],[45.46697,34.38221],[45.49171,34.3439],[45.53552,34.35148],[45.58667,34.30147],[45.56176,34.15088],[45.47264,34.03099],[45.41077,33.97421],[45.42789,33.9458],[45.50261,33.94968],[45.77814,33.60938],[45.89801,33.63661],[45.96183,33.55751],[45.86687,33.49263],[45.99919,33.5082],[46.20623,33.20395],[46.11905,33.11924],[46.05367,33.13097],[46.03966,33.09577],[46.15175,33.07229],[46.09103,32.98354],[46.17198,32.95612],[46.32298,32.9731],[46.46788,32.91992],[47.17218,32.45393],[47.37529,32.47808],[47.57144,32.20583],[47.52474,32.15972],[47.64771,32.07666],[47.86337,31.78422],[47.6804,31.39086],[47.68219,31.00004],[48.03221,30.9967],[48.02443,30.4789],[48.14585,30.44133],[48.18321,30.39703],[48.19425,30.32796],[48.21279,30.31644],[48.24385,30.33846],[48.26393,30.3408],[48.41117,30.19846],[48.41671,30.17254],[48.38714,30.13485],[48.38869,30.11062],[48.43384,30.08233],[48.4494,30.04456],[48.44785,30.00148],[48.51011,29.96238],[48.61441,29.93675],[48.83867,29.78572],[49.98877,27.87827],[50.37726,27.89227],[54.39838,25.68383],[55.14145,25.62624],[55.81777,26.18798],[56.2644,26.58649],[56.68954,26.76645],[56.79239,26.41236],[56.82555,25.7713],[56.86325,25.03856],[61.5251,24.57287],[61.57592,25.0492],[61.6433,25.27541],[61.683,25.66638],[61.83968,25.7538],[61.83831,26.07249],[61.89391,26.26251],[62.05117,26.31647],[62.21304,26.26601],[62.31484,26.528],[62.77352,26.64099],[63.1889,26.65072],[63.18688,26.83844],[63.25005,26.84212],[63.25005,27.08692],[63.32283,27.14437],[63.19649,27.25674],[62.80604,27.22412],[62.79684,27.34381],[62.84905,27.47627],[62.7638,28.02992],[62.79412,28.28108],[62.59499,28.24842],[62.40259,28.42703],[61.93581,28.55284],[61.65978,28.77937],[61.53765,29.00507],[61.31508,29.38903],[60.87231,29.86514],[61.80829,30.84224],[61.78268,30.92724],[61.8335,30.97669],[61.83257,31.0452],[61.80957,31.12576],[61.80569,31.16167],[61.70929,31.37391],[60.84541,31.49561],[60.86191,32.22565],[60.56485,33.12944],[60.88908,33.50219],[60.91133,33.55596],[60.69573,33.56054],[60.57762,33.59772],[60.5485,33.73422],[60.5838,33.80793],[60.50209,34.13992],[60.66502,34.31539],[60.91321,34.30411],[60.72316,34.52857],[60.99922,34.63064],[61.00197,34.70631],[61.06926,34.82139],[61.12831,35.09938],[61.0991,35.27845],[61.18187,35.30249],[61.27371,35.61482],[61.22719,35.67038],[61.26152,35.80749],[61.22444,35.92879],[61.12007,35.95992],[61.22719,36.12759],[61.1393,36.38782],[61.18187,36.55348],[61.14516,36.64644],[60.34767,36.63214],[60.00768,37.04102],[59.74678,37.12499],[59.55178,37.13594],[59.39385,37.34257],[59.39797,37.47892],[59.33507,37.53146],[59.22905,37.51161],[58.9338,37.67374],[58.6921,37.64548],[58.5479,37.70526],[58.47786,37.6433],[58.39959,37.63134],[58.22999,37.6856],[58.21399,37.77281],[57.79534,37.89299],[57.35042,37.98546],[57.37236,38.09321],[57.21169,38.28965],[57.03453,38.18717],[56.73928,38.27887],[56.62255,38.24005],[56.43303,38.26054],[56.32454,38.18502],[56.33278,38.08132],[55.97847,38.08024],[55.76561,38.12238],[55.44152,38.08564],[55.13412,37.94705],[54.851,37.75739],[54.77684,37.62264],[54.81804,37.61285],[54.77822,37.51597],[54.67247,37.43532],[54.58664,37.45809],[54.36211,37.34912],[54.24565,37.32047],[53.89734,37.3464],[49.20805,38.40869],[48.88288,38.43975],[48.84969,38.45015],[48.81072,38.44853],[48.78979,38.45026],[48.70001,38.40564],[48.62217,38.40198],[48.58793,38.45076],[48.45084,38.61013],[48.3146,38.59958],[48.24773,38.71883],[48.02581,38.82705],[48.01409,38.90333],[48.07734,38.91616],[48.08627,38.94434],[48.28437,38.97186],[48.33884,39.03022],[48.31239,39.09278],[48.15361,39.19419],[48.12404,39.25208],[48.15984,39.30028],[48.37385,39.37584],[48.34264,39.42935],[47.98977,39.70999],[47.84774,39.66285],[47.50099,39.49615],[47.38978,39.45999],[47.31301,39.37492],[47.05927,39.24846],[47.05771,39.20143],[46.95341,39.13505],[46.92539,39.16644],[46.83822,39.13143],[46.75752,39.03231],[46.53497,38.86548],[46.34059,38.92076],[46.20601,38.85262],[46.14785,38.84206],[46.06766,38.87861],[46.00228,38.87376],[45.94624,38.89072],[45.90266,38.87739],[45.83883,38.90768],[45.65172,38.95199],[45.6155,38.94304],[45.6131,38.964],[45.44966,38.99243],[45.44811,39.04927],[45.40452,39.07224],[45.40148,39.09007],[45.30489,39.18333],[45.16168,39.21952],[45.08751,39.35052],[45.05932,39.36435],[44.96746,39.42998],[44.88916,39.59653],[44.81043,39.62677],[44.71806,39.71124],[44.65422,39.72163],[44.6137,39.78393],[44.47298,39.68788],[44.48111,39.61579],[44.41849,39.56659],[44.42832,39.4131],[44.37921,39.4131],[44.29818,39.378],[44.22452,39.4169],[44.03667,39.39223]]]}},{"type":"Feature","properties":{"id":"IQ"},"geometry":{"type":"Polygon","coordinates":[[[38.79171,33.37328],[39.08202,32.50304],[38.98762,32.47694],[39.04251,32.30203],[39.26157,32.35555],[39.29903,32.23259],[40.01521,32.05667],[42.97601,30.72204],[42.97796,30.48295],[44.72255,29.19736],[46.42415,29.05947],[46.5527,29.10283],[46.89695,29.50584],[47.15166,30.01044],[47.37192,30.10421],[47.7095,30.10453],[48.01114,29.98906],[48.06782,30.02906],[48.17332,30.02448],[48.40479,29.85763],[48.59531,29.66815],[48.83867,29.78572],[48.61441,29.93675],[48.51011,29.96238],[48.44785,30.00148],[48.4494,30.04456],[48.43384,30.08233],[48.38869,30.11062],[48.38714,30.13485],[48.41671,30.17254],[48.41117,30.19846],[48.26393,30.3408],[48.24385,30.33846],[48.21279,30.31644],[48.19425,30.32796],[48.18321,30.39703],[48.14585,30.44133],[48.02443,30.4789],[48.03221,30.9967],[47.68219,31.00004],[47.6804,31.39086],[47.86337,31.78422],[47.64771,32.07666],[47.52474,32.15972],[47.57144,32.20583],[47.37529,32.47808],[47.17218,32.45393],[46.46788,32.91992],[46.32298,32.9731],[46.17198,32.95612],[46.09103,32.98354],[46.15175,33.07229],[46.03966,33.09577],[46.05367,33.13097],[46.11905,33.11924],[46.20623,33.20395],[45.99919,33.5082],[45.86687,33.49263],[45.96183,33.55751],[45.89801,33.63661],[45.77814,33.60938],[45.50261,33.94968],[45.42789,33.9458],[45.41077,33.97421],[45.47264,34.03099],[45.56176,34.15088],[45.58667,34.30147],[45.53552,34.35148],[45.49171,34.3439],[45.46697,34.38221],[45.43879,34.45949],[45.51883,34.47692],[45.53219,34.60441],[45.59074,34.55558],[45.60224,34.55057],[45.73923,34.54416],[45.70031,34.69277],[45.65672,34.7222],[45.68284,34.76624],[45.70031,34.82322],[45.73641,34.83975],[45.79682,34.85133],[45.78904,34.91135],[45.86532,34.89858],[45.89477,34.95805],[45.87864,35.03441],[45.92173,35.0465],[45.92203,35.09538],[45.93108,35.08148],[45.94756,35.09188],[46.06508,35.03699],[46.07747,35.0838],[46.11763,35.07551],[46.19116,35.11097],[46.15642,35.1268],[46.16229,35.16984],[46.19738,35.18536],[46.18457,35.22561],[46.11367,35.23729],[46.15474,35.2883],[46.13152,35.32548],[46.05358,35.38568],[45.98453,35.49848],[46.01518,35.52012],[45.97584,35.58132],[46.03028,35.57416],[46.01307,35.59756],[46.0165,35.61501],[45.99452,35.63574],[46.0117,35.65059],[46.01631,35.69139],[46.23736,35.71414],[46.34166,35.78363],[46.32921,35.82655],[46.17198,35.8013],[46.08325,35.8581],[45.94711,35.82218],[45.89784,35.83708],[45.81442,35.82107],[45.76145,35.79898],[45.6645,35.92872],[45.60018,35.96069],[45.55245,35.99943],[45.46594,36.00042],[45.38275,35.97156],[45.33916,35.99424],[45.37652,36.06222],[45.37312,36.09917],[45.32235,36.17383],[45.30038,36.27769],[45.26261,36.3001],[45.27394,36.35846],[45.23953,36.43257],[45.11811,36.40751],[45.00759,36.5402],[45.06985,36.62645],[45.06985,36.6814],[45.01537,36.75128],[44.84725,36.77622],[44.83479,36.81362],[44.90173,36.86096],[44.91199,36.91468],[44.89862,37.01897],[44.81611,37.04383],[44.75229,37.11958],[44.78319,37.1431],[44.76698,37.16162],[44.63179,37.19229],[44.42631,37.05825],[44.38117,37.05825],[44.35315,37.04955],[44.35937,37.02843],[44.30645,36.97373],[44.25975,36.98119],[44.18503,37.09551],[44.22239,37.15756],[44.27998,37.16501],[44.2613,37.25055],[44.13521,37.32486],[44.02002,37.33229],[43.90949,37.22453],[43.84878,37.22205],[43.82699,37.19477],[43.8052,37.22825],[43.7009,37.23692],[43.63085,37.21957],[43.56702,37.25675],[43.50787,37.24436],[43.33508,37.33105],[43.30083,37.30629],[43.11403,37.37436],[42.93705,37.32015],[42.78887,37.38615],[42.56725,37.14878],[42.35724,37.10998],[42.36697,37.0627],[41.81736,36.58782],[41.40058,36.52502],[41.28864,36.35368],[41.2564,36.06012],[41.37027,35.84095],[41.38184,35.62502],[41.26569,35.42708],[41.21654,35.1508],[41.2345,34.80049],[41.12388,34.65742],[40.97676,34.39788],[40.64314,34.31604],[38.79171,33.37328]]]}},{"type":"Feature","properties":{"id":"IL"},"geometry":{"type":"Polygon","coordinates":[[[33.62659,31.82938],[34.052,31.46619],[34.29262,31.70393],[34.48681,31.59711],[34.56797,31.54197],[34.48892,31.48365],[34.40077,31.40926],[34.36505,31.36404],[34.37381,31.30598],[34.36523,31.28963],[34.29417,31.24194],[34.26742,31.21998],[34.92298,29.45305],[34.97718,29.54294],[34.98207,29.58147],[35.02147,29.66343],[35.14108,30.07374],[35.19183,30.34636],[35.16218,30.43535],[35.19595,30.50297],[35.21379,30.60401],[35.29311,30.71365],[35.33456,30.81224],[35.33984,30.8802],[35.41371,30.95565],[35.43658,31.12444],[35.40316,31.25535],[35.47672,31.49578],[35.39675,31.49572],[35.22921,31.37445],[35.13033,31.3551],[35.02459,31.35979],[34.92571,31.34337],[34.88932,31.37093],[34.87833,31.39321],[34.89756,31.43891],[34.93258,31.47816],[34.94356,31.50743],[34.9415,31.55601],[34.95249,31.59813],[35.00879,31.65426],[35.08226,31.69107],[35.10782,31.71594],[35.11895,31.71454],[35.12933,31.7325],[35.13931,31.73012],[35.15119,31.73634],[35.15474,31.73352],[35.16478,31.73242],[35.18023,31.72067],[35.20538,31.72388],[35.21937,31.71578],[35.22392,31.71899],[35.23972,31.70896],[35.24315,31.71244],[35.2438,31.7201],[35.24981,31.72543],[35.25182,31.73945],[35.26319,31.74846],[35.25225,31.7678],[35.26058,31.79064],[35.25573,31.81362],[35.26404,31.82567],[35.251,31.83085],[35.25753,31.8387],[35.24816,31.8458],[35.2304,31.84222],[35.2249,31.85433],[35.22817,31.8638],[35.22567,31.86745],[35.22294,31.87889],[35.22014,31.88264],[35.2136,31.88241],[35.21276,31.88153],[35.21016,31.88237],[35.20945,31.8815],[35.20791,31.8821],[35.20673,31.88151],[35.20381,31.86716],[35.21128,31.863],[35.216,31.83894],[35.21469,31.81835],[35.19461,31.82687],[35.18169,31.82542],[35.18603,31.80901],[35.14174,31.81325],[35.07677,31.85627],[35.05617,31.85685],[35.01978,31.82944],[34.9724,31.83352],[34.99712,31.85569],[35.03489,31.85919],[35.03978,31.89276],[35.03489,31.92448],[35.00124,31.93264],[34.98682,31.96935],[35.00261,32.027],[34.9863,32.09551],[34.99437,32.10962],[34.98507,32.12606],[34.99039,32.14626],[34.96009,32.17503],[34.95703,32.19522],[34.98885,32.20758],[35.01841,32.23981],[35.02939,32.2671],[35.01119,32.28684],[35.01772,32.33863],[35.04243,32.35008],[35.05142,32.3667],[35.0421,32.38242],[35.05311,32.4024],[35.05423,32.41754],[35.07059,32.4585],[35.08564,32.46948],[35.09236,32.47614],[35.10024,32.47856],[35.10882,32.4757],[35.15937,32.50466],[35.2244,32.55289],[35.25049,32.52453],[35.29306,32.50947],[35.30685,32.51024],[35.35212,32.52047],[35.40224,32.50136],[35.42034,32.46009],[35.41598,32.45593],[35.41048,32.43706],[35.42078,32.41562],[35.55807,32.38674],[35.55494,32.42687],[35.57485,32.48669],[35.56614,32.64393],[35.59813,32.65159],[35.61669,32.67999],[35.66527,32.681],[35.68467,32.70715],[35.75983,32.74803],[35.78745,32.77938],[35.83758,32.82817],[35.84021,32.8725],[35.87012,32.91976],[35.89298,32.9456],[35.87188,32.98028],[35.84802,33.1031],[35.81911,33.11077],[35.81911,33.1336],[35.84285,33.16673],[35.83846,33.19397],[35.81647,33.2028],[35.81295,33.24841],[35.77513,33.27342],[35.813,33.3172],[35.77477,33.33609],[35.62019,33.27278],[35.62283,33.24226],[35.58502,33.26653],[35.58326,33.28381],[35.56523,33.28969],[35.55555,33.25844],[35.54544,33.25513],[35.54808,33.236],[35.5362,33.23196],[35.54228,33.19865],[35.52573,33.11921],[35.50335,33.114],[35.50272,33.09056],[35.448,33.09264],[35.43059,33.06659],[35.35223,33.05617],[35.31429,33.10515],[35.1924,33.08743],[35.10645,33.09318],[34.78515,33.20368],[33.62659,31.82938]]]}},{"type":"Feature","properties":{"id":"JM"},"geometry":{"type":"Polygon","coordinates":[[[-78.75694,18.78765],[-78.34606,16.57862],[-75.50728,17.08879],[-76.34192,18.86145],[-78.75694,18.78765]]]}},{"type":"Feature","properties":{"id":"CN-SN"},"geometry":{"type":"Polygon","coordinates":[[[105.47878,32.89406],[105.62461,32.70526],[106.04484,32.86805],[106.11007,32.72144],[106.43073,32.6307],[106.85851,32.71855],[107.1009,32.67174],[107.1215,32.48543],[107.25677,32.40779],[107.42362,32.55433],[107.97569,32.13782],[108.25309,32.28365],[108.50234,32.20641],[109.20066,31.85248],[109.27619,31.71823],[109.58381,31.72933],[109.62364,32.10177],[109.49901,32.3028],[109.55703,32.48543],[109.71393,32.61508],[110.04524,32.55144],[110.19218,32.62029],[110.13725,32.81238],[110.03391,32.86041],[110.02481,32.87482],[109.86259,32.911],[109.79049,32.87929],[109.75925,32.91273],[109.79324,33.0691],[109.42794,33.15479],[109.61299,33.2783],[110.03974,33.19158],[110.16197,33.20996],[110.22926,33.15882],[110.46958,33.17721],[110.55713,33.26653],[110.7003,33.09384],[110.9801,33.2605],[111.0189,33.56771],[110.84106,33.67978],[110.587,33.89093],[110.6385,34.18056],[110.35388,34.519],[110.41122,34.58432],[110.23921,34.62784],[110.2272,34.90733],[110.36041,35.139],[110.40572,35.30728],[110.61035,35.64948],[110.43594,36.1606],[110.4895,36.56039],[110.39474,36.99816],[110.74665,37.45169],[110.77377,37.62946],[110.58357,37.92578],[110.49636,38.02862],[110.5149,38.20905],[110.80192,38.44713],[110.87333,38.45842],[110.88775,38.65522],[110.95745,38.75836],[111.00963,38.90706],[110.97495,38.97836],[111.04637,39.02158],[111.09134,39.02985],[111.1576,39.10741],[111.23897,39.30003],[111.19228,39.30508],[111.11881,39.36403],[111.09289,39.35859],[111.04808,39.43022],[111.15074,39.58478],[110.88363,39.50854],[110.68759,39.26522],[110.59661,39.27744],[110.52005,39.3834],[110.43662,39.38261],[110.38135,39.30826],[110.23063,39.4566],[110.10944,39.42876],[110.20866,39.28488],[109.89795,39.14683],[109.70809,39.05011],[109.55428,38.80119],[108.9624,38.35673],[108.93356,38.17883],[109.18521,38.03592],[109.02831,38.01618],[108.93836,37.9182],[108.83193,38.0662],[108.78936,37.68436],[108.01174,37.66561],[107.96607,37.79269],[107.66139,37.87999],[107.2705,37.47921],[107.30346,37.0979],[107.34878,36.90378],[108.6589,36.41023],[108.64654,35.95021],[108.58886,35.31176],[107.84248,35.26524],[107.7079,35.30896],[107.8466,34.97487],[107.18604,34.9062],[107.05833,35.02887],[106.76788,35.09238],[106.48635,35.05754],[106.5612,34.74443],[106.33186,34.55972],[106.3322,34.51532],[106.47399,34.52183],[106.6175,34.44797],[106.64188,34.38651],[106.71295,34.37092],[106.41357,33.89777],[106.58523,33.57],[106.51519,33.50246],[106.36688,33.61347],[105.95764,33.61061],[105.72624,33.36322],[105.96244,33.15652],[105.85945,32.93896],[105.47878,32.89406]]]}},{"type":"Feature","properties":{"id":"JO"},"geometry":{"type":"Polygon","coordinates":[[[34.88293,29.37455],[34.95987,29.35727],[36.07081,29.18469],[36.50005,29.49696],[36.75083,29.86903],[37.4971,29.99949],[37.66395,30.33245],[37.99354,30.49998],[36.99791,31.50081],[38.99233,31.99721],[39.29903,32.23259],[39.26157,32.35555],[39.04251,32.30203],[38.98762,32.47694],[39.08202,32.50304],[38.79171,33.37328],[36.83946,32.31293],[36.40959,32.37908],[36.23948,32.50108],[36.20875,32.49529],[36.20379,32.52751],[36.08074,32.51463],[36.02239,32.65911],[35.96633,32.66237],[35.93307,32.71966],[35.88405,32.71321],[35.75983,32.74803],[35.68467,32.70715],[35.66527,32.681],[35.61669,32.67999],[35.59813,32.65159],[35.56614,32.64393],[35.57485,32.48669],[35.55494,32.42687],[35.55807,32.38674],[35.57111,32.21877],[35.52012,32.04076],[35.54375,31.96587],[35.52758,31.9131],[35.55941,31.76535],[35.47672,31.49578],[35.40316,31.25535],[35.43658,31.12444],[35.41371,30.95565],[35.33984,30.8802],[35.33456,30.81224],[35.29311,30.71365],[35.21379,30.60401],[35.19595,30.50297],[35.16218,30.43535],[35.19183,30.34636],[35.14108,30.07374],[35.02147,29.66343],[34.98207,29.58147],[34.97718,29.54294],[34.92298,29.45305],[34.88293,29.37455]]]}},{"type":"Feature","properties":{"id":"CN-BJ"},"geometry":{"type":"Polygon","coordinates":[[[115.41755,39.77925],[115.47042,39.74177],[115.51574,39.6041],[115.66268,39.60833],[115.74851,39.51251],[115.81031,39.50933],[115.91365,39.57552],[116.22058,39.5787],[116.23809,39.51649],[116.32942,39.4566],[116.43722,39.44494],[116.46366,39.52946],[116.52065,39.59193],[116.80732,39.61415],[116.89075,39.69714],[116.95083,39.65434],[117.14137,39.61203],[117.19493,39.75999],[117.14755,39.81723],[117.26154,39.83385],[117.13897,39.8797],[117.19425,40.07281],[117.40608,40.17362],[117.34291,40.23131],[117.32814,40.2879],[117.29587,40.27612],[117.21811,40.36616],[117.22824,40.41245],[117.25913,40.43701],[117.20626,40.50126],[117.25227,40.50857],[117.24781,40.54902],[117.3072,40.57719],[117.40882,40.56206],[117.41432,40.63701],[117.42977,40.61851],[117.44419,40.65042],[117.47749,40.63167],[117.51663,40.65069],[117.43972,40.68532],[117.30789,40.65199],[117.21794,40.69938],[116.98379,40.68896],[116.62605,41.06485],[116.6209,40.97989],[116.44306,40.98145],[116.47602,40.89586],[116.31465,40.93037],[116.46537,40.7652],[116.24565,40.79067],[116.11278,40.62646],[115.82267,40.5871],[115.81615,40.5575],[115.77907,40.56128],[115.73272,40.51145],[115.77632,40.46196],[115.76568,40.44668],[115.857,40.36145],[115.90541,40.35544],[115.95382,40.27481],[115.8467,40.14633],[115.76808,40.15736],[115.50338,40.07649],[115.42098,39.96449],[115.56518,39.81011],[115.41755,39.77925]]]}},{"type":"Feature","properties":{"id":"KZ"},"geometry":{"type":"Polygon","coordinates":[[[46.49011,48.43019],[47.11516,48.27188],[47.12107,47.83687],[47.38731,47.68176],[47.41689,47.83687],[47.64973,47.76559],[48.15348,47.74545],[48.45173,47.40818],[48.52326,47.4102],[49.01136,46.72716],[48.51142,46.69268],[48.54988,46.56267],[49.16518,46.38542],[49.32259,46.26944],[49.88945,46.04554],[49.2134,44.84989],[52.26048,41.69249],[52.47884,41.78034],[52.97575,42.1308],[54.20635,42.38477],[54.95182,41.92424],[55.45471,41.25609],[56.00314,41.32584],[55.97584,44.99322],[55.97584,44.99328],[55.97584,44.99338],[55.97584,44.99343],[55.97584,44.99348],[55.97584,44.99353],[55.97584,44.99359],[55.97584,44.99369],[55.97584,44.99374],[55.97584,44.99384],[55.97584,44.9939],[55.97584,44.994],[55.97584,44.99405],[55.97584,44.99415],[55.97584,44.99421],[55.97584,44.99426],[55.97584,44.99431],[55.97584,44.99436],[55.97584,44.99441],[55.97594,44.99446],[55.97605,44.99452],[55.97605,44.99457],[55.97605,44.99462],[55.97605,44.99467],[55.97605,44.99477],[55.97615,44.99477],[55.97615,44.99483],[55.97615,44.99493],[55.97615,44.99498],[55.97615,44.99503],[55.97615,44.99508],[55.97625,44.99514],[55.97636,44.99519],[55.97636,44.99524],[55.97646,44.99529],[55.97646,44.99534],[55.97656,44.99539],[55.97667,44.99545],[55.97677,44.9955],[55.97677,44.99555],[55.97677,44.9956],[55.97687,44.9956],[55.97698,44.99565],[55.97698,44.9957],[55.97708,44.99576],[55.97718,44.99581],[55.97729,44.99586],[55.97739,44.99586],[55.97739,44.99591],[55.97749,44.99591],[55.9776,44.99591],[55.9777,44.99596],[55.9777,44.99601],[55.9778,44.99607],[55.97791,44.99607],[55.97801,44.99607],[55.97801,44.99612],[55.97811,44.99617],[55.97822,44.99617],[55.97832,44.99622],[55.97842,44.99622],[58.59711,45.58671],[61.01475,44.41383],[62.01711,43.51008],[63.34656,43.64003],[64.53885,43.56941],[64.96464,43.74748],[65.18666,43.48835],[65.53277,43.31856],[65.85194,42.85481],[66.09482,42.93426],[66.00546,41.94455],[66.53302,41.87388],[66.69129,41.1311],[67.9644,41.14611],[67.98511,41.02794],[68.08273,41.08148],[68.1271,41.0324],[67.96736,40.83798],[68.49983,40.56437],[68.63,40.59358],[68.58444,40.91447],[68.49983,40.99669],[68.62221,41.03019],[68.65662,40.93861],[68.73945,40.96989],[68.7217,41.05025],[69.01308,41.22804],[69.05006,41.36183],[69.15137,41.43078],[69.17701,41.43769],[69.18528,41.45175],[69.20439,41.45391],[69.22671,41.46298],[69.23332,41.45847],[69.25059,41.46693],[69.29778,41.43673],[69.35554,41.47211],[69.37468,41.46555],[69.45081,41.46246],[69.39485,41.51518],[69.45751,41.56863],[69.49545,41.545],[70.94483,42.26238],[70.85973,42.30188],[70.97717,42.50147],[71.15232,42.60486],[71.17807,42.67381],[71.22785,42.69248],[71.2724,42.77853],[71.53272,42.8014],[71.62405,42.76613],[71.88792,42.83578],[73.44393,42.43098],[73.50992,42.82356],[73.55634,43.03071],[74.22489,43.24657],[74.57491,43.13702],[74.64615,43.05881],[74.70331,43.02519],[74.75,42.99029],[74.88756,42.98612],[75.22619,42.85528],[75.29966,42.86183],[75.72174,42.79672],[75.82823,42.94848],[78.48469,42.89649],[78.91502,42.76839],[79.19763,42.804],[79.52921,42.44778],[79.97364,42.42816],[80.17807,42.21166],[80.26841,42.23797],[80.16892,42.61137],[80.26886,42.8366],[80.38169,42.83142],[80.58999,42.9011],[80.3735,43.01557],[80.62913,43.141],[80.78817,43.14235],[80.77771,43.30065],[80.69718,43.32589],[80.75156,43.44948],[80.40031,44.10986],[80.40229,44.23319],[80.38384,44.63073],[79.8987,44.89957],[80.11169,45.03352],[81.73278,45.3504],[82.51374,45.1755],[82.58474,45.40027],[82.21792,45.56619],[83.04622,47.19053],[83.92184,46.98912],[84.73077,47.01394],[84.93995,46.87399],[85.22443,47.04816],[85.54294,47.06171],[85.69696,47.2898],[85.61067,47.49753],[85.5169,48.05493],[85.73581,48.3939],[86.38069,48.46064],[86.75343,48.70331],[86.73568,48.99918],[86.87238,49.12432],[87.28386,49.11626],[87.31465,49.23603],[87.03071,49.25142],[86.82606,49.51796],[86.61307,49.60239],[86.79056,49.74787],[86.63674,49.80136],[86.18709,49.50259],[85.24047,49.60239],[84.99198,50.06793],[84.29385,50.27257],[83.8442,50.87375],[83.14607,51.00796],[82.55443,50.75412],[81.94999,50.79307],[81.46581,50.77658],[81.41248,50.97524],[81.06091,50.94833],[81.16999,51.15662],[80.80318,51.28262],[80.44819,51.20855],[80.4127,50.95581],[80.08138,50.77658],[79.11255,52.01171],[77.90383,53.29807],[76.54243,53.99329],[76.44076,54.16017],[76.82266,54.1798],[76.91052,54.4677],[75.3668,54.07439],[75.43398,53.98652],[75.07405,53.80831],[73.39218,53.44623],[73.25412,53.61532],[73.68921,53.86522],[73.74778,54.07194],[73.37963,53.96132],[72.71026,54.1161],[72.43415,53.92685],[72.17477,54.36303],[71.96141,54.17736],[71.10379,54.13326],[71.08706,54.33376],[71.24185,54.64965],[71.08288,54.71253],[70.96009,55.10558],[70.76493,55.3027],[70.19179,55.1476],[69.74917,55.35545],[69.34224,55.36344],[68.90865,55.38148],[68.19206,55.18823],[68.26661,55.09226],[68.21308,54.98645],[65.20174,54.55216],[65.24663,54.35721],[65.11033,54.33028],[64.97216,54.4212],[63.97686,54.29763],[64.02715,54.22679],[63.91224,54.20013],[63.80604,54.27079],[62.58651,54.05871],[62.56876,53.94047],[62.45931,53.90737],[62.38535,54.03961],[62.00966,54.04134],[62.03913,53.94768],[61.65318,54.02445],[61.56941,53.95703],[61.47603,54.08048],[61.3706,54.08464],[61.26863,53.92797],[60.99796,53.93699],[61.14283,53.90063],[61.22574,53.80268],[60.90626,53.62937],[61.55706,53.57144],[61.57185,53.50112],[61.37957,53.45887],[61.29082,53.50992],[61.14291,53.41481],[61.19024,53.30536],[62.14574,53.09626],[62.12799,52.99133],[62.0422,52.96105],[61.23462,53.03227],[61.05842,52.92217],[60.71989,52.75923],[60.71693,52.66245],[60.84118,52.63912],[60.84709,52.52228],[60.98021,52.50068],[61.05417,52.35096],[60.78201,52.22067],[60.72581,52.15538],[60.48915,52.15175],[60.19925,51.99173],[59.99809,51.98263],[60.09867,51.87135],[60.50986,51.7964],[60.36787,51.66815],[60.5424,51.61675],[60.92401,51.61124],[60.95655,51.48615],[61.50677,51.40687],[61.55114,51.32746],[61.6813,51.25716],[61.56889,51.23679],[61.4431,50.80679],[60.81833,50.6629],[60.31914,50.67705],[60.17262,50.83312],[60.01288,50.8163],[59.81172,50.54451],[59.51886,50.49937],[59.48928,50.64216],[58.87974,50.70852],[58.3208,51.15151],[57.75578,51.13852],[57.74986,50.93017],[57.44221,50.88354],[57.17302,51.11253],[56.17906,50.93204],[56.11398,50.7471],[55.67774,50.54508],[54.72067,51.03261],[54.56685,51.01958],[54.71476,50.61214],[54.55797,50.52006],[54.41894,50.61214],[54.46331,50.85554],[54.12248,51.11542],[53.69299,51.23466],[53.46165,51.49445],[52.54329,51.48444],[52.36119,51.74161],[51.8246,51.67916],[51.77431,51.49536],[51.301,51.48799],[51.26254,51.68466],[50.59695,51.61859],[50.26859,51.28677],[49.97277,51.2405],[49.76866,51.11067],[49.39001,51.09396],[49.41959,50.85927],[49.12673,50.78639],[48.86936,50.61589],[48.57946,50.63278],[48.90782,50.02281],[48.68352,49.89546],[48.42564,49.82283],[48.24519,49.86099],[48.10044,50.09242],[47.58551,50.47867],[47.30448,50.30894],[47.34589,50.09308],[47.18319,49.93721],[46.9078,49.86707],[46.78398,49.34026],[46.98795,49.23531],[47.04416,49.17152],[47.01458,49.07085],[46.91104,48.99715],[46.78392,48.95352],[46.49011,48.43019]]]}},{"type":"Feature","properties":{"id":"KE"},"geometry":{"type":"Polygon","coordinates":[[[33.90936,0.10581],[33.98449,-0.13079],[33.9264,-0.54188],[33.93107,-0.99298],[34.02286,-1.00779],[34.03084,-1.05101],[34.0824,-1.02264],[37.67199,-3.06222],[37.71745,-3.304],[37.5903,-3.42735],[37.63099,-3.50723],[37.75036,-3.54243],[37.81321,-3.69179],[39.21631,-4.67835],[39.44306,-4.93877],[39.62121,-4.68136],[41.75542,-1.85308],[41.56362,-1.66375],[41.56,-1.59812],[41.00099,-0.83068],[40.98767,2.82959],[41.31368,3.14314],[41.89488,3.97375],[41.1754,3.94079],[40.77498,4.27683],[39.86043,3.86974],[39.76808,3.67058],[39.58339,3.47434],[39.55132,3.39634],[39.51551,3.40895],[39.49444,3.45521],[39.19954,3.47834],[39.07736,3.5267],[38.91938,3.51198],[38.52336,3.62551],[38.45812,3.60445],[38.14168,3.62487],[37.07724,4.33503],[36.84474,4.44518],[36.03924,4.44406],[35.95449,4.53244],[35.9419,4.61933],[35.51424,4.61643],[35.42366,4.76969],[35.47843,4.91872],[35.30992,4.90402],[35.34151,5.02364],[34.47601,4.72162],[33.9873,4.23316],[34.06046,4.15235],[34.15429,3.80464],[34.45815,3.67385],[34.44922,3.51627],[34.39112,3.48802],[34.41794,3.44342],[34.40006,3.37949],[34.45815,3.18319],[34.56242,3.11478],[34.60114,2.93034],[34.65774,2.8753],[34.73967,2.85447],[34.78137,2.76223],[34.77244,2.70272],[34.95267,2.47209],[34.90947,2.42447],[34.98692,1.97348],[34.9899,1.6668],[34.92734,1.56109],[34.87819,1.5596],[34.7918,1.36752],[34.82606,1.30944],[34.82606,1.26626],[34.80223,1.22754],[34.67562,1.21265],[34.58029,1.14712],[34.57427,1.09868],[34.52369,1.10692],[34.43349,0.85254],[34.40041,0.80266],[34.31516,0.75693],[34.27345,0.63182],[34.20196,0.62289],[34.13493,0.58118],[34.11408,0.48884],[34.08727,0.44713],[34.10067,0.36372],[33.90936,0.10581]]]}},{"type":"Feature","properties":{"id":"KH"},"geometry":{"type":"Polygon","coordinates":[[[102.33828,13.55613],[102.361,13.50551],[102.35563,13.47307],[102.35692,13.38274],[102.34611,13.35618],[102.36001,13.31142],[102.36146,13.26006],[102.43422,13.09061],[102.46011,13.08057],[102.52275,12.99813],[102.48694,12.97537],[102.49335,12.92711],[102.53053,12.77506],[102.4994,12.71736],[102.51963,12.66117],[102.57567,12.65358],[102.7796,12.43781],[102.78116,12.40284],[102.73134,12.37091],[102.70176,12.1686],[102.77026,12.06815],[102.78427,11.98746],[102.83957,11.8519],[102.90973,11.75613],[102.91449,11.65512],[102.52395,11.25257],[102.47649,9.66162],[103.99198,10.48391],[104.43778,10.42386],[104.47963,10.43046],[104.49869,10.4057],[104.59018,10.53073],[104.87933,10.52833],[104.95094,10.64003],[105.09571,10.72722],[105.02722,10.89236],[105.08326,10.95656],[105.11449,10.96332],[105.34011,10.86179],[105.42884,10.96878],[105.50045,10.94586],[105.77751,11.03671],[105.86376,10.89839],[105.84603,10.85873],[105.93403,10.83853],[105.94535,10.9168],[106.06708,10.8098],[106.18539,10.79451],[106.14301,10.98176],[106.20095,10.97795],[106.1757,11.07301],[106.1527,11.10476],[106.10444,11.07879],[105.86782,11.28343],[105.88962,11.43605],[105.87328,11.55953],[105.81645,11.56876],[105.80867,11.60536],[105.8507,11.66635],[105.88962,11.67854],[105.95188,11.63738],[106.00792,11.7197],[106.02038,11.77457],[106.06708,11.77761],[106.13158,11.73283],[106.18539,11.75171],[106.26478,11.72122],[106.30525,11.67549],[106.37219,11.69836],[106.44691,11.66787],[106.45158,11.68616],[106.41577,11.76999],[106.44535,11.8279],[106.44068,11.86294],[106.4687,11.86751],[106.4111,11.97413],[106.70687,11.96956],[106.79405,12.0807],[106.92325,12.06548],[106.99953,12.08983],[107.15831,12.27547],[107.34511,12.33327],[107.42917,12.24657],[107.4463,12.29373],[107.55059,12.36824],[107.5755,12.52177],[107.55993,12.7982],[107.49611,12.88926],[107.49144,13.01215],[107.62843,13.3668],[107.61909,13.52577],[107.53503,13.73908],[107.45252,13.78897],[107.46498,13.91593],[107.44318,13.99751],[107.38247,13.99147],[107.35757,14.02319],[107.37158,14.07906],[107.33577,14.11832],[107.40427,14.24509],[107.39493,14.32655],[107.44941,14.41552],[107.48521,14.40346],[107.52569,14.54665],[107.52102,14.59034],[107.55371,14.628],[107.54361,14.69092],[107.47238,14.61523],[107.44435,14.52785],[107.37897,14.54443],[107.3276,14.58812],[107.29803,14.58963],[107.26534,14.54292],[107.256,14.48716],[107.21241,14.48716],[107.17038,14.41782],[107.09722,14.3937],[107.03962,14.45099],[107.04585,14.41782],[106.98825,14.36806],[106.9649,14.3198],[106.90574,14.33639],[106.8497,14.29416],[106.80767,14.31226],[106.73762,14.42687],[106.63333,14.44194],[106.59908,14.50977],[106.57106,14.50525],[106.54148,14.59565],[106.50723,14.58963],[106.45898,14.55045],[106.47766,14.50977],[106.43874,14.52032],[106.40916,14.45249],[106.32355,14.44043],[106.25194,14.48415],[106.21302,14.36203],[106.00131,14.36957],[105.99509,14.32734],[106.02311,14.30623],[106.04801,14.20363],[106.10872,14.18401],[106.11962,14.11307],[106.18656,14.06324],[106.16632,14.01794],[106.10094,13.98471],[106.10405,13.9137],[105.90791,13.92881],[105.78182,14.02247],[105.78338,14.08438],[105.5561,14.15684],[105.44869,14.10703],[105.36775,14.09948],[105.2759,14.17496],[105.20894,14.34967],[105.17748,14.34432],[105.14012,14.23873],[105.08408,14.20402],[105.02804,14.23722],[104.97667,14.38806],[104.69335,14.42726],[104.55014,14.36091],[104.27616,14.39861],[103.93836,14.3398],[103.70175,14.38052],[103.71109,14.4348],[103.53518,14.42575],[103.39353,14.35639],[103.16469,14.33075],[102.93275,14.19044],[102.91251,14.01531],[102.77864,13.93374],[102.72727,13.77806],[102.56848,13.69366],[102.5481,13.6589],[102.58635,13.6286],[102.62483,13.60883],[102.57573,13.60461],[102.5358,13.56933],[102.44601,13.5637],[102.36859,13.57488],[102.33828,13.55613]]]}},{"type":"Feature","properties":{"id":"KN"},"geometry":{"type":"Polygon","coordinates":[[[-63.11114,17.23125],[-62.62949,16.82364],[-62.27053,17.22145],[-62.76692,17.64353],[-63.11114,17.23125]]]}},{"type":"Feature","properties":{"id":"KR"},"geometry":{"type":"Polygon","coordinates":[[[122.80525,33.30571],[127.42045,32.33183],[129.2669,34.87122],[133.61399,37.41],[128.65655,38.61914],[128.37487,38.62345],[128.31105,38.58462],[128.27652,38.41657],[128.02917,38.31861],[127.55013,38.32257],[127.49672,38.30647],[127.38727,38.33227],[127.15749,38.30722],[127.04479,38.25518],[126.95338,38.17735],[126.95887,38.1347],[126.88106,38.10246],[126.84961,38.0344],[126.67023,37.95852],[126.68793,37.9175],[126.68793,37.83728],[126.66067,37.7897],[126.59918,37.76364],[126.56709,37.76857],[126.46818,37.80873],[126.43239,37.84095],[126.24402,37.83113],[126.19097,37.81462],[126.18776,37.74728],[126.13074,37.70512],[125.81159,37.72949],[125.37112,37.62643],[125.06408,37.66334],[124.87921,37.80827],[124.84224,37.977],[124.67666,38.05679],[123.85601,37.49093],[122.80525,33.30571]]]}},{"type":"Feature","properties":{"id":"XK"},"geometry":{"type":"Polygon","coordinates":[[[20.02088,42.74789],[20.02915,42.71147],[20.0969,42.65559],[20.07761,42.55582],[20.17127,42.50469],[20.21797,42.41237],[20.24399,42.32168],[20.34479,42.32656],[20.3819,42.3029],[20.48857,42.25444],[20.56955,42.12097],[20.55633,42.08173],[20.59434,42.03879],[20.63069,41.94913],[20.57946,41.91593],[20.59524,41.8818],[20.68523,41.85318],[20.76786,41.91839],[20.75464,42.05229],[21.11491,42.20794],[21.16614,42.19815],[21.22728,42.08909],[21.31983,42.10993],[21.29913,42.13954],[21.30496,42.1418],[21.38428,42.24465],[21.43882,42.23609],[21.43882,42.2789],[21.50823,42.27156],[21.52145,42.24465],[21.58992,42.25915],[21.56772,42.30946],[21.5264,42.33634],[21.53467,42.36809],[21.57021,42.3647],[21.59029,42.38042],[21.62887,42.37664],[21.64209,42.41081],[21.62556,42.45106],[21.7035,42.51899],[21.70522,42.54176],[21.7327,42.55041],[21.75672,42.62695],[21.79413,42.65923],[21.75025,42.70125],[21.6626,42.67813],[21.58755,42.70418],[21.59154,42.72643],[21.47498,42.74695],[21.39045,42.74888],[21.44047,42.87276],[21.36941,42.87397],[21.32974,42.90424],[21.2719,42.8994],[21.23534,42.95523],[21.23877,43.00848],[21.2041,43.02277],[21.16734,42.99694],[21.14465,43.11089],[21.08952,43.13471],[21.05378,43.10707],[21.00749,43.13984],[20.96287,43.12416],[20.83727,43.17842],[20.88685,43.21697],[20.82145,43.26769],[20.73811,43.25068],[20.68688,43.21335],[20.59929,43.20492],[20.69515,43.09641],[20.64557,43.00826],[20.59929,43.01067],[20.48692,42.93208],[20.53484,42.8885],[20.43734,42.83157],[20.40594,42.84853],[20.35692,42.8335],[20.27869,42.81945],[20.2539,42.76245],[20.04898,42.77701],[20.02088,42.74789]]]}},{"type":"Feature","properties":{"id":"KW"},"geometry":{"type":"Polygon","coordinates":[[[46.5527,29.10283],[47.46202,29.0014],[47.58376,28.83382],[47.59863,28.66798],[47.70561,28.5221],[48.42991,28.53628],[49.00421,28.81495],[48.59531,29.66815],[48.40479,29.85763],[48.17332,30.02448],[48.06782,30.02906],[48.01114,29.98906],[47.7095,30.10453],[47.37192,30.10421],[47.15166,30.01044],[46.89695,29.50584],[46.5527,29.10283]]]}},{"type":"Feature","properties":{"id":"IN-JK"},"geometry":{"type":"Polygon","coordinates":[[[73.74862,34.34183],[73.8475,34.32935],[73.90517,34.35317],[73.98208,34.2522],[73.90677,34.10504],[73.88732,34.05105],[73.91341,34.01235],[74.21554,34.03853],[74.25262,34.01577],[74.26086,33.92237],[74.14001,33.83002],[74.05898,33.82089],[74.00891,33.75437],[73.96423,33.73071],[73.98968,33.66155],[73.97367,33.64061],[74.03576,33.56718],[74.10115,33.56392],[74.18121,33.4745],[74.17983,33.3679],[74.08782,33.26232],[74.01366,33.25199],[74.02144,33.18908],[74.15374,33.13477],[74.17571,33.07495],[74.31854,33.02891],[74.34875,32.97823],[74.31227,32.92795],[74.41467,32.90563],[74.45312,32.77755],[74.6289,32.75561],[74.64675,32.82604],[74.7113,32.84219],[74.65345,32.71225],[74.69542,32.66792],[74.64424,32.60985],[74.65251,32.56416],[74.67431,32.56676],[74.68362,32.49298],[74.84725,32.49075],[74.97634,32.45367],[75.03265,32.49538],[75.28259,32.36556],[75.49941,32.28074],[75.57083,32.36836],[75.78506,32.47095],[75.82936,32.52664],[75.92513,32.64689],[75.77888,32.9355],[75.95329,32.88362],[76.31584,33.1341],[76.76902,33.26337],[77.32795,32.82305],[77.66784,32.97007],[77.88345,32.7942],[77.97477,32.58905],[78.32565,32.75263],[78.38897,32.53938],[78.73916,32.69438],[78.7831,32.46873],[78.96713,32.33655],[78.99322,32.37948],[79.0979,32.38051],[79.13174,32.47766],[79.26768,32.53277],[79.46562,32.69668],[79.14016,33.02545],[79.15252,33.17156],[78.73636,33.56521],[78.67599,33.66445],[78.77349,33.73871],[78.73367,34.01121],[78.65657,34.03195],[78.66225,34.08858],[78.91769,34.15452],[78.99802,34.3027],[79.05364,34.32482],[78.74465,34.45174],[78.56475,34.50835],[78.54964,34.57283],[78.27781,34.61484],[78.18435,34.7998],[78.22692,34.88771],[78.00033,35.23954],[78.03466,35.3785],[78.11664,35.48022],[77.80532,35.52058],[77.70232,35.46244],[77.44277,35.46132],[76.96624,35.5932],[76.84539,35.67356],[76.77323,35.66062],[76.75475,35.52617],[76.85088,35.39754],[76.93465,35.39866],[77.11796,35.05419],[76.99251,34.93349],[76.87193,34.96906],[76.74514,34.92488],[76.74377,34.84039],[76.67648,34.76371],[76.47186,34.78965],[76.15463,34.6429],[76.04614,34.67566],[75.75438,34.51827],[75.38009,34.55021],[75.01479,34.64629],[74.6663,34.703],[74.58083,34.77386],[74.31239,34.79626],[74.12897,34.70073],[73.96423,34.68244],[73.93401,34.63386],[73.93951,34.57169],[73.89419,34.54568],[73.88732,34.48911],[73.74999,34.3781],[73.74862,34.34183]]]}},{"type":"Feature","properties":{"id":"LA"},"geometry":{"type":"Polygon","coordinates":[[[100.08404,20.36626],[100.09999,20.31614],[100.09337,20.26293],[100.11785,20.24787],[100.1712,20.24324],[100.16668,20.2986],[100.22076,20.31598],[100.25769,20.3992],[100.33383,20.4028],[100.37439,20.35156],[100.41473,20.25625],[100.44992,20.23644],[100.4537,20.19971],[100.47567,20.19133],[100.51052,20.14928],[100.55218,20.17741],[100.58808,20.15791],[100.5094,19.87904],[100.398,19.75047],[100.49604,19.53504],[100.58219,19.49164],[100.64606,19.55884],[100.77231,19.48324],[100.90302,19.61901],[101.08928,19.59748],[101.26545,19.59242],[101.26991,19.48324],[101.21347,19.46223],[101.20604,19.35296],[101.24911,19.33334],[101.261,19.12717],[101.35606,19.04716],[101.25803,18.89545],[101.22832,18.73377],[101.27585,18.68875],[101.06047,18.43247],[101.18227,18.34367],[101.15108,18.25624],[101.19118,18.2125],[101.1793,18.0544],[101.02185,17.87637],[100.96541,17.57926],[101.15108,17.47586],[101.44667,17.7392],[101.72294,17.92867],[101.78087,18.07559],[101.88485,18.02474],[102.11359,18.21532],[102.45523,17.97106],[102.59234,17.96127],[102.60971,17.95411],[102.61432,17.92273],[102.5896,17.84889],[102.59485,17.83537],[102.68194,17.80151],[102.69946,17.81686],[102.67543,17.84529],[102.68538,17.86653],[102.75954,17.89561],[102.79044,17.93612],[102.81988,17.94233],[102.86323,17.97531],[102.95812,18.0054],[102.9912,17.9949],[103.01998,17.97095],[103.0566,18.00144],[103.07823,18.03833],[103.07343,18.12351],[103.1493,18.17799],[103.14994,18.23172],[103.17093,18.2618],[103.29757,18.30475],[103.23818,18.34875],[103.24779,18.37807],[103.30977,18.4341],[103.41044,18.4486],[103.47773,18.42841],[103.60957,18.40528],[103.699,18.34125],[103.82449,18.33979],[103.85642,18.28666],[103.93916,18.33914],[103.97725,18.33631],[104.06533,18.21656],[104.10927,18.10826],[104.21776,17.99335],[104.2757,17.86139],[104.35432,17.82871],[104.45404,17.66788],[104.69867,17.53038],[104.80061,17.39367],[104.80716,17.19025],[104.73712,17.01404],[104.7373,16.91125],[104.76442,16.84752],[104.7397,16.81005],[104.76099,16.69302],[104.73349,16.565],[104.88057,16.37311],[105.00262,16.25627],[105.06204,16.09792],[105.42001,16.00657],[105.38508,15.987],[105.34115,15.92737],[105.37959,15.84074],[105.42285,15.76971],[105.46573,15.74742],[105.61756,15.68792],[105.60446,15.53301],[105.58191,15.41031],[105.47635,15.3796],[105.4692,15.33709],[105.50662,15.32054],[105.58043,15.32724],[105.46661,15.13132],[105.61162,15.00037],[105.5121,14.80802],[105.53864,14.55731],[105.43783,14.43865],[105.20894,14.34967],[105.2759,14.17496],[105.36775,14.09948],[105.44869,14.10703],[105.5561,14.15684],[105.78338,14.08438],[105.78182,14.02247],[105.90791,13.92881],[106.10405,13.9137],[106.10094,13.98471],[106.16632,14.01794],[106.18656,14.06324],[106.11962,14.11307],[106.10872,14.18401],[106.04801,14.20363],[106.02311,14.30623],[105.99509,14.32734],[106.00131,14.36957],[106.21302,14.36203],[106.25194,14.48415],[106.32355,14.44043],[106.40916,14.45249],[106.43874,14.52032],[106.47766,14.50977],[106.45898,14.55045],[106.50723,14.58963],[106.54148,14.59565],[106.57106,14.50525],[106.59908,14.50977],[106.63333,14.44194],[106.73762,14.42687],[106.80767,14.31226],[106.8497,14.29416],[106.90574,14.33639],[106.9649,14.3198],[106.98825,14.36806],[107.04585,14.41782],[107.03962,14.45099],[107.09722,14.3937],[107.17038,14.41782],[107.21241,14.48716],[107.256,14.48716],[107.26534,14.54292],[107.29803,14.58963],[107.3276,14.58812],[107.37897,14.54443],[107.44435,14.52785],[107.47238,14.61523],[107.54361,14.69092],[107.51579,14.79282],[107.59285,14.87795],[107.48277,14.93751],[107.46516,15.00982],[107.61486,15.0566],[107.61926,15.13949],[107.58844,15.20111],[107.62587,15.2266],[107.60605,15.37524],[107.62367,15.42193],[107.53341,15.40496],[107.50699,15.48771],[107.3815,15.49832],[107.34408,15.62345],[107.27583,15.62769],[107.27143,15.71459],[107.21859,15.74638],[107.21419,15.83747],[107.34188,15.89464],[107.39471,15.88829],[107.46296,16.01106],[107.44975,16.08511],[107.33968,16.05549],[107.25822,16.13587],[107.14595,16.17816],[107.15035,16.26271],[107.09091,16.3092],[107.02597,16.31132],[106.97385,16.30204],[106.96638,16.34938],[106.88067,16.43594],[106.88727,16.52671],[106.84104,16.55415],[106.74418,16.41904],[106.65832,16.47816],[106.66052,16.56892],[106.61477,16.60713],[106.58267,16.6012],[106.59013,16.62259],[106.55485,16.68704],[106.55265,16.86831],[106.52183,16.87884],[106.51963,16.92097],[106.54824,16.92729],[106.55045,17.0031],[106.50862,16.9673],[106.43597,17.01362],[106.31929,17.20509],[106.29287,17.3018],[106.24444,17.24714],[106.18991,17.28227],[106.09019,17.36399],[105.85744,17.63221],[105.76612,17.67147],[105.60381,17.89356],[105.64784,17.96687],[105.46292,18.22008],[105.38366,18.15315],[105.15942,18.38691],[105.10408,18.43533],[105.1327,18.58355],[105.19654,18.64196],[105.12829,18.70453],[104.64617,18.85668],[104.5361,18.97747],[103.87125,19.31854],[104.06058,19.43484],[104.10832,19.51575],[104.05617,19.61743],[104.06498,19.66926],[104.23229,19.70242],[104.41281,19.70035],[104.53169,19.61743],[104.64837,19.62365],[104.68359,19.72729],[104.8355,19.80395],[104.8465,19.91783],[104.9874,20.09573],[104.91695,20.15567],[104.86852,20.14121],[104.61315,20.24452],[104.62195,20.36633],[104.72102,20.40554],[104.66158,20.47774],[104.47886,20.37459],[104.40621,20.3849],[104.38199,20.47155],[104.63957,20.6653],[104.27412,20.91433],[104.11121,20.96779],[103.98024,20.91531],[103.82282,20.8732],[103.73478,20.6669],[103.68633,20.66324],[103.45737,20.82382],[103.38032,20.79501],[103.21497,20.89832],[103.12055,20.89994],[103.03469,21.05821],[102.97745,21.05821],[102.89825,21.24707],[102.80794,21.25736],[102.88939,21.3107],[102.94223,21.46034],[102.86297,21.4255],[102.98846,21.58936],[102.97965,21.74076],[102.86077,21.71213],[102.85637,21.84501],[102.81894,21.83888],[102.82115,21.73667],[102.74189,21.66713],[102.67145,21.65894],[102.62301,21.91447],[102.49092,21.99002],[102.51734,22.02676],[102.18712,22.30403],[102.14099,22.40092],[102.1245,22.43372],[102.03633,22.46164],[101.98487,22.42766],[101.91344,22.44417],[101.90714,22.38688],[101.86828,22.38397],[101.7685,22.50337],[101.68973,22.46843],[101.61306,22.27515],[101.56789,22.28876],[101.53638,22.24794],[101.60675,22.13513],[101.57525,22.13026],[101.62566,21.96574],[101.7791,21.83019],[101.74555,21.72852],[101.83257,21.61562],[101.80001,21.57461],[101.7475,21.5873],[101.7727,21.51794],[101.74224,21.48276],[101.74014,21.30967],[101.84412,21.25291],[101.83887,21.20983],[101.76745,21.21571],[101.79266,21.19025],[101.7622,21.14813],[101.70548,21.14911],[101.66977,21.20004],[101.60886,21.17947],[101.59491,21.18621],[101.6068,21.23329],[101.54563,21.25668],[101.29326,21.17254],[101.2229,21.23271],[101.26912,21.36482],[101.19349,21.41959],[101.2124,21.56422],[101.15156,21.56129],[101.16198,21.52808],[101.00234,21.39612],[100.80173,21.2934],[100.72716,21.31786],[100.63578,21.05639],[100.55281,21.02796],[100.50974,20.88574],[100.64628,20.88279],[100.60112,20.8347],[100.51079,20.82194],[100.36375,20.82783],[100.1957,20.68247],[100.08404,20.36626]]]}},{"type":"Feature","properties":{"id":"LB"},"geometry":{"type":"Polygon","coordinates":[[[34.78515,33.20368],[35.10645,33.09318],[35.1924,33.08743],[35.31429,33.10515],[35.35223,33.05617],[35.43059,33.06659],[35.448,33.09264],[35.50272,33.09056],[35.50335,33.114],[35.52573,33.11921],[35.54228,33.19865],[35.5362,33.23196],[35.54808,33.236],[35.54544,33.25513],[35.55555,33.25844],[35.56523,33.28969],[35.58326,33.28381],[35.58502,33.26653],[35.62283,33.24226],[35.62019,33.27278],[35.77477,33.33609],[35.81324,33.36354],[35.82577,33.40479],[35.88668,33.43183],[35.94816,33.47886],[35.94465,33.52774],[36.05723,33.57904],[35.9341,33.6596],[36.06778,33.82927],[36.14517,33.85118],[36.3967,33.83365],[36.38263,33.86579],[36.28589,33.91981],[36.41078,34.05253],[36.50576,34.05982],[36.5128,34.09916],[36.62537,34.20251],[36.59195,34.2316],[36.58667,34.27667],[36.60778,34.31009],[36.56556,34.31881],[36.53039,34.3798],[36.55853,34.41609],[36.46179,34.46541],[36.4442,34.50165],[36.34745,34.5002],[36.3369,34.52629],[36.39846,34.55672],[36.41429,34.61175],[36.45299,34.59438],[36.46003,34.6378],[36.42941,34.62505],[36.35384,34.65447],[36.35135,34.68516],[36.32399,34.69334],[36.29165,34.62991],[35.98718,34.64977],[35.97386,34.63322],[35.48515,34.70851],[34.78515,33.20368]]]}},{"type":"Feature","properties":{"id":"LR"},"geometry":{"type":"Polygon","coordinates":[[[-12.15048,6.15992],[-7.52774,3.7105],[-7.53259,4.35145],[-7.59349,4.8909],[-7.53876,4.94294],[-7.55369,5.08667],[-7.48901,5.14118],[-7.46165,5.26256],[-7.36463,5.32944],[-7.43428,5.42355],[-7.37209,5.61173],[-7.43926,5.74787],[-7.43677,5.84687],[-7.46165,5.84934],[-7.48155,5.80974],[-7.67309,5.94337],[-7.70294,5.90625],[-7.78254,5.99037],[-7.79747,6.07696],[-7.8497,6.08932],[-7.83478,6.20309],[-7.90692,6.27728],[-8.00642,6.31684],[-8.17557,6.28222],[-8.3298,6.36381],[-8.38453,6.35887],[-8.45666,6.49977],[-8.48652,6.43797],[-8.59456,6.50612],[-8.31736,6.82837],[-8.29249,7.1691],[-8.37458,7.25794],[-8.41935,7.51203],[-8.47114,7.55676],[-8.55874,7.62525],[-8.55874,7.70167],[-8.67814,7.69428],[-8.72789,7.51429],[-8.8448,7.35149],[-8.85724,7.26019],[-8.93435,7.2824],[-9.09107,7.1985],[-9.18311,7.30461],[-9.20798,7.38109],[-9.305,7.42056],[-9.41943,7.41809],[-9.48161,7.37122],[-9.37465,7.62032],[-9.35724,7.74111],[-9.44928,7.9284],[-9.41445,8.02448],[-9.50898,8.18455],[-9.47415,8.35195],[-9.77763,8.54633],[-10.05873,8.42578],[-10.05375,8.50697],[-10.14579,8.52665],[-10.203,8.47991],[-10.27575,8.48711],[-10.30084,8.30008],[-10.31635,8.28554],[-10.29839,8.21283],[-10.35227,8.15223],[-10.45023,8.15627],[-10.51554,8.1393],[-10.57523,8.04829],[-10.60492,8.04072],[-10.60422,7.7739],[-11.29417,7.21576],[-11.4027,6.97746],[-11.50429,6.92704],[-12.15048,6.15992]]]}},{"type":"Feature","properties":{"id":"LY"},"geometry":{"type":"Polygon","coordinates":[[[9.3876,30.16738],[9.78136,29.40961],[9.89569,26.57696],[9.51696,26.39148],[9.38834,26.19288],[10.03146,25.35635],[10.02432,24.98124],[10.33159,24.5465],[10.85323,24.5595],[11.41061,24.21456],[11.62498,24.26669],[11.96886,23.51735],[13.5631,23.16574],[14.22918,22.61719],[14.99751,23.00539],[15.99566,23.49639],[23.99539,19.49944],[23.99715,20.00038],[24.99794,19.99661],[24.99885,21.99535],[24.99968,29.24574],[24.71117,30.17441],[25.01077,30.73861],[24.83101,31.31921],[25.06041,31.57937],[25.14001,31.67534],[25.63787,31.9359],[22.5213,33.45682],[11.66543,33.34642],[11.56255,33.16754],[11.55852,33.1409],[11.51549,33.09826],[11.46037,32.6307],[11.57828,32.48013],[11.53898,32.4138],[11.04234,32.2145],[10.7315,31.97235],[10.62788,31.96629],[10.48497,31.72956],[10.31364,31.72648],[10.12239,31.42098],[10.29516,30.90337],[9.88152,30.34074],[9.76848,30.34366],[9.55544,30.23971],[9.3876,30.16738]]]}},{"type":"Feature","properties":{"id":"LC"},"geometry":{"type":"Polygon","coordinates":[[[-61.43129,13.68336],[-60.70539,13.41452],[-60.5958,14.23076],[-61.26561,14.25664],[-61.43129,13.68336]]]}},{"type":"Feature","properties":{"id":"LI"},"geometry":{"type":"Polygon","coordinates":[[[9.47139,47.06402],[9.47548,47.05257],[9.54041,47.06495],[9.55721,47.04762],[9.60717,47.06091],[9.61216,47.07732],[9.63395,47.08443],[9.62623,47.14685],[9.56539,47.17124],[9.58264,47.20673],[9.56981,47.21926],[9.55176,47.22585],[9.56766,47.24281],[9.53116,47.27029],[9.52406,47.24959],[9.50318,47.22153],[9.4891,47.19346],[9.48774,47.17402],[9.51044,47.13727],[9.52089,47.10019],[9.51362,47.08505],[9.47139,47.06402]]]}},{"type":"Feature","properties":{"id":"CN-HN"},"geometry":{"type":"Polygon","coordinates":[[[108.78936,27.08908],[108.87451,27.00652],[109.13268,27.06462],[109.40734,27.15264],[109.49043,27.06524],[109.51652,26.75726],[109.28581,26.70022],[109.38056,26.57931],[109.28649,26.28664],[109.45129,26.30449],[109.47669,26.03334],[109.72663,26.00001],[109.68749,25.88517],[109.82276,25.88023],[109.81109,26.04259],[109.97486,26.19241],[110.09811,26.01914],[110.31028,25.96915],[110.61172,26.33465],[110.75317,26.25277],[110.9262,26.26694],[110.96328,26.3851],[111.09306,26.31188],[111.29287,26.24662],[111.19125,25.94693],[111.25579,25.85922],[111.48239,25.88208],[111.37596,25.73001],[111.30042,25.70155],[111.31347,25.47613],[110.97015,25.10922],[110.98525,24.9157],[111.28326,25.14528],[111.46179,25.02526],[111.42745,24.6832],[111.52633,24.63952],[111.68666,24.78486],[112.04166,24.77987],[112.15015,24.82662],[112.17143,24.91944],[112.11547,24.96582],[112.13504,25.00535],[112.18963,25.18987],[112.65174,25.13751],[112.70393,25.08622],[112.7798,24.89328],[113.00571,24.93999],[112.96674,25.16361],[113.02854,25.20059],[112.98923,25.24857],[112.8713,25.24857],[112.84984,25.34278],[112.89653,25.31501],[113.01721,25.34976],[113.12725,25.47039],[113.26217,25.51486],[113.56635,25.30802],[113.98315,25.40916],[113.91448,25.69908],[114.00787,25.89258],[114.02606,26.021],[114.23789,26.20042],[113.94058,26.18193],[114.07482,26.40847],[114.104,26.57624],[113.91208,26.61277],[113.86402,26.65513],[113.83895,26.79342],[113.91517,26.94716],[113.77715,27.11781],[113.875,27.38548],[113.62232,27.34859],[113.62232,27.41017],[113.5976,27.42114],[113.59863,27.63365],[113.75656,27.81782],[113.72085,27.8755],[113.74351,27.9477],[114.03945,28.06289],[113.99963,28.15253],[114.25094,28.38233],[114.07653,28.55919],[114.14932,28.77187],[114.01817,28.89758],[113.9447,29.05196],[113.85749,29.04056],[113.81835,29.10837],[113.69132,29.06637],[113.69132,29.2187],[113.59588,29.25914],[113.7569,29.44767],[113.62403,29.52118],[113.7363,29.58928],[113.56704,29.67254],[113.51898,29.82813],[113.15505,29.46112],[112.94769,29.47188],[112.9264,29.69014],[112.8955,29.79328],[112.80796,29.74917],[112.68093,29.59734],[112.46566,29.6433],[112.28713,29.50147],[112.2377,29.65673],[112.10861,29.66001],[112.06706,29.73188],[111.95548,29.82843],[111.79447,29.91209],[111.6053,29.89006],[111.51912,29.93232],[111.40274,29.91268],[111.24481,30.04383],[110.94646,30.06493],[110.75729,30.12523],[110.75386,30.04888],[110.52246,30.06523],[110.4943,29.91923],[110.64742,29.77272],[110.37036,29.63345],[110.14514,29.78374],[109.78637,29.76556],[109.70088,29.60928],[109.53609,29.62331],[109.45678,29.55613],[109.2346,29.11829],[109.31877,29.05827],[109.23208,28.87985],[109.23688,28.78255],[109.29885,28.73018],[109.18556,28.63184],[109.19466,28.60275],[109.29671,28.62913],[109.31576,28.58595],[109.29121,28.57268],[109.27645,28.52202],[109.26727,28.51787],[109.26898,28.50437],[109.26993,28.49849],[109.25302,28.4711],[109.2616,28.39034],[109.28495,28.3772],[109.26881,28.3145],[109.39807,28.27717],[109.29336,28.05531],[109.38056,28.03683],[109.31533,27.9856],[109.33799,27.78198],[109.46708,27.69508],[109.45197,27.5722],[109.2937,27.42541],[109.14642,27.45344],[109.10659,27.3495],[109.04651,27.33578],[109.0472,27.29643],[108.78936,27.08908]]]}},{"type":"Feature","properties":{"id":"LS"},"geometry":{"type":"Polygon","coordinates":[[[27.01016,-29.65439],[27.09489,-29.72796],[27.22719,-30.00718],[27.29603,-30.05473],[27.32555,-30.14785],[27.40778,-30.14577],[27.37293,-30.19401],[27.36649,-30.27246],[27.38108,-30.33456],[27.45452,-30.32239],[27.56901,-30.42504],[27.56781,-30.44562],[27.62137,-30.50509],[27.6521,-30.51707],[27.67819,-30.53437],[27.69467,-30.55862],[27.74814,-30.60635],[28.12073,-30.68072],[28.2319,-30.28476],[28.399,-30.1592],[28.68627,-30.12885],[28.80222,-30.10579],[28.9338,-30.05072],[29.16548,-29.91706],[29.12553,-29.76266],[29.28545,-29.58456],[29.33204,-29.45598],[29.44883,-29.3772],[29.40524,-29.21246],[28.68043,-28.58744],[28.65091,-28.57025],[28.40612,-28.6215],[28.30518,-28.69531],[28.2348,-28.69471],[28.1317,-28.7293],[28.02503,-28.85991],[27.98675,-28.8787],[27.9392,-28.84864],[27.88933,-28.88156],[27.8907,-28.91612],[27.75458,-28.89839],[27.55974,-29.18954],[27.5158,-29.2261],[27.54258,-29.25575],[27.48679,-29.29349],[27.45125,-29.29708],[27.47254,-29.31968],[27.4358,-29.33465],[27.33464,-29.48161],[27.01016,-29.65439]]]}},{"type":"Feature","properties":{"id":"LT"},"geometry":{"type":"Polygon","coordinates":[[[20.60454,55.40986],[20.95181,55.27994],[21.26425,55.24456],[21.35465,55.28427],[21.38446,55.29348],[21.46766,55.21115],[21.51095,55.18507],[21.55605,55.20311],[21.64954,55.1791],[21.85521,55.09493],[21.96505,55.07353],[21.99543,55.08691],[22.03984,55.07888],[22.02582,55.05078],[22.06087,55.02935],[22.11697,55.02131],[22.14267,55.05345],[22.31562,55.0655],[22.47688,55.04408],[22.58907,55.07085],[22.60075,55.01863],[22.65451,54.97037],[22.68723,54.9811],[22.76422,54.92521],[22.85083,54.88711],[22.87317,54.79492],[22.73631,54.72952],[22.73397,54.66604],[22.75467,54.6483],[22.74225,54.64339],[22.7522,54.63525],[22.68021,54.58486],[22.71293,54.56454],[22.67788,54.532],[22.70208,54.45312],[22.7253,54.41732],[22.79705,54.36264],[22.83756,54.40827],[23.00584,54.38514],[22.99649,54.35927],[23.05726,54.34565],[23.04323,54.31567],[23.104,54.29794],[23.13905,54.31567],[23.15526,54.31076],[23.15938,54.29894],[23.24656,54.25701],[23.3494,54.25155],[23.39525,54.21672],[23.42418,54.17911],[23.45223,54.17775],[23.49196,54.14764],[23.52702,54.04622],[23.48261,53.98855],[23.51284,53.95052],[23.61677,53.92691],[23.71726,53.93379],[23.80543,53.89558],[23.81309,53.94205],[23.95098,53.9613],[23.98837,53.92554],[24.19638,53.96405],[24.34128,53.90076],[24.44411,53.90076],[24.62275,54.00217],[24.69652,54.01901],[24.69185,53.96543],[24.74279,53.96663],[24.85311,54.02862],[24.77131,54.11091],[24.96894,54.17589],[24.991,54.14241],[25.0728,54.13419],[25.19199,54.219],[25.22705,54.26271],[25.35559,54.26544],[25.509,54.30267],[25.56823,54.25212],[25.51452,54.17799],[25.54724,54.14925],[25.64875,54.1259],[25.71084,54.16704],[25.78563,54.15747],[25.78553,54.23327],[25.68513,54.31727],[25.55425,54.31591],[25.5376,54.33158],[25.63371,54.42075],[25.62203,54.4656],[25.64813,54.48704],[25.68045,54.5321],[25.75977,54.57252],[25.74122,54.80108],[25.89462,54.93438],[25.99129,54.95705],[26.05907,54.94631],[26.13386,54.98924],[26.20397,54.99729],[26.26941,55.08032],[26.23202,55.10439],[26.30628,55.12536],[26.35121,55.1525],[26.46249,55.12814],[26.51481,55.16051],[26.54753,55.14181],[26.69243,55.16718],[26.68075,55.19787],[26.72983,55.21788],[26.73017,55.24226],[26.835,55.28182],[26.83266,55.30444],[26.80929,55.31642],[26.6714,55.33902],[26.5709,55.32572],[26.44937,55.34832],[26.5522,55.40277],[26.55094,55.5093],[26.63167,55.57887],[26.63231,55.67968],[26.58248,55.6754],[26.46661,55.70375],[26.39561,55.71156],[26.18509,55.86813],[26.03815,55.95884],[25.90047,56.0013],[25.85893,56.00188],[25.81773,56.05444],[25.69246,56.08892],[25.68588,56.14725],[25.53621,56.16663],[25.39751,56.15707],[25.23099,56.19147],[25.09325,56.1878],[25.05762,56.26742],[24.89005,56.46666],[24.83686,56.41565],[24.70022,56.40483],[24.57353,56.31525],[24.58143,56.29125],[24.42746,56.26522],[24.32334,56.30226],[24.13139,56.24881],[24.02657,56.3231],[23.75726,56.37282],[23.49803,56.34307],[23.40486,56.37689],[23.31606,56.3827],[23.17312,56.36795],[23.09531,56.30511],[22.96988,56.41213],[22.83048,56.367],[22.69354,56.36284],[22.56441,56.39305],[22.3361,56.4016],[22.09728,56.42851],[22.00548,56.41508],[21.74558,56.33181],[21.57888,56.31406],[21.49736,56.29106],[21.24644,56.16917],[21.15016,56.07818],[20.68447,56.04073],[20.60454,55.40986]]]}},{"type":"Feature","properties":{"id":"LU"},"geometry":{"type":"Polygon","coordinates":[[[5.73621,49.89796],[5.78415,49.87922],[5.75269,49.8711],[5.75861,49.85631],[5.74567,49.85368],[5.75884,49.84811],[5.74953,49.84709],[5.74975,49.83933],[5.74076,49.83823],[5.7404,49.83452],[5.74844,49.82435],[5.74364,49.82058],[5.74953,49.81428],[5.75409,49.79239],[5.78871,49.7962],[5.82245,49.75048],[5.83149,49.74729],[5.82562,49.72395],[5.84193,49.72161],[5.86503,49.72739],[5.88677,49.70951],[5.86527,49.69291],[5.86175,49.67862],[5.9069,49.66377],[5.90164,49.6511],[5.90599,49.63853],[5.88552,49.63507],[5.88393,49.62802],[5.87609,49.62047],[5.8762,49.60898],[5.84826,49.5969],[5.84971,49.58674],[5.86986,49.58756],[5.87256,49.57539],[5.8424,49.56082],[5.84692,49.55663],[5.84143,49.5533],[5.81838,49.54777],[5.80871,49.5425],[5.81664,49.53775],[5.83648,49.5425],[5.84466,49.53027],[5.83467,49.52717],[5.83389,49.52152],[5.86571,49.50015],[5.94128,49.50034],[5.94224,49.49608],[5.96876,49.49053],[5.97693,49.45513],[6.02648,49.45451],[6.02743,49.44845],[6.04176,49.44801],[6.05553,49.46663],[6.07887,49.46399],[6.08373,49.45594],[6.10072,49.45268],[6.09845,49.46351],[6.10325,49.4707],[6.12346,49.4735],[6.12814,49.49365],[6.14321,49.48796],[6.16115,49.49297],[6.15366,49.50226],[6.17386,49.50934],[6.19543,49.50536],[6.2409,49.51408],[6.25029,49.50609],[6.27875,49.503],[6.28818,49.48465],[6.3687,49.4593],[6.36778,49.46937],[6.36907,49.48931],[6.36788,49.50377],[6.35666,49.52931],[6.38072,49.55171],[6.38228,49.55855],[6.35825,49.57053],[6.36676,49.57813],[6.38024,49.57593],[6.38342,49.5799],[6.37464,49.58886],[6.385,49.59946],[6.39822,49.60081],[6.41861,49.61723],[6.4413,49.65722],[6.43768,49.66021],[6.42726,49.66078],[6.42937,49.66857],[6.44654,49.67799],[6.46048,49.69092],[6.48014,49.69767],[6.49785,49.71118],[6.50647,49.71353],[6.5042,49.71808],[6.49694,49.72205],[6.49535,49.72645],[6.50261,49.72718],[6.51397,49.72058],[6.51805,49.72425],[6.50193,49.73291],[6.50174,49.75292],[6.51646,49.75961],[6.51828,49.76855],[6.51056,49.77515],[6.51669,49.78336],[6.50534,49.78952],[6.52169,49.79787],[6.53122,49.80666],[6.52121,49.81338],[6.51215,49.80124],[6.50647,49.80916],[6.48718,49.81267],[6.47111,49.82263],[6.45425,49.81164],[6.44131,49.81443],[6.42905,49.81091],[6.42521,49.81591],[6.40022,49.82029],[6.36576,49.85032],[6.34267,49.84974],[6.33585,49.83785],[6.32098,49.83728],[6.32303,49.85133],[6.30963,49.87021],[6.29692,49.86685],[6.28874,49.87592],[6.26146,49.88203],[6.23496,49.89972],[6.22926,49.92096],[6.21882,49.92403],[6.22608,49.929],[6.22094,49.94955],[6.19856,49.95053],[6.19089,49.96991],[6.18045,49.96611],[6.18554,49.95622],[6.17872,49.9537],[6.16466,49.97086],[6.1701,49.98518],[6.14147,49.99563],[6.14948,50.00908],[6.13806,50.01056],[6.1295,50.01849],[6.13273,50.02019],[6.13794,50.01466],[6.14666,50.02207],[6.13044,50.02929],[6.13458,50.04141],[6.11274,50.05916],[6.12055,50.09171],[6.1379,50.12964],[6.1137,50.13668],[6.12028,50.16374],[6.08577,50.17246],[6.06406,50.15344],[6.03093,50.16362],[6.02488,50.18283],[5.96453,50.17259],[5.95929,50.13295],[5.89488,50.11476],[5.8857,50.07824],[5.85474,50.06342],[5.86904,50.04614],[5.8551,50.02683],[5.81866,50.01286],[5.82331,49.99662],[5.83968,49.9892],[5.83467,49.97823],[5.81163,49.97142],[5.80833,49.96451],[5.77291,49.96056],[5.77314,49.93646],[5.73621,49.89796]]]}},{"type":"Feature","properties":{"id":"LV"},"geometry":{"type":"Polygon","coordinates":[[[19.64795,57.06466],[20.68447,56.04073],[21.15016,56.07818],[21.24644,56.16917],[21.49736,56.29106],[21.57888,56.31406],[21.74558,56.33181],[22.00548,56.41508],[22.09728,56.42851],[22.3361,56.4016],[22.56441,56.39305],[22.69354,56.36284],[22.83048,56.367],[22.96988,56.41213],[23.09531,56.30511],[23.17312,56.36795],[23.31606,56.3827],[23.40486,56.37689],[23.49803,56.34307],[23.75726,56.37282],[24.02657,56.3231],[24.13139,56.24881],[24.32334,56.30226],[24.42746,56.26522],[24.58143,56.29125],[24.57353,56.31525],[24.70022,56.40483],[24.83686,56.41565],[24.89005,56.46666],[25.05762,56.26742],[25.09325,56.1878],[25.23099,56.19147],[25.39751,56.15707],[25.53621,56.16663],[25.68588,56.14725],[25.69246,56.08892],[25.81773,56.05444],[25.85893,56.00188],[25.90047,56.0013],[26.03815,55.95884],[26.18509,55.86813],[26.39561,55.71156],[26.46661,55.70375],[26.58248,55.6754],[26.63231,55.67968],[26.64888,55.70515],[26.71802,55.70645],[26.76872,55.67658],[26.87448,55.7172],[26.97153,55.8102],[27.1559,55.85032],[27.27804,55.78299],[27.3541,55.8089],[27.61683,55.78558],[27.63065,55.89687],[27.97865,56.11849],[28.15217,56.16964],[28.23716,56.27588],[28.16599,56.37806],[28.19057,56.44637],[28.10069,56.524],[28.13526,56.57989],[28.04768,56.59004],[27.86101,56.88204],[27.66511,56.83921],[27.86101,57.29402],[27.52453,57.42826],[27.56832,57.53728],[27.34698,57.52242],[26.90364,57.62823],[26.54675,57.51813],[26.46527,57.56885],[26.29253,57.59244],[26.1866,57.6849],[26.2029,57.7206],[26.08098,57.76619],[26.0543,57.76105],[26.03332,57.7718],[26.02415,57.76865],[26.02069,57.77169],[26.0266,57.77441],[26.027,57.78158],[26.02456,57.78342],[26.0324,57.79037],[26.05949,57.84744],[25.73499,57.90193],[25.29581,58.08288],[25.28237,57.98539],[25.19484,58.0831],[24.3579,57.87471],[24.26221,57.91787],[23.20055,57.56697],[22.80496,57.87798],[19.84909,57.57876],[19.64795,57.06466]]]}},{"type":"Feature","properties":{"id":"MC"},"geometry":{"type":"Polygon","coordinates":[[[7.40903,43.7296],[7.41855,43.72479],[7.50102,43.51859],[7.53358,43.53609],[7.45448,43.7432],[7.4379,43.74963],[7.4389,43.75151],[7.43708,43.75197],[7.43624,43.75014],[7.43013,43.74895],[7.42809,43.74396],[7.42443,43.74087],[7.42299,43.74176],[7.42062,43.73977],[7.41233,43.73439],[7.41298,43.73311],[7.41291,43.73168],[7.41113,43.73156],[7.40903,43.7296]]]}},{"type":"Feature","properties":{"id":"MD"},"geometry":{"type":"Polygon","coordinates":[[[26.62823,48.25804],[26.81161,48.25049],[26.87708,48.19919],[26.94265,48.1969],[26.98042,48.15752],[26.96119,48.13003],[27.04118,48.12522],[27.02985,48.09083],[27.15622,47.98538],[27.1618,47.92391],[27.29069,47.73722],[27.25519,47.71366],[27.32202,47.64009],[27.3979,47.59473],[27.47942,47.48113],[27.55731,47.46637],[27.60263,47.32507],[27.68706,47.28962],[27.73172,47.29248],[27.81892,47.1381],[28.09095,46.97621],[28.12173,46.82283],[28.24808,46.64305],[28.22281,46.50481],[28.25769,46.43334],[28.18902,46.35283],[28.19864,46.31869],[28.10937,46.22852],[28.13684,46.18099],[28.08612,46.01105],[28.13111,45.92819],[28.16568,45.6421],[28.08927,45.6051],[28.18741,45.47358],[28.21139,45.46895],[28.30201,45.54744],[28.41836,45.51715],[28.43072,45.48538],[28.51449,45.49982],[28.49252,45.56716],[28.54196,45.58062],[28.51587,45.6613],[28.47879,45.66994],[28.52823,45.73803],[28.70401,45.78019],[28.69852,45.81753],[28.78503,45.83475],[28.74383,45.96664],[28.98004,46.00385],[29.00613,46.04962],[28.94643,46.09176],[29.06656,46.19716],[28.94953,46.25852],[28.98478,46.31803],[29.004,46.31495],[28.9306,46.45699],[29.01241,46.46177],[29.02409,46.49582],[29.23547,46.55435],[29.24886,46.37912],[29.35357,46.49505],[29.49914,46.45889],[29.5939,46.35472],[29.6763,46.36041],[29.66359,46.4215],[29.74496,46.45605],[29.88329,46.35851],[29.94114,46.40114],[30.09103,46.38694],[30.16794,46.40967],[30.02511,46.45132],[29.88916,46.54302],[29.94409,46.56002],[29.9743,46.75325],[29.94522,46.80055],[29.98814,46.82358],[29.87405,46.88199],[29.75458,46.8604],[29.72986,46.92234],[29.57056,46.94766],[29.62137,47.05069],[29.61038,47.09932],[29.53044,47.07851],[29.49732,47.12878],[29.57696,47.13581],[29.54996,47.24962],[29.59665,47.25521],[29.5733,47.36508],[29.48678,47.36043],[29.47854,47.30366],[29.39889,47.30179],[29.3261,47.44664],[29.18603,47.43387],[29.11743,47.55001],[29.22414,47.60012],[29.22242,47.73607],[29.27255,47.79953],[29.20663,47.80367],[29.27804,47.88893],[29.19839,47.89261],[29.1723,47.99013],[28.9306,47.96255],[28.8414,48.03392],[28.85232,48.12506],[28.69896,48.13106],[28.53921,48.17453],[28.48428,48.0737],[28.42454,48.12047],[28.43701,48.15832],[28.38712,48.17567],[28.34009,48.13147],[28.30609,48.14018],[28.30586,48.1597],[28.34912,48.1787],[28.36996,48.20543],[28.35519,48.24957],[28.32508,48.23384],[28.2856,48.23202],[28.19314,48.20749],[28.17666,48.25963],[28.07504,48.23494],[28.09873,48.3124],[28.04527,48.32661],[27.95883,48.32368],[27.88391,48.36699],[27.87533,48.4037],[27.81902,48.41874],[27.79225,48.44244],[27.74422,48.45926],[27.6658,48.44034],[27.59027,48.46311],[27.5889,48.49224],[27.46942,48.454],[27.44333,48.41209],[27.37741,48.41026],[27.37604,48.44398],[27.32159,48.4434],[27.27855,48.37534],[27.13434,48.37288],[27.08078,48.43214],[27.0231,48.42485],[27.03821,48.37653],[26.93384,48.36558],[26.85556,48.41095],[26.71274,48.40388],[26.82809,48.31629],[26.79239,48.29071],[26.6839,48.35828],[26.62823,48.25804]]]}},{"type":"Feature","properties":{"id":"MG"},"geometry":{"type":"Polygon","coordinates":[[[40.40841,-23.17181],[42.93867,-25.64228],[47.18248,-26.33102],[51.94557,-12.74579],[48.86266,-10.8109],[47.29063,-12.45583],[43.72277,-16.09877],[40.40841,-23.17181]]]}},{"type":"Feature","properties":{"id":"MK"},"geometry":{"type":"Polygon","coordinates":[[[20.45331,41.51436],[20.49039,41.49277],[20.51301,41.442],[20.55976,41.4087],[20.52119,41.34381],[20.49432,41.33679],[20.51068,41.2323],[20.59715,41.13644],[20.58546,41.11179],[20.59832,41.09066],[20.63454,41.0889],[20.65558,41.08009],[20.71634,40.91781],[20.73504,40.9081],[20.81567,40.89662],[20.83671,40.92752],[20.94305,40.92399],[20.97693,40.90103],[20.97887,40.85475],[21.15262,40.85546],[21.21105,40.8855],[21.25779,40.86165],[21.35595,40.87578],[21.41555,40.9173],[21.53007,40.90759],[21.57448,40.86076],[21.69601,40.9429],[21.7556,40.92525],[21.91102,41.04786],[21.90869,41.09191],[22.06527,41.15617],[22.1424,41.12449],[22.17629,41.15969],[22.26744,41.16409],[22.42285,41.11921],[22.5549,41.13065],[22.58295,41.11568],[22.62852,41.14385],[22.65306,41.18168],[22.71266,41.13945],[22.74538,41.16321],[22.76408,41.32225],[22.81199,41.3398],[22.93334,41.34104],[22.96331,41.35782],[22.95513,41.63265],[23.03342,41.71034],[23.01239,41.76527],[22.96682,41.77137],[22.90254,41.87587],[22.86749,42.02275],[22.67701,42.06614],[22.51224,42.15457],[22.50289,42.19527],[22.47251,42.20393],[22.38136,42.30339],[22.34773,42.31725],[22.29275,42.34913],[22.29605,42.37477],[22.16384,42.32103],[22.02908,42.29848],[21.94405,42.34669],[21.91595,42.30392],[21.84654,42.3247],[21.77176,42.2648],[21.70111,42.23789],[21.58992,42.25915],[21.52145,42.24465],[21.50823,42.27156],[21.43882,42.2789],[21.43882,42.23609],[21.38428,42.24465],[21.30496,42.1418],[21.29913,42.13954],[21.31983,42.10993],[21.22728,42.08909],[21.16614,42.19815],[21.11491,42.20794],[20.75464,42.05229],[20.76786,41.91839],[20.68523,41.85318],[20.59524,41.8818],[20.55976,41.87068],[20.57144,41.7897],[20.53405,41.78099],[20.51301,41.72433],[20.52937,41.69292],[20.51769,41.65975],[20.55508,41.58113],[20.52103,41.56473],[20.45809,41.5549],[20.45331,41.51436]]]}},{"type":"Feature","properties":{"id":"ML"},"geometry":{"type":"Polygon","coordinates":[[[-12.23936,14.76324],[-11.93043,13.84505],[-12.06897,13.71049],[-11.83345,13.33333],[-11.63025,13.39174],[-11.39935,12.97808],[-11.37536,12.40788],[-11.50006,12.17826],[-11.24136,12.01286],[-10.99758,12.24634],[-10.80355,12.1053],[-10.71897,11.91552],[-10.30604,12.24634],[-9.714,12.0226],[-9.63938,12.18312],[-9.32097,12.29009],[-9.38067,12.48446],[-9.13689,12.50875],[-8.94784,12.34842],[-8.80854,11.66715],[-8.40058,11.37466],[-8.66923,10.99397],[-8.35083,11.06234],[-8.2667,10.91762],[-8.32614,10.69273],[-8.22711,10.41722],[-8.10207,10.44649],[-7.9578,10.2703],[-7.97971,10.17117],[-7.92107,10.15577],[-7.63048,10.46334],[-7.54462,10.40921],[-7.52261,10.4655],[-7.44555,10.44602],[-7.3707,10.24677],[-7.13331,10.24877],[-7.0603,10.14711],[-7.00966,10.15794],[-6.97444,10.21644],[-7.01186,10.25111],[-6.93921,10.35291],[-6.68164,10.35074],[-6.63541,10.66893],[-6.52974,10.59104],[-6.42847,10.5694],[-6.40646,10.69922],[-6.325,10.68624],[-6.24795,10.74248],[-6.1731,10.46983],[-6.18851,10.24244],[-5.99478,10.19694],[-5.78124,10.43952],[-5.65135,10.46767],[-5.51058,10.43177],[-5.46643,10.56074],[-5.47083,10.75329],[-5.41579,10.84628],[-5.49284,11.07538],[-5.32994,11.13371],[-5.32553,11.21578],[-5.25949,11.24816],[-5.25509,11.36905],[-5.20665,11.43811],[-5.22867,11.60421],[-5.29251,11.61715],[-5.26389,11.75728],[-5.40258,11.8327],[-5.26389,11.84778],[-5.07897,11.97918],[-4.72893,12.01579],[-4.70692,12.06746],[-4.62987,12.06531],[-4.62546,12.13204],[-4.54841,12.1385],[-4.57703,12.19875],[-4.41412,12.31922],[-4.47356,12.71252],[-4.238,12.71467],[-4.21819,12.95722],[-4.34477,13.12927],[-3.96501,13.49778],[-3.90558,13.44375],[-3.96282,13.38164],[-3.7911,13.36665],[-3.54454,13.1781],[-3.4313,13.1588],[-3.43507,13.27272],[-3.23599,13.29035],[-3.28396,13.5422],[-3.26407,13.70699],[-2.88189,13.64921],[-2.90831,13.81174],[-2.84667,14.05532],[-2.66175,14.14713],[-2.47587,14.29671],[-2.10223,14.14878],[-1.9992,14.19011],[-1.97945,14.47709],[-1.68083,14.50023],[-1.32166,14.72774],[-1.05875,14.7921],[-0.72004,15.08655],[-0.24673,15.07805],[0.06588,14.96961],[0.23859,15.00135],[0.72632,14.95898],[0.96711,14.98275],[1.31275,15.27978],[3.01806,15.34571],[3.03134,15.42221],[3.50368,15.35934],[4.19893,16.39923],[4.21787,17.00118],[4.26762,17.00432],[4.26651,19.14224],[3.36082,18.9745],[3.12501,19.1366],[3.24648,19.81703],[1.20992,20.73533],[1.15698,21.12843],[-4.83423,24.99935],[-6.57191,25.0002],[-5.60725,16.49919],[-5.33435,16.33354],[-5.50165,15.50061],[-9.32979,15.50032],[-9.31106,15.69412],[-9.33314,15.7044],[-9.44673,15.60553],[-9.40447,15.4396],[-10.71721,15.4223],[-10.90932,15.11001],[-11.43483,15.62339],[-11.70705,15.51558],[-11.94903,14.76143],[-12.23936,14.76324]]]}},{"type":"Feature","properties":{"id":"MM"},"geometry":{"type":"Polygon","coordinates":[[[92.17752,21.17445],[92.26071,21.05697],[92.37665,20.72172],[92.28464,20.63179],[92.31348,20.57137],[92.4302,20.5688],[92.39837,20.38919],[92.61282,13.95915],[93.69443,13.6468],[94.6395,14.00732],[97.16045,11.79791],[97.63455,9.60854],[98.21525,9.56576],[98.33094,9.91973],[98.47298,9.95782],[98.52291,9.92389],[98.55174,9.92804],[98.7391,10.31488],[98.81944,10.52761],[98.77275,10.62548],[98.78511,10.68351],[98.86819,10.78336],[99.0069,10.85485],[98.99701,10.92962],[99.02337,10.97217],[99.06938,10.94857],[99.32756,11.28545],[99.31573,11.32081],[99.39485,11.3925],[99.47598,11.62434],[99.5672,11.62732],[99.64108,11.78948],[99.64891,11.82699],[99.53424,12.02317],[99.56445,12.14805],[99.47519,12.1353],[99.409,12.60603],[99.29254,12.68921],[99.18905,12.84799],[99.18748,12.9898],[99.10646,13.05804],[99.12225,13.19847],[99.20617,13.20575],[99.16695,13.72621],[98.97356,14.04868],[98.56762,14.37701],[98.24874,14.83013],[98.18821,15.13125],[98.22,15.21327],[98.30446,15.30667],[98.40522,15.25268],[98.41906,15.27103],[98.39351,15.34177],[98.4866,15.39154],[98.56027,15.33471],[98.58598,15.46821],[98.541,15.65406],[98.59853,15.87197],[98.57019,16.04578],[98.69585,16.13353],[98.8376,16.11706],[98.92656,16.36425],[98.84485,16.42354],[98.68074,16.27068],[98.63817,16.47424],[98.57912,16.55983],[98.5695,16.62826],[98.51113,16.64503],[98.51833,16.676],[98.51472,16.68521],[98.51579,16.69433],[98.51043,16.70107],[98.49713,16.69022],[98.50253,16.7139],[98.46994,16.73613],[98.53833,16.81934],[98.49603,16.8446],[98.52624,16.89979],[98.39441,17.06266],[98.34566,17.04822],[98.10439,17.33847],[98.11185,17.36829],[97.91829,17.54504],[97.76407,17.71595],[97.66794,17.88005],[97.73723,17.97912],[97.60841,18.23846],[97.64116,18.29778],[97.56219,18.33885],[97.50383,18.26844],[97.34522,18.54596],[97.36444,18.57138],[97.5258,18.4939],[97.76752,18.58097],[97.73836,18.88478],[97.66487,18.9371],[97.73654,18.9812],[97.73797,19.04261],[97.83479,19.09972],[97.84024,19.22217],[97.78606,19.26769],[97.84186,19.29526],[97.78769,19.39429],[97.88423,19.5041],[97.84715,19.55782],[98.04364,19.65755],[98.03314,19.80941],[98.13829,19.78541],[98.24884,19.67876],[98.51182,19.71303],[98.56065,19.67807],[98.83661,19.80931],[98.98679,19.7419],[99.0735,20.10298],[99.20328,20.12877],[99.416,20.08614],[99.52943,20.14811],[99.5569,20.20676],[99.46077,20.36198],[99.46008,20.39673],[99.68255,20.32077],[99.81096,20.33687],[99.86383,20.44371],[99.88211,20.44488],[99.88451,20.44596],[99.89168,20.44548],[99.89301,20.44311],[99.89692,20.44789],[99.90499,20.4487],[99.91616,20.44986],[99.95721,20.46301],[100.08404,20.36626],[100.1957,20.68247],[100.36375,20.82783],[100.51079,20.82194],[100.60112,20.8347],[100.64628,20.88279],[100.50974,20.88574],[100.55281,21.02796],[100.63578,21.05639],[100.72716,21.31786],[100.80173,21.2934],[101.00234,21.39612],[101.16198,21.52808],[101.15156,21.56129],[101.11744,21.77659],[100.87265,21.67396],[100.72143,21.51898],[100.57861,21.45637],[100.4811,21.46148],[100.42892,21.54325],[100.35201,21.53176],[100.25863,21.47043],[100.18447,21.51898],[100.1625,21.48704],[100.12542,21.50365],[100.10757,21.59945],[100.17486,21.65306],[100.12679,21.70539],[100.04956,21.66843],[99.98654,21.71064],[99.94003,21.82782],[99.99084,21.97053],[99.96612,22.05965],[99.85351,22.04183],[99.47585,22.13345],[99.33166,22.09656],[99.1552,22.15874],[99.19176,22.16983],[99.17318,22.18025],[99.28771,22.4105],[99.37972,22.50188],[99.38247,22.57544],[99.31243,22.73893],[99.45654,22.85726],[99.43537,22.94086],[99.54218,22.90014],[99.52214,23.08218],[99.34127,23.13099],[99.25741,23.09025],[99.04601,23.12215],[99.05975,23.16382],[98.88597,23.18656],[98.92515,23.29535],[98.93958,23.31414],[98.87573,23.33038],[98.92104,23.36946],[98.87683,23.48995],[98.82877,23.47908],[98.80294,23.5345],[98.88396,23.59555],[98.81775,23.694],[98.82933,23.72921],[98.79607,23.77947],[98.68209,23.80492],[98.67797,23.9644],[98.89632,24.10612],[98.87998,24.15624],[98.85319,24.13042],[98.59256,24.08371],[98.54476,24.13119],[98.20666,24.11406],[98.07806,24.07988],[98.06703,24.08028],[98.0607,24.07812],[98.05671,24.07961],[98.05302,24.07408],[98.04709,24.07616],[97.99583,24.04932],[97.98691,24.03897],[97.93951,24.01953],[97.90998,24.02094],[97.88616,24.00463],[97.88414,23.99405],[97.88814,23.98605],[97.89683,23.98389],[97.89676,23.97931],[97.8955,23.97758],[97.88811,23.97446],[97.86545,23.97723],[97.84328,23.97603],[97.79416,23.95663],[97.79456,23.94836],[97.72302,23.89288],[97.64667,23.84574],[97.5247,23.94032],[97.62363,24.00506],[97.72903,24.12606],[97.75305,24.16902],[97.72799,24.18883],[97.72998,24.2302],[97.76799,24.26365],[97.71941,24.29652],[97.66723,24.30027],[97.65624,24.33781],[97.7098,24.35658],[97.66998,24.45288],[97.60029,24.4401],[97.52757,24.43748],[97.56286,24.54535],[97.56525,24.72838],[97.54675,24.74202],[97.5542,24.74943],[97.56383,24.75535],[97.56648,24.76475],[97.64354,24.79171],[97.70181,24.84557],[97.73127,24.83015],[97.76481,24.8289],[97.79949,24.85655],[97.72903,24.91332],[97.72216,25.08508],[97.77023,25.11492],[97.83614,25.2715],[97.92541,25.20815],[98.14925,25.41547],[98.12591,25.50722],[98.18084,25.56298],[98.16848,25.62739],[98.25774,25.6051],[98.31268,25.55307],[98.40606,25.61129],[98.54064,25.85129],[98.63128,25.79937],[98.70818,25.86241],[98.60763,26.01512],[98.57085,26.11547],[98.63128,26.15492],[98.66884,26.09165],[98.7329,26.17218],[98.67797,26.24487],[98.72741,26.36183],[98.77547,26.61994],[98.7333,26.85615],[98.69582,27.56499],[98.43353,27.67086],[98.42529,27.55404],[98.32641,27.51385],[98.13964,27.9478],[98.15337,28.12114],[97.90069,28.3776],[97.79632,28.33168],[97.70705,28.5056],[97.56835,28.55628],[97.50518,28.49716],[97.47085,28.2688],[97.41729,28.29783],[97.34547,28.21385],[97.31292,28.06784],[97.35412,28.06663],[97.38845,28.01329],[97.35824,27.87256],[97.29919,27.92233],[96.90112,27.62149],[96.91431,27.45752],[97.17422,27.14052],[97.14675,27.09041],[96.89132,27.17474],[96.85287,27.2065],[96.88445,27.25046],[96.73888,27.36638],[96.55761,27.29928],[96.40779,27.29818],[96.15591,27.24572],[96.04949,27.19428],[95.93002,27.04149],[95.81603,27.01335],[95.437,26.7083],[95.30339,26.65372],[95.23513,26.68499],[95.05798,26.45408],[95.12801,26.38397],[95.11428,26.1019],[95.18556,26.07338],[94.80117,25.49359],[94.68032,25.47003],[94.57458,25.20318],[94.74212,25.13606],[94.73937,25.00545],[94.60204,24.70889],[94.5526,24.70764],[94.50729,24.59281],[94.45279,24.56656],[94.32362,24.27692],[94.30215,24.23752],[94.14081,23.83333],[93.92089,23.95812],[93.80279,23.92549],[93.75952,24.0003],[93.62871,24.00922],[93.50616,23.94432],[93.46633,23.97067],[93.41415,24.07854],[93.34735,24.10151],[93.32351,24.04468],[93.36059,23.93176],[93.3908,23.92925],[93.3908,23.7622],[93.43475,23.68299],[93.38805,23.4728],[93.39981,23.38828],[93.38781,23.36139],[93.36862,23.35426],[93.38478,23.13698],[93.2878,23.00464],[93.12988,23.05772],[93.134,22.92498],[93.09417,22.69459],[93.134,22.59573],[93.11477,22.54374],[93.13537,22.45873],[93.18206,22.43716],[93.19991,22.25425],[93.14224,22.24535],[93.15734,22.18687],[93.04885,22.20595],[92.99255,22.05965],[92.99804,21.98964],[92.93899,22.02656],[92.89504,21.95143],[92.86208,22.05456],[92.70416,22.16017],[92.67532,22.03547],[92.60949,21.97638],[92.62187,21.87037],[92.59775,21.6092],[92.68152,21.28454],[92.60187,21.24615],[92.55105,21.3856],[92.43158,21.37025],[92.37939,21.47764],[92.20087,21.337],[92.17752,21.17445]]]}},{"type":"Feature","properties":{"id":"ME"},"geometry":{"type":"Polygon","coordinates":[[[18.43588,42.48556],[18.52152,42.42302],[18.54128,42.39171],[18.45131,42.21682],[19.26406,41.74971],[19.37597,41.84849],[19.37451,41.8842],[19.33812,41.90669],[19.34601,41.95675],[19.37691,41.96977],[19.36867,42.02564],[19.37548,42.06835],[19.40687,42.10024],[19.28623,42.17745],[19.42,42.33019],[19.42352,42.36546],[19.4836,42.40831],[19.65972,42.62774],[19.73244,42.66299],[19.77375,42.58517],[19.74731,42.57422],[19.76549,42.50237],[19.82333,42.46581],[19.9324,42.51699],[20.00842,42.5109],[20.01834,42.54622],[20.07761,42.55582],[20.0969,42.65559],[20.02915,42.71147],[20.02088,42.74789],[20.04898,42.77701],[20.2539,42.76245],[20.27869,42.81945],[20.35692,42.8335],[20.34528,42.90676],[20.16415,42.97177],[20.14896,42.99058],[20.12325,42.96237],[20.05431,42.99571],[20.04729,43.02732],[19.98887,43.0538],[19.96549,43.11098],[19.92576,43.08539],[19.79255,43.11951],[19.76918,43.16044],[19.64063,43.19027],[19.62661,43.2286],[19.54598,43.25158],[19.52962,43.31623],[19.48171,43.32644],[19.44315,43.38846],[19.22229,43.47926],[19.22807,43.5264],[19.15685,43.53943],[19.13933,43.5282],[19.04934,43.50384],[19.01078,43.55806],[18.91379,43.50299],[18.95469,43.49367],[18.96053,43.45042],[19.01078,43.43854],[19.04071,43.397],[19.08673,43.31453],[19.08206,43.29668],[19.04233,43.30008],[19.00844,43.24988],[18.95001,43.29327],[18.95819,43.32899],[18.90911,43.36383],[18.83912,43.34795],[18.84794,43.33735],[18.85342,43.32426],[18.76538,43.29838],[18.6976,43.25243],[18.71747,43.2286],[18.66605,43.2056],[18.64735,43.14766],[18.66254,43.03928],[18.52232,43.01451],[18.49076,42.95553],[18.49661,42.89306],[18.4935,42.86433],[18.47633,42.85829],[18.45921,42.81682],[18.47324,42.74992],[18.56789,42.72074],[18.55221,42.69045],[18.54603,42.69171],[18.54841,42.68328],[18.57373,42.64429],[18.52232,42.62279],[18.55504,42.58409],[18.53751,42.57376],[18.49778,42.58409],[18.43735,42.55921],[18.44307,42.51077],[18.43588,42.48556]]]}},{"type":"Feature","properties":{"id":"MN"},"geometry":{"type":"Polygon","coordinates":[[[87.73822,48.89582],[88.0788,48.71436],[87.96361,48.58478],[88.58939,48.34531],[88.58316,48.21893],[88.8011,48.11302],[88.93186,48.10263],[89.0711,47.98528],[89.55453,48.0423],[89.76624,47.82745],[90.06512,47.88177],[90.10871,47.7375],[90.33598,47.68303],[90.48854,47.41826],[90.48542,47.30438],[90.76108,46.99399],[90.84035,46.99525],[91.03649,46.72916],[91.0147,46.58171],[91.07696,46.57315],[90.89639,46.30711],[90.99672,46.14207],[91.03026,46.04194],[90.70907,45.73437],[90.65114,45.49314],[90.89169,45.19667],[91.64048,45.07408],[93.51161,44.95964],[94.10003,44.71016],[94.71959,44.35284],[95.01191,44.25274],[95.39772,44.2805],[95.32891,44.02407],[95.52594,43.99353],[95.89543,43.2528],[96.35658,42.90363],[96.37926,42.72055],[97.1777,42.7964],[99.50671,42.56535],[100.33297,42.68231],[100.84979,42.67087],[101.28833,42.58524],[101.80515,42.50074],[102.07645,42.22519],[102.42826,42.15137],[102.72403,42.14675],[103.3685,41.89696],[103.92804,41.78246],[104.52258,41.8706],[104.51667,41.66113],[104.91272,41.64619],[105.01119,41.58382],[105.24708,41.7442],[106.76517,42.28741],[107.24774,42.36107],[107.29755,42.41395],[107.49681,42.46221],[107.57258,42.40898],[108.23156,42.45532],[108.84489,42.40246],[109.00679,42.45302],[109.452,42.44842],[109.89402,42.63111],[110.08401,42.6411],[110.4327,42.78293],[111.0149,43.3289],[111.59087,43.51207],[111.79758,43.6637],[111.93776,43.68709],[111.96289,43.81596],[111.40498,44.3461],[111.76275,44.98032],[111.98695,45.09074],[112.4164,45.06858],[112.74662,44.86297],[113.63821,44.74326],[113.909,44.91444],[114.08071,44.92847],[114.5166,45.27189],[114.54801,45.38337],[114.74612,45.43585],[114.94546,45.37377],[115.35757,45.39106],[115.69688,45.45761],[115.91898,45.6227],[116.16989,45.68603],[116.27366,45.78637],[116.24012,45.8778],[116.26678,45.96479],[116.58612,46.30211],[116.75551,46.33083],[116.83166,46.38637],[117.07252,46.35818],[117.36609,46.36335],[117.41782,46.57862],[117.60748,46.59771],[117.69554,46.50991],[118.30534,46.73519],[118.78747,46.68689],[118.8337,46.77742],[118.89974,46.77139],[118.92616,46.72765],[119.00541,46.74273],[119.10448,46.65516],[119.24978,46.64761],[119.30261,46.6083],[119.37306,46.61132],[119.42827,46.63783],[119.65265,46.62342],[119.68127,46.59015],[119.77373,46.62947],[119.80455,46.67631],[119.89261,46.66423],[119.91242,46.90091],[119.85518,46.92196],[119.71209,47.19192],[119.62403,47.24575],[119.56019,47.24874],[119.54918,47.29505],[119.31964,47.42617],[119.35892,47.48104],[119.13995,47.53997],[119.12343,47.66458],[118.7564,47.76947],[118.55766,47.99277],[118.29654,48.00246],[118.22677,48.03853],[118.11009,48.04],[118.03676,48.00982],[117.80196,48.01661],[117.50181,47.77216],[117.37875,47.63627],[117.08918,47.82242],[116.87527,47.88836],[116.67405,47.89039],[116.4465,47.83662],[116.2527,47.87766],[116.08431,47.80693],[115.94296,47.67741],[115.57128,47.91988],[115.52082,48.15367],[115.811,48.25699],[115.78876,48.51781],[116.06565,48.81716],[116.03781,48.87014],[116.71193,49.83813],[116.62502,49.92919],[116.22402,50.04477],[115.73602,49.87688],[115.26068,49.97367],[114.9703,50.19254],[114.325,50.28098],[113.20216,49.83356],[113.02647,49.60772],[110.64493,49.1816],[110.39891,49.25083],[110.24373,49.16676],[109.51325,49.22859],[109.18017,49.34709],[108.53969,49.32325],[108.27937,49.53167],[107.95387,49.66659],[107.96116,49.93191],[107.36407,49.97612],[107.1174,50.04239],[107.00007,50.1977],[106.80326,50.30177],[106.58373,50.34044],[106.51122,50.34408],[106.49628,50.32436],[106.47156,50.31909],[106.07865,50.33474],[106.05562,50.40582],[105.32528,50.4648],[103.70343,50.13952],[102.71178,50.38873],[102.32194,50.67982],[102.14032,51.35566],[101.5044,51.50467],[101.39085,51.45753],[100.61116,51.73028],[99.89203,51.74903],[99.75578,51.90108],[99.27888,51.96876],[98.87768,52.14563],[98.74142,51.8637],[98.33222,51.71832],[98.22053,51.46579],[98.05257,51.46696],[97.83305,51.00248],[98.01472,50.86652],[97.9693,50.78044],[98.06393,50.61262],[98.31373,50.4996],[98.29481,50.33561],[97.85197,49.91339],[97.76871,49.99861],[97.56432,49.92801],[97.56811,49.84265],[97.24639,49.74737],[96.97388,49.88413],[95.80056,50.04239],[95.74757,49.97915],[95.02465,49.96941],[94.97166,50.04725],[94.6121,50.04239],[94.49477,50.17832],[94.39258,50.22193],[94.30823,50.57498],[92.99595,50.63183],[93.01109,50.79001],[92.44714,50.78762],[92.07173,50.69585],[91.86048,50.73734],[89.59711,49.90851],[89.70687,49.72535],[88.82499,49.44808],[88.42449,49.48821],[88.17223,49.46934],[88.15543,49.30314],[87.98977,49.18147],[87.81333,49.17354],[87.88171,48.95853],[87.73822,48.89582]]]}},{"type":"Feature","properties":{"id":"MZ"},"geometry":{"type":"Polygon","coordinates":[[[30.22098,-14.99447],[30.41902,-15.62269],[30.42568,-15.9962],[30.91597,-15.99924],[30.97761,-16.05848],[31.13171,-15.98019],[31.30563,-16.01193],[31.42451,-16.15154],[31.67988,-16.19595],[31.90223,-16.34388],[31.91324,-16.41569],[32.02772,-16.43892],[32.28529,-16.43892],[32.42838,-16.4727],[32.71017,-16.59932],[32.69917,-16.66893],[32.78943,-16.70267],[32.97655,-16.70689],[32.91051,-16.89446],[32.84113,-16.92259],[32.96554,-17.11971],[33.00517,-17.30477],[33.0426,-17.3468],[32.96554,-17.48964],[32.98536,-17.55891],[33.0492,-17.60298],[32.94133,-17.99705],[33.03159,-18.35054],[33.02278,-18.4696],[32.88629,-18.51344],[32.88629,-18.58023],[32.95013,-18.69079],[32.9017,-18.7992],[32.82465,-18.77419],[32.70137,-18.84712],[32.73439,-18.92628],[32.69917,-18.94293],[32.72118,-19.02204],[32.84006,-19.0262],[32.87088,-19.09279],[32.85107,-19.29238],[32.77966,-19.36098],[32.78282,-19.47513],[32.84446,-19.48343],[32.84666,-19.68462],[32.95013,-19.67219],[33.06461,-19.77787],[33.01178,-20.02007],[32.93032,-20.03868],[32.85987,-20.16686],[32.85987,-20.27841],[32.66174,-20.56106],[32.55167,-20.56312],[32.48122,-20.63319],[32.51644,-20.91929],[32.37115,-21.133],[32.48236,-21.32873],[32.41234,-21.31246],[31.38336,-22.36919],[31.30611,-22.422],[31.55779,-23.176],[31.56539,-23.47268],[31.67942,-23.60858],[31.70223,-23.72695],[31.77445,-23.90082],[31.87707,-23.95293],[31.90368,-24.18892],[31.9835,-24.29983],[32.03196,-25.10785],[32.01676,-25.38117],[31.97875,-25.46356],[32.00631,-25.65044],[31.92649,-25.84216],[31.974,-25.95387],[32.00916,-25.999],[32.08599,-26.00978],[32.10435,-26.15656],[32.07352,-26.40185],[32.13409,-26.5317],[32.13315,-26.84345],[32.19409,-26.84032],[32.22302,-26.84136],[32.29584,-26.852],[32.35222,-26.86027],[32.89816,-26.8579],[33.10054,-26.92273],[39.10324,-21.48967],[41.06663,-17.08802],[42.99868,-12.65261],[42.93552,-11.11413],[40.74206,-10.25691],[40.44265,-10.4618],[40.00295,-10.80255],[39.58249,-10.96043],[39.24395,-11.17433],[38.88996,-11.16978],[38.47258,-11.4199],[38.21598,-11.27289],[37.93618,-11.26228],[37.8388,-11.3123],[37.76614,-11.53352],[37.3936,-11.68949],[36.80309,-11.56836],[36.62068,-11.72884],[36.19094,-11.70008],[36.19094,-11.57593],[35.82767,-11.41081],[35.63599,-11.55927],[34.96296,-11.57354],[34.64241,-11.57499],[34.57917,-11.87849],[34.82903,-12.04837],[34.70739,-12.15652],[34.46088,-12.0174],[34.37831,-12.17408],[34.60253,-13.48487],[34.86229,-13.48958],[35.47989,-14.15594],[35.5299,-14.27714],[35.86945,-14.67481],[35.87212,-14.89478],[35.91812,-14.89514],[35.78799,-15.17428],[35.85303,-15.41913],[35.80487,-16.03907],[35.70107,-16.10147],[35.52365,-16.15414],[35.43355,-16.11371],[35.30157,-16.2211],[35.25828,-16.4792],[35.14235,-16.56812],[35.27219,-16.69402],[35.30929,-16.82871],[35.27065,-16.93817],[35.3062,-17.1244],[35.0923,-17.13235],[35.04805,-17.00027],[35.17017,-16.93521],[35.13771,-16.81687],[35.04805,-16.83167],[34.40344,-16.20923],[34.43126,-16.04737],[34.25195,-15.90321],[34.44981,-15.60864],[34.43126,-15.44778],[34.57503,-15.30619],[34.61522,-14.99583],[34.567,-14.77345],[34.54503,-14.74672],[34.52057,-14.68263],[34.53516,-14.67782],[34.55112,-14.64494],[34.53962,-14.59776],[34.52366,-14.5667],[34.49636,-14.55091],[34.48932,-14.53646],[34.47628,-14.53363],[34.45053,-14.49873],[34.44641,-14.47746],[34.4192,-14.43191],[34.39277,-14.39467],[34.35843,-14.38652],[34.34453,-14.3985],[34.22355,-14.43607],[34.18733,-14.43823],[34.08588,-14.48893],[33.92898,-14.47929],[33.88503,-14.51652],[33.7247,-14.4989],[33.66677,-14.61306],[33.24249,-14.00019],[30.22098,-14.99447]]]}},{"type":"Feature","properties":{"id":"MR"},"geometry":{"type":"Polygon","coordinates":[[[-17.15288,16.07139],[-16.50854,16.09032],[-16.48967,16.0496],[-16.44814,16.09753],[-16.4429,16.20605],[-16.27016,16.51565],[-15.6509,16.50315],[-15.00557,16.64997],[-14.32144,16.61495],[-13.80075,16.13961],[-13.43135,16.09022],[-13.11029,15.52116],[-12.23936,14.76324],[-11.94903,14.76143],[-11.70705,15.51558],[-11.43483,15.62339],[-10.90932,15.11001],[-10.71721,15.4223],[-9.40447,15.4396],[-9.44673,15.60553],[-9.33314,15.7044],[-9.31106,15.69412],[-9.32979,15.50032],[-5.50165,15.50061],[-5.33435,16.33354],[-5.60725,16.49919],[-6.57191,25.0002],[-4.83423,24.99935],[-8.66674,27.31569],[-8.66721,25.99918],[-12.0002,25.9986],[-12.00251,23.4538],[-12.14969,23.41935],[-12.36213,23.3187],[-12.5741,23.28975],[-13.00412,23.02297],[-13.10753,22.89493],[-13.15313,22.75649],[-13.08438,22.53866],[-13.01525,21.33343],[-16.95474,21.33997],[-16.99806,21.12142],[-17.0357,21.05368],[-17.0396,20.9961],[-17.06781,20.92697],[-17.0695,20.85742],[-17.0471,20.76408],[-17.15288,16.07139]]]}},{"type":"Feature","properties":{"id":"MW"},"geometry":{"type":"Polygon","coordinates":[[[32.66468,-13.60019],[32.68654,-13.64268],[32.7828,-13.64805],[32.84528,-13.71576],[32.76962,-13.77224],[32.79015,-13.80755],[32.88985,-13.82956],[32.99042,-13.95689],[33.02977,-14.05022],[33.07568,-13.98447],[33.16749,-13.93992],[33.24249,-14.00019],[33.66677,-14.61306],[33.7247,-14.4989],[33.88503,-14.51652],[33.92898,-14.47929],[34.08588,-14.48893],[34.18733,-14.43823],[34.22355,-14.43607],[34.34453,-14.3985],[34.35843,-14.38652],[34.39277,-14.39467],[34.4192,-14.43191],[34.44641,-14.47746],[34.45053,-14.49873],[34.47628,-14.53363],[34.48932,-14.53646],[34.49636,-14.55091],[34.52366,-14.5667],[34.53962,-14.59776],[34.55112,-14.64494],[34.53516,-14.67782],[34.52057,-14.68263],[34.54503,-14.74672],[34.567,-14.77345],[34.61522,-14.99583],[34.57503,-15.30619],[34.43126,-15.44778],[34.44981,-15.60864],[34.25195,-15.90321],[34.43126,-16.04737],[34.40344,-16.20923],[35.04805,-16.83167],[35.13771,-16.81687],[35.17017,-16.93521],[35.04805,-17.00027],[35.0923,-17.13235],[35.3062,-17.1244],[35.27065,-16.93817],[35.30929,-16.82871],[35.27219,-16.69402],[35.14235,-16.56812],[35.25828,-16.4792],[35.30157,-16.2211],[35.43355,-16.11371],[35.52365,-16.15414],[35.70107,-16.10147],[35.80487,-16.03907],[35.85303,-15.41913],[35.78799,-15.17428],[35.91812,-14.89514],[35.87212,-14.89478],[35.86945,-14.67481],[35.5299,-14.27714],[35.47989,-14.15594],[34.86229,-13.48958],[34.60253,-13.48487],[34.37831,-12.17408],[34.46088,-12.0174],[34.70739,-12.15652],[34.82903,-12.04837],[34.57917,-11.87849],[34.64241,-11.57499],[34.96296,-11.57354],[34.91153,-11.39799],[34.79375,-11.32245],[34.63305,-11.11731],[34.61161,-11.01611],[34.67047,-10.93796],[34.65946,-10.6828],[34.57581,-10.56271],[34.51911,-10.12279],[34.54499,-10.0678],[34.03865,-9.49398],[33.95829,-9.54066],[33.9638,-9.62206],[33.93298,-9.71647],[33.76677,-9.58516],[33.48052,-9.62442],[33.31581,-9.48554],[33.14925,-9.49322],[32.99397,-9.36712],[32.95389,-9.40138],[33.00476,-9.5133],[33.00256,-9.63053],[33.05485,-9.61316],[33.10163,-9.66525],[33.12144,-9.58929],[33.2095,-9.61099],[33.31517,-9.82364],[33.36581,-9.81063],[33.37902,-9.9104],[33.31297,-10.05133],[33.53863,-10.20148],[33.54797,-10.36077],[33.70675,-10.56896],[33.47636,-10.78465],[33.28022,-10.84428],[33.25998,-10.88862],[33.39697,-11.15296],[33.29267,-11.3789],[33.29267,-11.43536],[33.23663,-11.40637],[33.24252,-11.59302],[33.32692,-11.59248],[33.33937,-11.91252],[33.25998,-12.14242],[33.3705,-12.34931],[33.47636,-12.32498],[33.54485,-12.35996],[33.37517,-12.54085],[33.28177,-12.54692],[33.18837,-12.61377],[33.05917,-12.59554],[32.94397,-12.76868],[32.96733,-12.88251],[33.02181,-12.88707],[32.98289,-13.12671],[33.0078,-13.19492],[32.86113,-13.47292],[32.84176,-13.52794],[32.73683,-13.57682],[32.68436,-13.55769],[32.66468,-13.60019]]]}},{"type":"Feature","properties":{"id":"NA"},"geometry":{"type":"Polygon","coordinates":[[[10.5065,-17.25284],[15.70388,-29.23989],[16.45332,-28.63117],[16.46592,-28.57126],[16.59922,-28.53246],[16.90446,-28.057],[17.15405,-28.08573],[17.4579,-28.68718],[18.99885,-28.89165],[19.99882,-28.42622],[19.99817,-24.76768],[19.99912,-21.99991],[20.99751,-22.00026],[20.99904,-18.31743],[21.45556,-18.31795],[23.0996,-18.00075],[23.29618,-17.99855],[23.61088,-18.4881],[24.19416,-18.01919],[24.40577,-17.95726],[24.57485,-18.07151],[24.6303,-17.9863],[24.71887,-17.9218],[24.73364,-17.89338],[24.95586,-17.79674],[25.05895,-17.84452],[25.16882,-17.78253],[25.26433,-17.79571],[25.00198,-17.58221],[24.70864,-17.49501],[24.5621,-17.52963],[24.38712,-17.46818],[24.32811,-17.49082],[24.23619,-17.47489],[23.47474,-17.62877],[21.42741,-18.02787],[21.14283,-17.94318],[18.84226,-17.80375],[18.39229,-17.38927],[14.28743,-17.38814],[13.95896,-17.43141],[13.36212,-16.98048],[12.97145,-16.98567],[12.52111,-17.24495],[12.07076,-17.15165],[11.75063,-17.25013],[10.5065,-17.25284]]]}},{"type":"Feature","properties":{"id":"NE"},"geometry":{"type":"Polygon","coordinates":[[[0.16936,14.51654],[0.38051,14.05575],[0.61924,13.68491],[0.77377,13.6866],[0.77637,13.64442],[0.99514,13.5668],[1.02813,13.46635],[1.20088,13.38951],[1.24429,13.39373],[1.28509,13.35488],[1.24516,13.33968],[1.21217,13.37853],[1.18873,13.31771],[0.99253,13.37515],[0.99167,13.10727],[2.26349,12.41915],[2.05785,12.35539],[2.39723,11.89473],[2.45824,11.98672],[2.39657,12.10952],[2.37783,12.24804],[2.6593,12.30631],[2.83978,12.40585],[3.25352,12.01467],[3.31613,11.88495],[3.48187,11.86092],[3.59375,11.70269],[3.61075,11.69181],[3.67988,11.75429],[3.67122,11.80865],[3.63063,11.83042],[3.61955,11.91847],[3.67775,11.97599],[3.63136,12.11826],[3.66364,12.25884],[3.65111,12.52223],[3.94339,12.74979],[4.10006,12.98862],[4.14367,13.17189],[4.14186,13.47586],[4.23456,13.47725],[4.4668,13.68286],[4.87425,13.78],[4.9368,13.7345],[5.07396,13.75052],[5.21026,13.73627],[5.27797,13.75474],[5.35437,13.83567],[5.52957,13.8845],[6.15771,13.64564],[6.27411,13.67835],[6.43053,13.6006],[6.69617,13.34057],[6.94445,12.99825],[7.0521,13.00076],[7.12676,13.02445],[7.22399,13.1293],[7.39241,13.09717],[7.81085,13.34902],[8.07997,13.30847],[8.25185,13.20369],[8.41853,13.06166],[8.49493,13.07519],[8.60431,13.01768],[8.64251,12.93985],[8.97413,12.83661],[9.65995,12.80614],[10.00373,13.18171],[10.19993,13.27129],[10.46731,13.28819],[10.66004,13.36422],[11.4535,13.37773],[11.88236,13.2527],[12.04209,13.14452],[12.16189,13.10056],[12.19315,13.12423],[12.47095,13.06673],[12.58033,13.27805],[12.6793,13.29157],[12.87376,13.48919],[13.05085,13.53984],[13.19844,13.52802],[13.33213,13.71195],[13.6302,13.71094],[13.47559,14.40881],[13.48259,14.46704],[13.68573,14.55276],[13.67878,14.64013],[13.809,14.72915],[13.78991,14.87519],[13.86301,15.04043],[14.37425,15.72591],[15.50373,16.89649],[15.6032,18.77402],[15.75098,19.93002],[15.99632,20.35364],[15.6721,20.70069],[15.59841,20.74039],[15.56004,20.79488],[15.55382,20.86507],[15.57248,20.92138],[15.62515,20.95395],[15.28332,21.44557],[15.20213,21.49365],[15.19692,21.99339],[14.99751,23.00539],[14.22918,22.61719],[13.5631,23.16574],[11.96886,23.51735],[7.48273,20.87258],[7.38361,20.79165],[5.8153,19.45101],[4.26651,19.14224],[4.26762,17.00432],[4.21787,17.00118],[4.19893,16.39923],[3.50368,15.35934],[3.03134,15.42221],[3.01806,15.34571],[1.31275,15.27978],[0.96711,14.98275],[0.72632,14.95898],[0.23859,15.00135],[0.16936,14.51654]]]}},{"type":"Feature","properties":{"id":"NG"},"geometry":{"type":"Polygon","coordinates":[[[2.67523,7.87825],[2.73095,7.7755],[2.73405,7.5423],[2.78668,7.5116],[2.79442,7.43486],[2.74489,7.42565],[2.76965,7.13543],[2.71702,6.95722],[2.74024,6.92802],[2.73405,6.78508],[2.78823,6.76356],[2.78204,6.70514],[2.7325,6.64057],[2.74334,6.57291],[2.70464,6.50831],[2.70566,6.38038],[2.74181,6.13349],[5.87055,3.78489],[8.34397,4.30689],[8.60302,4.87353],[8.78027,5.1243],[8.92029,5.58403],[8.83687,5.68483],[8.88156,5.78857],[8.84209,5.82562],[9.51757,6.43874],[9.70674,6.51717],[9.77824,6.79088],[9.86314,6.77756],[10.15135,7.03781],[10.21466,6.88996],[10.53639,6.93432],[10.57214,7.16345],[10.59746,7.14719],[10.60789,7.06885],[10.83727,6.9358],[10.8179,6.83377],[10.94302,6.69325],[11.09644,6.68437],[11.09495,6.51717],[11.42041,6.53789],[11.42264,6.5882],[11.51499,6.60892],[11.57755,6.74059],[11.55818,6.86186],[11.63117,6.9905],[11.87396,7.09398],[11.84864,7.26098],[11.93205,7.47812],[12.01844,7.52981],[11.99908,7.67302],[12.20909,7.97553],[12.19271,8.10826],[12.24782,8.17904],[12.26123,8.43696],[12.4489,8.52536],[12.44146,8.6152],[12.68722,8.65938],[12.71701,8.7595],[12.79,8.75361],[12.81085,8.91992],[12.90022,9.11411],[12.91958,9.33905],[12.85628,9.36698],[13.02385,9.49334],[13.22642,9.57266],[13.25472,9.76795],[13.29941,9.8296],[13.25025,9.86042],[13.24132,9.91031],[13.27409,9.93232],[13.286,9.9822],[13.25323,10.00127],[13.25025,10.03647],[13.34111,10.12299],[13.43644,10.13326],[13.5705,10.53183],[13.54964,10.61236],[13.73434,10.9255],[13.70753,10.94451],[13.7403,11.00593],[13.78945,11.00154],[13.97489,11.30258],[14.17821,11.23831],[14.6124,11.51283],[14.64591,11.66166],[14.55207,11.72001],[14.61612,11.7798],[14.6474,12.17466],[14.4843,12.35223],[14.22215,12.36533],[14.17523,12.41916],[14.20204,12.53405],[14.08251,13.0797],[13.6302,13.71094],[13.33213,13.71195],[13.19844,13.52802],[13.05085,13.53984],[12.87376,13.48919],[12.6793,13.29157],[12.58033,13.27805],[12.47095,13.06673],[12.19315,13.12423],[12.16189,13.10056],[12.04209,13.14452],[11.88236,13.2527],[11.4535,13.37773],[10.66004,13.36422],[10.46731,13.28819],[10.19993,13.27129],[10.00373,13.18171],[9.65995,12.80614],[8.97413,12.83661],[8.64251,12.93985],[8.60431,13.01768],[8.49493,13.07519],[8.41853,13.06166],[8.25185,13.20369],[8.07997,13.30847],[7.81085,13.34902],[7.39241,13.09717],[7.22399,13.1293],[7.12676,13.02445],[7.0521,13.00076],[6.94445,12.99825],[6.69617,13.34057],[6.43053,13.6006],[6.27411,13.67835],[6.15771,13.64564],[5.52957,13.8845],[5.35437,13.83567],[5.27797,13.75474],[5.21026,13.73627],[5.07396,13.75052],[4.9368,13.7345],[4.87425,13.78],[4.4668,13.68286],[4.23456,13.47725],[4.14186,13.47586],[4.14367,13.17189],[4.10006,12.98862],[3.94339,12.74979],[3.65111,12.52223],[3.66364,12.25884],[3.63136,12.11826],[3.67775,11.97599],[3.61955,11.91847],[3.63063,11.83042],[3.67122,11.80865],[3.67988,11.75429],[3.61075,11.69181],[3.59375,11.70269],[3.49175,11.29765],[3.71505,11.13015],[3.84243,10.59316],[3.78292,10.40538],[3.6844,10.46351],[3.57275,10.27185],[3.66908,10.18136],[3.54429,9.87739],[3.35383,9.83641],[3.32099,9.78032],[3.34726,9.70696],[3.25093,9.61632],[3.13928,9.47167],[3.14147,9.28375],[3.08017,9.10006],[2.77907,9.06924],[2.67523,7.87825]]]}},{"type":"Feature","properties":{"id":"NI"},"geometry":{"type":"Polygon","coordinates":[[[-88.11443,12.63306],[-86.14524,11.09059],[-85.71223,11.06868],[-85.60529,11.22607],[-84.92439,10.9497],[-84.68197,11.07568],[-83.90838,10.71161],[-83.66597,10.79916],[-83.68276,11.01562],[-82.56142,11.91792],[-82.06974,14.49418],[-83.04763,15.03256],[-83.13724,15.00002],[-83.49268,15.01158],[-83.62101,14.89448],[-83.89551,14.76697],[-84.10584,14.76353],[-84.48373,14.63249],[-84.70119,14.68078],[-84.82596,14.82212],[-84.90082,14.80489],[-85.1575,14.53934],[-85.18602,14.24929],[-85.32149,14.2562],[-85.45762,14.11304],[-85.73964,13.9698],[-85.75477,13.8499],[-86.03458,13.99181],[-86.00685,14.08474],[-86.14801,14.04317],[-86.35219,13.77157],[-86.76812,13.79605],[-86.71267,13.30348],[-86.87066,13.30641],[-86.93383,13.18677],[-86.93197,13.05313],[-87.03785,12.98682],[-87.06306,13.00892],[-87.37107,12.98646],[-87.55124,13.12523],[-87.7346,13.13228],[-88.11443,12.63306]]]}},{"type":"Feature","properties":{"id":"IN-OR"},"geometry":{"type":"Polygon","coordinates":[[[81.39221,17.81014],[81.77398,17.88596],[82.04383,18.06622],[82.25532,17.98199],[82.35351,18.14193],[82.42835,18.49198],[82.63366,18.23522],[82.82592,18.43727],[82.96737,18.33692],[83.08822,18.54081],[83.11019,18.77176],[83.37936,18.83481],[83.33816,19.01084],[83.65058,19.12311],[83.88748,18.80296],[84.03991,18.79581],[84.08128,18.74478],[84.2284,18.78883],[84.31594,18.78411],[84.50408,19.04394],[84.59369,19.02317],[84.65961,19.06925],[84.59335,19.11662],[84.70287,19.15068],[84.76158,19.07055],[85.11932,18.86211],[87.74779,20.78694],[87.43709,21.76045],[87.27264,21.81114],[87.22457,21.96024],[87.05635,21.85066],[86.94992,22.08627],[86.71371,22.1448],[86.7247,22.21569],[86.03324,22.56012],[85.96115,22.48242],[86.01779,22.30434],[85.90587,21.95801],[85.38986,22.15783],[85.22952,22.00003],[85.01426,22.10886],[85.10662,22.318],[85.05958,22.48083],[84.4876,22.40785],[84.27955,22.32467],[83.99425,22.53602],[84.04129,22.46005],[84.00421,22.37261],[83.65436,22.22967],[83.54347,22.05159],[83.58844,21.84078],[83.48167,21.81242],[83.46828,21.72569],[83.3385,21.50226],[83.40236,21.33958],[83.27499,21.3722],[83.1792,21.11044],[82.67555,21.16008],[82.4517,20.82608],[82.34252,20.85688],[82.39437,20.05302],[82.70439,20.00141],[82.72258,19.85036],[82.58285,19.76444],[82.58628,19.86263],[82.45513,19.91461],[82.34939,19.83776],[81.93809,20.09527],[81.82617,19.92558],[82.06375,19.78221],[82.2512,18.91473],[82.17945,18.90401],[82.16468,18.79094],[81.90101,18.64266],[81.84402,18.5714],[81.79664,18.477],[81.69158,18.42196],[81.706,18.39916],[81.61468,18.31052],[81.54275,18.26864],[81.50722,18.19217],[81.52679,18.16248],[81.44988,17.89707],[81.40165,17.88727],[81.39221,17.81014]]]}},{"type":"Feature","properties":{"id":"BV"},"geometry":{"type":"Polygon","coordinates":[[[2.85578,-54.70531],[3.87126,-54.71036],[3.87947,-54.15611],[2.86398,-54.15099],[2.85578,-54.70531]]]}},{"type":"Feature","properties":{"id":"NP"},"geometry":{"type":"Polygon","coordinates":[[[80.05743,28.91479],[80.06957,28.82763],[80.12125,28.82346],[80.37188,28.63371],[80.44504,28.63098],[80.52443,28.54897],[80.50575,28.6706],[80.55142,28.69182],[80.89648,28.47237],[81.08507,28.38346],[81.19847,28.36284],[81.32923,28.13521],[81.38683,28.17638],[81.48179,28.12148],[81.47867,28.08303],[81.91223,27.84995],[81.97214,27.93322],[82.06554,27.92222],[82.46405,27.6716],[82.70378,27.72122],[82.74119,27.49838],[82.93261,27.50328],[82.94938,27.46036],[83.19413,27.45632],[83.27197,27.38309],[83.2673,27.36235],[83.29999,27.32778],[83.35136,27.33885],[83.38872,27.39276],[83.39495,27.4798],[83.61288,27.47013],[83.85595,27.35797],[83.86182,27.4241],[83.93306,27.44939],[84.02229,27.43836],[84.10791,27.52399],[84.21376,27.45218],[84.25735,27.44941],[84.29315,27.39],[84.62161,27.33885],[84.69166,27.21294],[84.64496,27.04669],[84.793,26.9968],[84.82913,27.01989],[84.85754,26.98984],[84.96687,26.95599],[84.97186,26.9149],[85.00536,26.89523],[85.05592,26.88991],[85.02635,26.85381],[85.15883,26.86966],[85.19291,26.86909],[85.18046,26.80519],[85.21159,26.75933],[85.34302,26.74954],[85.47752,26.79292],[85.56471,26.84133],[85.5757,26.85955],[85.59461,26.85161],[85.61621,26.86721],[85.66239,26.84822],[85.73483,26.79613],[85.72315,26.67471],[85.76907,26.63076],[85.83126,26.61134],[85.85126,26.60866],[85.8492,26.56667],[86.02729,26.66756],[86.13596,26.60651],[86.22513,26.58863],[86.26235,26.61886],[86.31564,26.61925],[86.49726,26.54218],[86.54258,26.53819],[86.57073,26.49825],[86.61313,26.48658],[86.62686,26.46891],[86.69124,26.45169],[86.74025,26.42386],[86.76797,26.45892],[86.82898,26.43919],[86.94543,26.52076],[86.95912,26.52076],[87.01559,26.53228],[87.04691,26.58685],[87.0707,26.58571],[87.09147,26.45039],[87.14751,26.40542],[87.18863,26.40558],[87.24682,26.4143],[87.26587,26.40592],[87.26568,26.37294],[87.34568,26.34787],[87.37314,26.40815],[87.46566,26.44058],[87.51571,26.43106],[87.55274,26.40596],[87.59175,26.38342],[87.66803,26.40294],[87.67893,26.43501],[87.76004,26.40711],[87.7918,26.46737],[87.84193,26.43663],[87.89085,26.48565],[87.90115,26.44923],[88.00895,26.36029],[88.09414,26.43732],[88.09963,26.54195],[88.16452,26.64111],[88.1659,26.68177],[88.19107,26.75516],[88.12302,26.95324],[88.13422,26.98705],[88.11719,26.98758],[87.9887,27.11045],[88.01587,27.21388],[88.01646,27.21612],[88.07277,27.43007],[88.04008,27.49223],[88.19107,27.79285],[88.1973,27.85067],[88.13378,27.88015],[87.82681,27.95248],[87.72718,27.80938],[87.56996,27.84517],[87.11696,27.84104],[87.03757,27.94835],[86.75582,28.04182],[86.74181,28.10638],[86.56265,28.09569],[86.51609,27.96623],[86.42736,27.91122],[86.22966,27.9786],[86.18607,28.17364],[86.088,28.09264],[86.08333,28.02121],[86.12069,27.93047],[86.06309,27.90021],[85.94946,27.9401],[85.97813,27.99023],[85.90743,28.05144],[85.84672,28.18187],[85.74864,28.23126],[85.71907,28.38064],[85.69105,28.38475],[85.60854,28.25045],[85.59765,28.30529],[85.4233,28.32996],[85.38127,28.28336],[85.10729,28.34092],[85.18668,28.54076],[85.19135,28.62825],[85.06059,28.68562],[84.85511,28.58041],[84.62317,28.73887],[84.47528,28.74023],[84.2231,28.89571],[84.24801,29.02783],[84.18107,29.23451],[83.97559,29.33091],[83.82303,29.30513],[83.63156,29.16249],[83.44787,29.30513],[83.28131,29.56813],[83.07116,29.61957],[82.73024,29.81695],[82.5341,29.9735],[82.38622,30.02608],[82.16984,30.0692],[82.19475,30.16884],[82.10757,30.23745],[82.10135,30.35439],[81.99082,30.33423],[81.62033,30.44703],[81.41018,30.42153],[81.39928,30.21862],[81.33355,30.15303],[81.2623,30.14596],[81.29032,30.08806],[81.24362,30.0126],[81.12842,30.01395],[81.03953,30.20059],[80.93695,30.18229],[80.8778,30.13384],[80.67076,29.95732],[80.60226,29.95732],[80.56957,29.88176],[80.56247,29.86661],[80.48997,29.79566],[80.43458,29.80466],[80.41554,29.79451],[80.36803,29.73865],[80.38428,29.68513],[80.41858,29.63581],[80.37939,29.57098],[80.24322,29.44299],[80.31428,29.30784],[80.28626,29.20327],[80.24112,29.21414],[80.26602,29.13938],[80.23178,29.11626],[80.18085,29.13649],[80.05743,28.91479]]]}},{"type":"Feature","properties":{"id":"NR"},"geometry":{"type":"Polygon","coordinates":[[[166.65,-0.8],[167.2,-0.8],[167.2,-0.26],[166.65,-0.26],[166.65,-0.8]]]}},{"type":"Feature","properties":{"id":"PK"},"geometry":{"type":"Polygon","coordinates":[[[60.87231,29.86514],[61.31508,29.38903],[61.53765,29.00507],[61.65978,28.77937],[61.93581,28.55284],[62.40259,28.42703],[62.59499,28.24842],[62.79412,28.28108],[62.7638,28.02992],[62.84905,27.47627],[62.79684,27.34381],[62.80604,27.22412],[63.19649,27.25674],[63.32283,27.14437],[63.25005,27.08692],[63.25005,26.84212],[63.18688,26.83844],[63.1889,26.65072],[62.77352,26.64099],[62.31484,26.528],[62.21304,26.26601],[62.05117,26.31647],[61.89391,26.26251],[61.83831,26.07249],[61.83968,25.7538],[61.683,25.66638],[61.6433,25.27541],[61.57592,25.0492],[61.5251,24.57287],[68.11329,23.53945],[68.20763,23.85849],[68.39339,23.96838],[68.74643,23.97027],[68.7416,24.31904],[68.90914,24.33156],[68.97781,24.26021],[69.07806,24.29777],[69.19341,24.25646],[69.29778,24.28712],[69.59579,24.29777],[69.73335,24.17007],[70.03428,24.172],[70.11712,24.30915],[70.5667,24.43787],[70.57906,24.27774],[70.71502,24.23517],[70.88393,24.27398],[70.85784,24.30903],[70.94985,24.3791],[71.04461,24.34657],[71.12838,24.42662],[71.00341,24.46038],[70.97594,24.60904],[71.09405,24.69017],[70.94002,24.92843],[70.89148,25.15064],[70.66695,25.39314],[70.67382,25.68186],[70.60378,25.71898],[70.53649,25.68928],[70.37444,25.67443],[70.2687,25.71156],[70.0985,25.93238],[70.08193,26.08094],[70.17532,26.24118],[70.17532,26.55362],[70.05584,26.60398],[69.88555,26.56836],[69.50904,26.74892],[69.58519,27.18109],[70.03136,27.56627],[70.12502,27.8057],[70.37307,28.01208],[70.60927,28.02178],[70.79054,27.68423],[71.89921,27.96035],[71.9244,28.11555],[72.20329,28.3869],[72.29495,28.66367],[72.40402,28.78283],[72.94272,29.02487],[73.01337,29.16422],[73.05886,29.1878],[73.28094,29.56646],[73.3962,29.94707],[73.58665,30.01848],[73.80299,30.06969],[73.97225,30.19829],[73.95736,30.28466],[73.88993,30.36305],[74.5616,31.04153],[74.67971,31.05479],[74.6852,31.12771],[74.60006,31.13711],[74.60281,31.10419],[74.56023,31.08303],[74.51629,31.13829],[74.53223,31.30321],[74.59773,31.4136],[74.64713,31.45605],[74.59319,31.50197],[74.61517,31.55698],[74.57498,31.60382],[74.47771,31.72227],[74.58907,31.87824],[74.79919,31.95983],[74.86236,32.04485],[74.9269,32.0658],[75.00793,32.03786],[75.25649,32.10187],[75.38046,32.26836],[75.28259,32.36556],[75.03265,32.49538],[74.97634,32.45367],[74.84725,32.49075],[74.68362,32.49298],[74.67431,32.56676],[74.65251,32.56416],[74.64424,32.60985],[74.69542,32.66792],[74.65345,32.71225],[74.7113,32.84219],[74.64675,32.82604],[74.6289,32.75561],[74.45312,32.77755],[74.41467,32.90563],[74.31227,32.92795],[74.34875,32.97823],[74.31854,33.02891],[74.17571,33.07495],[74.15374,33.13477],[74.02144,33.18908],[74.01366,33.25199],[74.08782,33.26232],[74.17983,33.3679],[74.18121,33.4745],[74.10115,33.56392],[74.03576,33.56718],[73.97367,33.64061],[73.98968,33.66155],[73.96423,33.73071],[74.00891,33.75437],[74.05898,33.82089],[74.14001,33.83002],[74.26086,33.92237],[74.25262,34.01577],[74.21554,34.03853],[73.91341,34.01235],[73.88732,34.05105],[73.90677,34.10504],[73.98208,34.2522],[73.90517,34.35317],[73.8475,34.32935],[73.74862,34.34183],[73.74999,34.3781],[73.88732,34.48911],[73.89419,34.54568],[73.93951,34.57169],[73.93401,34.63386],[73.96423,34.68244],[74.12897,34.70073],[74.31239,34.79626],[74.58083,34.77386],[74.6663,34.703],[75.01479,34.64629],[75.38009,34.55021],[75.75438,34.51827],[76.04614,34.67566],[76.15463,34.6429],[76.47186,34.78965],[76.67648,34.76371],[76.74377,34.84039],[76.74514,34.92488],[76.87193,34.96906],[76.99251,34.93349],[77.11796,35.05419],[76.93465,35.39866],[76.85088,35.39754],[76.75475,35.52617],[76.77323,35.66062],[76.50961,35.8908],[76.33453,35.84296],[76.14913,35.82848],[76.15325,35.9264],[75.93028,36.13136],[76.00906,36.17511],[76.0324,36.41198],[75.92391,36.56986],[75.72737,36.7529],[75.45562,36.71971],[75.40481,36.95382],[75.13839,37.02622],[74.56453,37.03023],[74.53739,36.96224],[74.43389,37.00977],[74.04856,36.82648],[73.82685,36.91421],[72.6323,36.84601],[72.18135,36.71838],[71.80267,36.49924],[71.60491,36.39429],[71.19505,36.04134],[71.37969,35.95865],[71.55273,35.71483],[71.49917,35.6267],[71.65435,35.4479],[71.54294,35.31037],[71.5541,35.28776],[71.67495,35.21262],[71.52938,35.09023],[71.55273,35.02615],[71.49917,35.00478],[71.50329,34.97328],[71.29472,34.87728],[71.28356,34.80882],[71.08718,34.69034],[71.11602,34.63047],[71.0089,34.54568],[71.02401,34.44835],[71.17662,34.36769],[71.12815,34.26619],[71.13078,34.16503],[71.09453,34.13524],[71.09307,34.11961],[71.06933,34.10564],[71.07345,34.06242],[70.88119,33.97933],[70.54336,33.9463],[69.90203,34.04194],[69.87307,33.9689],[69.85671,33.93719],[70.00503,33.73528],[70.14236,33.71701],[70.14785,33.6553],[70.20141,33.64387],[70.17062,33.53535],[70.32775,33.34496],[70.13686,33.21064],[70.07369,33.22557],[70.02563,33.14282],[69.85259,33.09451],[69.79766,33.13247],[69.71526,33.09911],[69.57656,33.09911],[69.49004,33.01509],[69.49854,32.88843],[69.5436,32.8768],[69.47082,32.85834],[69.38018,32.76601],[69.43649,32.7302],[69.44747,32.6678],[69.38155,32.56601],[69.2868,32.53938],[69.23599,32.45946],[69.27932,32.29119],[69.27032,32.14141],[69.3225,31.93186],[69.20577,31.85957],[69.11514,31.70782],[69.00939,31.62249],[68.95995,31.64822],[68.91078,31.59687],[68.79997,31.61665],[68.6956,31.75687],[68.57475,31.83158],[68.44222,31.76446],[68.27605,31.75863],[68.25614,31.80357],[68.1655,31.82691],[68.00071,31.6564],[67.86887,31.63536],[67.72056,31.52304],[67.58323,31.52772],[67.62374,31.40473],[67.7748,31.4188],[67.78854,31.33203],[67.29964,31.19586],[67.03323,31.24519],[67.04147,31.31561],[66.83273,31.26867],[66.72561,31.20526],[66.68166,31.07597],[66.58175,30.97532],[66.42645,30.95309],[66.39194,30.9408],[66.28413,30.57001],[66.34869,30.404],[66.23609,30.06321],[66.36042,29.9583],[66.24175,29.85181],[65.04005,29.53957],[64.62116,29.58903],[64.19796,29.50407],[64.12966,29.39157],[63.5876,29.50456],[62.47751,29.40782],[60.87231,29.86514]]]}},{"type":"Feature","properties":{"id":"IN-JH"},"geometry":{"type":"Polygon","coordinates":[[[83.32134,24.10131],[83.4185,24.08596],[83.55342,23.8783],[83.69487,23.82209],[83.71564,23.68587],[83.78929,23.58853],[84.02652,23.63068],[83.97039,23.37424],[84.06978,23.33059],[84.02961,23.16592],[84.14909,22.97546],[84.3695,22.97704],[84.40177,22.89325],[83.99425,22.53602],[84.27955,22.32467],[84.4876,22.40785],[85.05958,22.48083],[85.10662,22.318],[85.01426,22.10886],[85.22952,22.00003],[85.38986,22.15783],[85.90587,21.95801],[86.01779,22.30434],[85.96115,22.48242],[86.03324,22.56012],[86.7247,22.21569],[86.88812,22.24715],[86.78031,22.56487],[86.42188,22.77586],[86.5472,22.97641],[86.21761,22.98747],[86.04423,23.14572],[85.92269,23.12236],[85.82279,23.26784],[85.89969,23.37062],[85.8633,23.41316],[85.87223,23.47489],[86.04389,23.48245],[86.0123,23.56619],[86.14311,23.56619],[86.14208,23.47647],[86.31374,23.42544],[86.44214,23.62816],[86.5242,23.62628],[86.59286,23.67156],[86.7374,23.68194],[86.79508,23.68351],[86.8198,23.76115],[86.79748,23.82806],[86.89704,23.89054],[87.08312,23.80639],[87.27933,23.87516],[87.23281,24.04238],[87.4443,23.97527],[87.71038,24.14048],[87.63587,24.21628],[87.7811,24.34928],[87.77904,24.57147],[87.89543,24.57616],[87.83431,24.7381],[87.95173,24.97174],[87.78831,25.15212],[87.86521,25.27139],[87.65373,25.28009],[87.58369,25.35271],[87.53906,25.27947],[87.48962,25.29933],[87.47348,25.19686],[87.32688,25.22016],[87.28946,25.09741],[87.21668,25.09243],[87.1511,25.02246],[87.15866,24.89391],[87.10235,24.84812],[87.05532,24.61237],[86.60316,24.60457],[86.45004,24.36586],[86.28833,24.46027],[86.32163,24.5824],[86.12491,24.61143],[86.12869,24.71876],[85.7373,24.8154],[85.66108,24.61518],[84.89307,24.36304],[84.81994,24.529],[84.57447,24.41151],[84.49756,24.28546],[83.9994,24.63141],[83.94584,24.54899],[83.75015,24.50245],[83.49883,24.52651],[83.39275,24.50027],[83.46347,24.36992],[83.37387,24.3155],[83.4027,24.26793],[83.32134,24.10131]]]}},{"type":"Feature","properties":{"id":"PA"},"geometry":{"type":"Polygon","coordinates":[[[-83.05209,8.33394],[-82.9388,8.26634],[-82.88641,8.10219],[-82.89137,8.05755],[-82.89978,8.04083],[-82.94503,7.93865],[-82.13751,6.97312],[-78.06168,7.07793],[-77.89178,7.22681],[-77.81426,7.48319],[-77.72157,7.47612],[-77.72514,7.72348],[-77.57185,7.51147],[-77.17257,7.97422],[-77.45064,8.49991],[-77.32389,8.81247],[-77.58292,9.22278],[-78.79327,9.93766],[-82.51044,9.65379],[-82.56507,9.57279],[-82.61345,9.49881],[-82.66667,9.49746],[-82.77206,9.59573],[-82.87919,9.62645],[-82.84871,9.4973],[-82.93516,9.46741],[-82.93516,9.07687],[-82.72126,8.97125],[-82.88253,8.83331],[-82.91377,8.774],[-82.92068,8.74832],[-82.8794,8.6981],[-82.82739,8.60153],[-82.83975,8.54755],[-82.83322,8.52464],[-82.8382,8.48117],[-82.8679,8.44042],[-82.93056,8.43465],[-83.05209,8.33394]]]}},{"type":"Feature","properties":{"id":"PE"},"geometry":{"type":"Polygon","coordinates":[[[-84.52388,-3.36941],[-84.46057,-7.80762],[-78.15039,-17.99635],[-73.98689,-20.10822],[-70.59118,-18.35072],[-70.378,-18.3495],[-70.31267,-18.31258],[-70.16394,-18.31737],[-69.96732,-18.25992],[-69.81607,-18.12582],[-69.75305,-17.94605],[-69.82868,-17.72048],[-69.79087,-17.65563],[-69.66483,-17.65083],[-69.46897,-17.4988],[-69.46863,-17.37466],[-69.62883,-17.28142],[-69.16896,-16.72233],[-69.00853,-16.66769],[-69.04027,-16.57214],[-68.98358,-16.42165],[-68.79464,-16.33272],[-68.96238,-16.194],[-69.09986,-16.22693],[-69.20291,-16.16668],[-69.40336,-15.61358],[-69.14856,-15.23478],[-69.36254,-14.94634],[-68.88135,-14.18639],[-69.05265,-13.68546],[-68.8864,-13.40792],[-68.85615,-12.87769],[-68.65044,-12.50689],[-68.98115,-11.8979],[-69.57156,-10.94555],[-69.57835,-10.94051],[-69.90896,-10.92744],[-70.38791,-11.07096],[-70.51395,-10.92249],[-70.64134,-11.0108],[-70.62487,-9.80666],[-70.55429,-9.76692],[-70.58453,-9.58303],[-70.53373,-9.42628],[-71.23394,-9.9668],[-72.14742,-9.98049],[-72.31883,-9.5184],[-72.72216,-9.41397],[-73.21498,-9.40904],[-72.92886,-9.04074],[-73.76576,-7.89884],[-73.65485,-7.77897],[-73.96938,-7.58465],[-73.77011,-7.28944],[-73.73986,-6.87919],[-73.12983,-6.43852],[-73.24579,-6.05764],[-72.83973,-5.14765],[-72.64391,-5.0391],[-71.87003,-4.51661],[-70.96814,-4.36915],[-70.77601,-4.15717],[-70.33236,-4.15214],[-70.19582,-4.3607],[-70.11305,-4.27281],[-70.00888,-4.37833],[-69.94708,-4.2431],[-70.3374,-3.79505],[-70.52393,-3.87553],[-70.71396,-3.7921],[-70.04609,-2.73906],[-70.94377,-2.23142],[-71.75223,-2.15058],[-72.92587,-2.44514],[-73.65312,-1.26222],[-74.26675,-0.97229],[-74.42701,-0.50218],[-75.18513,-0.0308],[-75.25764,-0.11943],[-75.40192,-0.17196],[-75.61997,-0.10012],[-75.60169,-0.18708],[-75.53615,-0.19213],[-75.22862,-0.60048],[-75.22862,-0.95588],[-75.3872,-0.9374],[-75.57429,-1.55961],[-76.05203,-2.12179],[-76.6324,-2.58397],[-77.94147,-3.05454],[-78.19369,-3.36431],[-78.14324,-3.47653],[-78.22642,-3.51113],[-78.24589,-3.39907],[-78.34362,-3.38633],[-78.68394,-4.60754],[-78.85149,-4.66795],[-79.01659,-5.01481],[-79.1162,-4.97774],[-79.26248,-4.95167],[-79.59402,-4.46848],[-79.79722,-4.47558],[-80.13945,-4.29786],[-80.39256,-4.48269],[-80.46386,-4.41516],[-80.32114,-4.21323],[-80.45023,-4.20938],[-80.4822,-4.05477],[-80.46386,-4.01342],[-80.13232,-3.90317],[-80.19926,-3.68894],[-80.18741,-3.63994],[-80.19848,-3.59249],[-80.21642,-3.5888],[-80.20535,-3.51667],[-80.22629,-3.501],[-80.23651,-3.48652],[-80.24586,-3.48677],[-80.24475,-3.47846],[-80.24123,-3.46124],[-80.20647,-3.431],[-80.30602,-3.39149],[-84.52388,-3.36941]]]}},{"type":"Feature","properties":{"id":"PG"},"geometry":{"type":"Polygon","coordinates":[[[140.85295,-6.72996],[141.01763,-6.90181],[141.00782,-9.1242],[140.88922,-9.34945],[142.0601,-9.56571],[142.0953,-9.23534],[142.1462,-9.19923],[142.23304,-9.19253],[142.31447,-9.24611],[142.5723,-9.35994],[142.81927,-9.31709],[144.30183,-9.48146],[155.22803,-12.9001],[154.74815,-7.33315],[155.60735,-6.92266],[155.69784,-6.92661],[155.92557,-6.84664],[156.03993,-6.65703],[156.03296,-6.55528],[157.60997,-5.69776],[156.88247,-1.39237],[141.00167,0.68547],[140.99813,-6.3233],[140.85295,-6.72996]]]}},{"type":"Feature","properties":{"id":"PL"},"geometry":{"type":"Polygon","coordinates":[[[14.12256,52.84311],[14.13806,52.82392],[14.22071,52.81175],[14.61073,52.59847],[14.6289,52.57136],[14.60081,52.53116],[14.63056,52.48993],[14.54423,52.42568],[14.55228,52.35264],[14.56378,52.33838],[14.58149,52.28007],[14.70139,52.25038],[14.71319,52.22144],[14.68344,52.19612],[14.70616,52.16927],[14.67683,52.13936],[14.6917,52.10283],[14.72971,52.09167],[14.76026,52.06624],[14.71339,52.00337],[14.70488,51.97679],[14.7139,51.95643],[14.71836,51.95606],[14.72163,51.95188],[14.7177,51.94048],[14.70601,51.92944],[14.6933,51.9044],[14.6588,51.88359],[14.59089,51.83302],[14.60493,51.80473],[14.64625,51.79472],[14.66386,51.73282],[14.69065,51.70842],[14.75392,51.67445],[14.75759,51.62318],[14.7727,51.61263],[14.71125,51.56209],[14.73047,51.54606],[14.72652,51.53902],[14.73219,51.52922],[14.94749,51.47155],[14.9652,51.44793],[14.96899,51.38367],[14.98008,51.33449],[15.04288,51.28387],[15.01242,51.21285],[15.0047,51.16874],[14.99311,51.16249],[14.99414,51.15813],[15.00083,51.14974],[14.99646,51.14365],[14.99079,51.14284],[14.99689,51.12205],[14.98229,51.11354],[14.97938,51.07742],[14.95529,51.04552],[14.92942,50.99744],[14.89252,50.94999],[14.89681,50.9422],[14.81664,50.88148],[14.82803,50.86966],[14.99852,50.86817],[15.01088,50.97984],[14.96419,50.99108],[15.02433,51.0242],[15.03895,51.0123],[15.06218,51.02269],[15.10152,51.01095],[15.11937,50.99021],[15.16744,51.01959],[15.1743,50.9833],[15.2361,50.99886],[15.27043,50.97724],[15.2773,50.8907],[15.36656,50.83956],[15.3803,50.77187],[15.43798,50.80833],[15.73186,50.73885],[15.81683,50.75666],[15.87331,50.67188],[15.97219,50.69799],[16.0175,50.63009],[15.98317,50.61528],[16.02437,50.60046],[16.10265,50.66405],[16.20839,50.63096],[16.23174,50.67101],[16.33611,50.66579],[16.44597,50.58041],[16.34572,50.49575],[16.31413,50.50274],[16.19526,50.43291],[16.21585,50.40627],[16.22821,50.41054],[16.28118,50.36891],[16.30289,50.38292],[16.36495,50.37679],[16.3622,50.34875],[16.39379,50.3207],[16.42674,50.32509],[16.56407,50.21009],[16.55446,50.16613],[16.63137,50.1142],[16.7014,50.09659],[16.8456,50.20834],[16.98018,50.24172],[17.00353,50.21449],[17.02825,50.23118],[16.99803,50.25753],[17.02138,50.27772],[16.99803,50.30316],[16.94448,50.31281],[16.90877,50.38642],[16.85933,50.41093],[16.89229,50.45117],[17.1224,50.39494],[17.14498,50.38117],[17.19579,50.38817],[17.19991,50.3654],[17.27681,50.32246],[17.34273,50.32947],[17.34548,50.2628],[17.3702,50.28123],[17.58889,50.27837],[17.67764,50.28977],[17.69292,50.32859],[17.74648,50.29966],[17.72176,50.25665],[17.76296,50.23382],[17.70528,50.18812],[17.59404,50.16437],[17.66683,50.10275],[17.6888,50.12037],[17.7506,50.07896],[17.77669,50.02253],[17.86886,49.97452],[18.00191,50.01723],[18.04585,50.01194],[18.04585,50.03311],[18.00396,50.04954],[18.03212,50.06574],[18.07898,50.04535],[18.10628,50.00223],[18.20241,49.99958],[18.21752,49.97309],[18.27107,49.96779],[18.27794,49.93863],[18.31914,49.91565],[18.33278,49.92415],[18.33562,49.94747],[18.41604,49.93498],[18.53423,49.89906],[18.54495,49.9079],[18.54299,49.92537],[18.57697,49.91565],[18.57045,49.87849],[18.60341,49.86256],[18.57183,49.83334],[18.61278,49.7618],[18.61368,49.75426],[18.62645,49.75002],[18.62943,49.74603],[18.62676,49.71983],[18.69817,49.70473],[18.72838,49.68163],[18.80479,49.6815],[18.84786,49.5446],[18.84521,49.51672],[18.94536,49.52143],[18.97283,49.49914],[18.9742,49.39557],[19.18019,49.41165],[19.25435,49.53391],[19.36009,49.53747],[19.37795,49.574],[19.45348,49.61583],[19.52626,49.57311],[19.53313,49.52856],[19.57845,49.46077],[19.64162,49.45184],[19.6375,49.40897],[19.72127,49.39288],[19.78581,49.41701],[19.82237,49.27806],[19.75286,49.20751],[19.86409,49.19316],[19.90529,49.23532],[19.98494,49.22904],[20.08238,49.1813],[20.13738,49.31685],[20.21977,49.35265],[20.31453,49.34817],[20.31728,49.39914],[20.39939,49.3896],[20.46422,49.41612],[20.5631,49.375],[20.61666,49.41791],[20.72274,49.41813],[20.77971,49.35383],[20.9229,49.29626],[20.98733,49.30774],[21.09799,49.37176],[21.041,49.41791],[21.12477,49.43666],[21.19756,49.4054],[21.27858,49.45988],[21.43376,49.41433],[21.62328,49.4447],[21.77983,49.35443],[21.82927,49.39467],[21.96385,49.3437],[22.04427,49.22136],[22.56155,49.08865],[22.89122,49.00725],[22.86336,49.10513],[22.72009,49.20288],[22.748,49.32759],[22.69444,49.49378],[22.64534,49.53094],[22.78304,49.65543],[22.80261,49.69098],[22.83179,49.69875],[22.99329,49.84249],[23.28221,50.0957],[23.67635,50.33385],[23.71382,50.38248],[23.79445,50.40481],[23.99563,50.41289],[24.03668,50.44507],[24.07048,50.5071],[24.0996,50.60752],[24.0595,50.71625],[23.95925,50.79271],[23.99254,50.83847],[24.0952,50.83262],[24.14524,50.86128],[24.04576,50.90196],[23.92217,51.00836],[23.90376,51.07697],[23.80678,51.18405],[23.63858,51.32182],[23.69905,51.40871],[23.62751,51.50512],[23.56236,51.53673],[23.57053,51.55938],[23.53198,51.74298],[23.62691,51.78208],[23.61523,51.92066],[23.68733,51.9906],[23.64066,52.07626],[23.61,52.11264],[23.54314,52.12148],[23.47859,52.18215],[23.20071,52.22848],[23.18196,52.28812],[23.34141,52.44845],[23.45112,52.53774],[23.58296,52.59868],[23.73615,52.6149],[23.93763,52.71332],[23.91805,52.94016],[23.94689,52.95919],[23.92184,53.02079],[23.87548,53.0831],[23.91393,53.16469],[23.85657,53.22923],[23.81995,53.24131],[23.62004,53.60942],[23.51284,53.95052],[23.48261,53.98855],[23.52702,54.04622],[23.49196,54.14764],[23.45223,54.17775],[23.42418,54.17911],[23.39525,54.21672],[23.3494,54.25155],[23.24656,54.25701],[23.15938,54.29894],[23.15526,54.31076],[23.13905,54.31567],[23.104,54.29794],[23.04323,54.31567],[23.05726,54.34565],[22.99649,54.35927],[23.00584,54.38514],[22.83756,54.40827],[22.79705,54.36264],[21.41123,54.32395],[20.63871,54.3706],[19.8038,54.44203],[19.64312,54.45423],[18.57853,55.25302],[14.20811,54.12784],[14.22634,53.9291],[14.20647,53.91671],[14.18544,53.91258],[14.20823,53.90776],[14.21323,53.8664],[14.27249,53.74464],[14.26782,53.69866],[14.2836,53.67721],[14.27133,53.66613],[14.28477,53.65955],[14.2853,53.63392],[14.31904,53.61581],[14.30416,53.55499],[14.3273,53.50587],[14.35209,53.49506],[14.4215,53.27724],[14.44133,53.27427],[14.45125,53.26241],[14.40662,53.21098],[14.37853,53.20405],[14.36696,53.16444],[14.38679,53.13669],[14.35044,53.05829],[14.25954,53.00264],[14.14056,52.95786],[14.15873,52.87715],[14.12256,52.84311]]]}},{"type":"Feature","properties":{"id":"KP"},"geometry":{"type":"Polygon","coordinates":[[[123.85601,37.49093],[124.67666,38.05679],[124.84224,37.977],[124.87921,37.80827],[125.06408,37.66334],[125.37112,37.62643],[125.81159,37.72949],[126.13074,37.70512],[126.18776,37.74728],[126.19097,37.81462],[126.24402,37.83113],[126.43239,37.84095],[126.46818,37.80873],[126.56709,37.76857],[126.59918,37.76364],[126.66067,37.7897],[126.68793,37.83728],[126.68793,37.9175],[126.67023,37.95852],[126.84961,38.0344],[126.88106,38.10246],[126.95887,38.1347],[126.95338,38.17735],[127.04479,38.25518],[127.15749,38.30722],[127.38727,38.33227],[127.49672,38.30647],[127.55013,38.32257],[128.02917,38.31861],[128.27652,38.41657],[128.31105,38.58462],[128.37487,38.62345],[128.65655,38.61914],[131.95041,41.5445],[130.65022,42.32281],[130.66367,42.38024],[130.64181,42.41422],[130.60805,42.4317],[130.56835,42.43281],[130.55143,42.52158],[130.50123,42.61636],[130.44361,42.54849],[130.41826,42.6011],[130.2385,42.71127],[130.23068,42.80125],[130.26095,42.9027],[130.09764,42.91425],[130.12957,42.98361],[129.96409,42.97306],[129.95082,43.01051],[129.8865,43.00395],[129.85261,42.96494],[129.83277,42.86746],[129.80719,42.79218],[129.7835,42.76521],[129.77183,42.69435],[129.75294,42.59409],[129.72541,42.43739],[129.60482,42.44461],[129.54701,42.37254],[129.42882,42.44702],[129.28541,42.41574],[129.22423,42.3553],[129.22285,42.26491],[129.15178,42.17224],[128.96068,42.06657],[128.94007,42.03537],[128.04487,42.01769],[128.15119,41.74568],[128.30716,41.60322],[128.20061,41.40895],[128.18546,41.41279],[128.12967,41.37931],[128.03311,41.39232],[128.02633,41.42103],[127.92943,41.44291],[127.29712,41.49473],[127.17841,41.59714],[126.90729,41.79955],[126.60631,41.65565],[126.53189,41.35206],[126.242,41.15454],[126.00335,40.92835],[125.76869,40.87908],[125.71172,40.85223],[124.86913,40.45387],[124.40719,40.13655],[124.38556,40.11047],[124.3322,40.05573],[124.37089,40.03004],[124.35029,39.95639],[124.23201,39.9248],[124.17532,39.8232],[123.90497,38.79949],[123.85601,37.49093]]]}},{"type":"Feature","properties":{"id":"PT"},"geometry":{"type":"Polygon","coordinates":[[[-32.42346,39.07068],[-15.92339,29.50503],[-14.33337,30.94071],[-7.37282,36.96896],[-7.39769,37.16868],[-7.41133,37.20314],[-7.41854,37.23813],[-7.43227,37.25152],[-7.43974,37.38913],[-7.46878,37.47127],[-7.51759,37.56119],[-7.41981,37.75729],[-7.33441,37.81193],[-7.27314,37.90145],[-7.24544,37.98884],[-7.12648,38.00296],[-7.10366,38.04404],[-7.05966,38.01966],[-7.00375,38.01914],[-6.93418,38.21454],[-7.09389,38.17227],[-7.15581,38.27597],[-7.32529,38.44336],[-7.265,38.61674],[-7.26174,38.72107],[-7.03848,38.87221],[-7.051,38.907],[-6.95211,39.0243],[-6.97004,39.07619],[-7.04011,39.11919],[-7.10692,39.10275],[-7.14929,39.11287],[-7.12811,39.17101],[-7.23566,39.20132],[-7.23403,39.27579],[-7.3149,39.34857],[-7.2927,39.45847],[-7.49477,39.58794],[-7.54121,39.66717],[-7.33507,39.64569],[-7.24707,39.66576],[-7.01613,39.66877],[-6.97492,39.81488],[-6.91463,39.86618],[-6.86737,40.01986],[-6.94233,40.10716],[-7.00589,40.12087],[-7.02544,40.18564],[-7.00426,40.23169],[-6.86085,40.26776],[-6.86085,40.2976],[-6.80218,40.33239],[-6.78426,40.36468],[-6.84618,40.42177],[-6.84944,40.46394],[-6.7973,40.51723],[-6.80218,40.55067],[-6.84292,40.56801],[-6.79567,40.65955],[-6.82826,40.74603],[-6.82337,40.84472],[-6.79892,40.84842],[-6.80707,40.88047],[-6.84292,40.89771],[-6.8527,40.93958],[-6.9357,41.02888],[-6.913,41.03922],[-6.88843,41.03027],[-6.84781,41.02692],[-6.80942,41.03629],[-6.79241,41.05397],[-6.75655,41.10187],[-6.77319,41.13049],[-6.69711,41.1858],[-6.68286,41.21641],[-6.65046,41.24725],[-6.55937,41.24417],[-6.38551,41.35274],[-6.38553,41.38655],[-6.3306,41.37677],[-6.26777,41.48796],[-6.19128,41.57638],[-6.29863,41.66432],[-6.44204,41.68258],[-6.49907,41.65823],[-6.54633,41.68623],[-6.56426,41.74219],[-6.51374,41.8758],[-6.56752,41.88429],[-6.5447,41.94371],[-6.58544,41.96674],[-6.61967,41.94008],[-6.75004,41.94129],[-6.76959,41.98734],[-6.81196,41.99097],[-6.82174,41.94493],[-6.94396,41.94403],[-6.95537,41.96553],[-6.98144,41.9728],[-7.01078,41.94977],[-7.07596,41.94977],[-7.08574,41.97401],[-7.14115,41.98855],[-7.18549,41.97515],[-7.18677,41.88793],[-7.32366,41.8406],[-7.37092,41.85031],[-7.42864,41.80589],[-7.42854,41.83262],[-7.44759,41.84451],[-7.45566,41.86488],[-7.49803,41.87095],[-7.52737,41.83939],[-7.62188,41.83089],[-7.58603,41.87944],[-7.65774,41.88308],[-7.69848,41.90977],[-7.84188,41.88065],[-7.88055,41.84571],[-7.88751,41.92553],[-7.90707,41.92432],[-7.92336,41.8758],[-7.9804,41.87337],[-8.01136,41.83453],[-8.0961,41.81024],[-8.16455,41.81753],[-8.16944,41.87944],[-8.19551,41.87459],[-8.2185,41.91237],[-8.16232,41.9828],[-8.08796,42.01398],[-8.08847,42.05767],[-8.11729,42.08537],[-8.18178,42.06436],[-8.19406,42.12141],[-8.18947,42.13853],[-8.1986,42.15402],[-8.22406,42.1328],[-8.24681,42.13993],[-8.2732,42.12396],[-8.29809,42.106],[-8.32161,42.10218],[-8.33912,42.08358],[-8.36353,42.09065],[-8.38323,42.07683],[-8.40143,42.08052],[-8.42512,42.07199],[-8.44123,42.08218],[-8.48185,42.0811],[-8.52837,42.07658],[-8.5252,42.06264],[-8.54563,42.0537],[-8.58086,42.05147],[-8.59493,42.05708],[-8.63791,42.04691],[-8.64626,42.03668],[-8.65832,42.02972],[-8.6681,41.99703],[-8.69071,41.98862],[-8.7478,41.96282],[-8.74606,41.9469],[-8.75712,41.92833],[-8.81794,41.90375],[-8.87157,41.86488],[-9.14112,41.86623],[-30.18705,41.4962],[-32.42346,39.07068]]]}},{"type":"Feature","properties":{"id":"PY"},"geometry":{"type":"Polygon","coordinates":[[[-62.64455,-22.25091],[-62.51761,-22.37684],[-62.22768,-22.55807],[-61.9756,-23.0507],[-61.0782,-23.62932],[-60.99754,-23.80934],[-60.28163,-24.04436],[-60.03367,-24.00701],[-59.45482,-24.34787],[-59.33886,-24.49935],[-58.33055,-24.97099],[-58.25492,-24.92528],[-57.80821,-25.13863],[-57.57431,-25.47269],[-57.87176,-25.93604],[-58.1188,-26.16704],[-58.3198,-26.83443],[-58.65321,-27.14028],[-58.59549,-27.29973],[-58.04205,-27.2387],[-56.85337,-27.5165],[-56.18313,-27.29851],[-55.89195,-27.3467],[-55.74475,-27.44485],[-55.59094,-27.32444],[-55.62322,-27.1941],[-55.39611,-26.97679],[-55.25243,-26.93808],[-55.16948,-26.96068],[-55.06351,-26.80195],[-55.00584,-26.78754],[-54.80868,-26.55669],[-54.70732,-26.45099],[-54.69333,-26.37705],[-54.67359,-25.98607],[-54.60664,-25.9691],[-54.62063,-25.91213],[-54.59398,-25.59224],[-54.59509,-25.53696],[-54.60196,-25.48397],[-54.62033,-25.46026],[-54.4423,-25.13381],[-54.28207,-24.07305],[-54.32807,-24.01865],[-54.6238,-23.83078],[-55.02691,-23.97317],[-55.0518,-23.98666],[-55.12292,-23.99669],[-55.41784,-23.9657],[-55.44117,-23.9185],[-55.43585,-23.87157],[-55.5555,-23.28237],[-55.52288,-23.2595],[-55.5446,-23.22811],[-55.63849,-22.95122],[-55.62493,-22.62765],[-55.68742,-22.58407],[-55.6986,-22.56268],[-55.72366,-22.5519],[-55.741,-22.52018],[-55.74941,-22.46436],[-55.8331,-22.29008],[-56.23206,-22.25347],[-56.45893,-22.08072],[-56.5212,-22.11556],[-56.6508,-22.28387],[-57.98625,-22.09157],[-57.94642,-21.73799],[-57.88239,-21.6868],[-57.93492,-21.65505],[-57.84536,-20.93155],[-58.16225,-20.16193],[-58.23216,-19.80058],[-59.06965,-19.29148],[-60.00638,-19.2981],[-61.73723,-19.63958],[-61.93912,-20.10053],[-62.26883,-20.55311],[-62.2757,-21.06657],[-62.64455,-22.25091]]]}},{"type":"Feature","properties":{"id":"QA"},"geometry":{"type":"Polygon","coordinates":[[[50.57069,25.57887],[50.8133,24.74049],[50.92992,24.54396],[51.09638,24.46907],[51.29972,24.50747],[51.39468,24.62785],[51.58834,24.66608],[51.83108,24.71675],[51.83682,26.70231],[50.93865,26.30758],[50.81266,25.88946],[50.86149,25.6965],[50.7801,25.595],[50.80824,25.54641],[50.57069,25.57887]]]}},{"type":"Feature","properties":{"id":"RO"},"geometry":{"type":"Polygon","coordinates":[[[20.26068,46.12332],[20.35862,45.99356],[20.54818,45.89939],[20.65645,45.82801],[20.70069,45.7493],[20.77416,45.75601],[20.78446,45.78522],[20.82364,45.77738],[20.80361,45.65875],[20.76798,45.60969],[20.83321,45.53567],[20.77217,45.49788],[20.86026,45.47295],[20.87948,45.42743],[21.09894,45.30144],[21.17612,45.32566],[21.20392,45.2677],[21.29398,45.24148],[21.48278,45.19557],[21.51299,45.15345],[21.4505,45.04294],[21.35855,45.01941],[21.54938,44.9327],[21.56328,44.89502],[21.48202,44.87199],[21.44013,44.87613],[21.35643,44.86364],[21.38802,44.78133],[21.55007,44.77304],[21.60019,44.75208],[21.61942,44.67059],[21.67504,44.67107],[21.71692,44.65349],[21.7795,44.66165],[21.99364,44.63395],[22.08016,44.49844],[22.13234,44.47444],[22.18315,44.48179],[22.30844,44.6619],[22.45301,44.7194],[22.61917,44.61489],[22.69196,44.61587],[22.76749,44.54446],[22.70981,44.51852],[22.61368,44.55719],[22.56493,44.53419],[22.54021,44.47836],[22.45436,44.47258],[22.56012,44.30712],[22.68166,44.28206],[22.67173,44.21564],[23.04988,44.07694],[23.01674,44.01946],[22.87873,43.9844],[22.83753,43.88055],[22.85314,43.84452],[23.05288,43.79494],[23.26772,43.84843],[23.4507,43.84936],[23.61687,43.79289],[23.73978,43.80627],[24.18149,43.68218],[24.35364,43.70211],[24.50264,43.76314],[24.62281,43.74082],[24.73542,43.68523],[24.96682,43.72693],[25.10718,43.6831],[25.17144,43.70261],[25.39528,43.61866],[25.72792,43.69263],[25.94911,43.85745],[26.05584,43.90925],[26.10115,43.96908],[26.38764,44.04356],[26.62712,44.05698],[26.95141,44.13555],[27.26845,44.12602],[27.39757,44.0141],[27.60834,44.01206],[27.64542,44.04958],[27.73468,43.95326],[27.92008,44.00761],[27.99558,43.84193],[28.23293,43.76],[29.24336,43.70874],[30.04414,45.08461],[29.69272,45.19227],[29.65428,45.25629],[29.68175,45.26885],[29.59798,45.38857],[29.42632,45.44545],[29.24779,45.43388],[28.96077,45.33164],[28.94292,45.28045],[28.81383,45.3384],[28.78911,45.24179],[28.71358,45.22631],[28.5735,45.24759],[28.34554,45.32102],[28.28504,45.43907],[28.21139,45.46895],[28.18741,45.47358],[28.08927,45.6051],[28.16568,45.6421],[28.13111,45.92819],[28.08612,46.01105],[28.13684,46.18099],[28.10937,46.22852],[28.19864,46.31869],[28.18902,46.35283],[28.25769,46.43334],[28.22281,46.50481],[28.24808,46.64305],[28.12173,46.82283],[28.09095,46.97621],[27.81892,47.1381],[27.73172,47.29248],[27.68706,47.28962],[27.60263,47.32507],[27.55731,47.46637],[27.47942,47.48113],[27.3979,47.59473],[27.32202,47.64009],[27.25519,47.71366],[27.29069,47.73722],[27.1618,47.92391],[27.15622,47.98538],[27.02985,48.09083],[27.04118,48.12522],[26.96119,48.13003],[26.98042,48.15752],[26.94265,48.1969],[26.87708,48.19919],[26.81161,48.25049],[26.62823,48.25804],[26.55202,48.22445],[26.33504,48.18418],[26.17711,47.99246],[26.05901,47.9897],[25.77723,47.93919],[25.63878,47.94924],[25.23778,47.89403],[25.11144,47.75203],[24.88896,47.7234],[24.81893,47.82031],[24.70632,47.84428],[24.61994,47.95062],[24.43578,47.97131],[24.34926,47.9244],[24.22566,47.90231],[24.11281,47.91487],[24.06466,47.95317],[24.02999,47.95087],[24.00801,47.968],[23.98553,47.96076],[23.96337,47.96672],[23.94192,47.94868],[23.89352,47.94512],[23.8602,47.9329],[23.80904,47.98142],[23.75188,47.99705],[23.66262,47.98786],[23.63894,48.00293],[23.5653,48.00499],[23.52803,48.01818],[23.4979,47.96858],[23.33577,48.0237],[23.27397,48.08245],[23.15999,48.12188],[23.1133,48.08061],[23.08858,48.00716],[23.0158,47.99338],[22.92241,48.02002],[22.94301,47.96672],[22.89849,47.95851],[22.77991,47.87211],[22.76617,47.8417],[22.67247,47.7871],[22.46559,47.76583],[22.41979,47.7391],[22.31816,47.76126],[22.00917,47.50492],[22.03389,47.42508],[22.01055,47.37767],[21.94463,47.38046],[21.78395,47.11104],[21.648,47.03902],[21.68645,46.99595],[21.59581,46.91628],[21.59307,46.86935],[21.52028,46.84118],[21.48935,46.7577],[21.5151,46.72147],[21.43926,46.65109],[21.33214,46.63035],[21.26929,46.4993],[21.28061,46.44941],[21.16872,46.30118],[21.06572,46.24897],[20.86797,46.28884],[20.74574,46.25467],[20.76085,46.21002],[20.63863,46.12728],[20.49718,46.18721],[20.45377,46.14405],[20.35573,46.16629],[20.28324,46.1438],[20.26068,46.12332]]]}},{"type":"Feature","properties":{"id":"RU-KGD"},"geometry":{"type":"Polygon","coordinates":[[[18.57853,55.25302],[19.64312,54.45423],[19.8038,54.44203],[20.63871,54.3706],[21.41123,54.32395],[22.79705,54.36264],[22.7253,54.41732],[22.70208,54.45312],[22.67788,54.532],[22.71293,54.56454],[22.68021,54.58486],[22.7522,54.63525],[22.74225,54.64339],[22.75467,54.6483],[22.73397,54.66604],[22.73631,54.72952],[22.87317,54.79492],[22.85083,54.88711],[22.76422,54.92521],[22.68723,54.9811],[22.65451,54.97037],[22.60075,55.01863],[22.58907,55.07085],[22.47688,55.04408],[22.31562,55.0655],[22.14267,55.05345],[22.11697,55.02131],[22.06087,55.02935],[22.02582,55.05078],[22.03984,55.07888],[21.99543,55.08691],[21.96505,55.07353],[21.85521,55.09493],[21.64954,55.1791],[21.55605,55.20311],[21.51095,55.18507],[21.46766,55.21115],[21.38446,55.29348],[21.35465,55.28427],[21.26425,55.24456],[20.95181,55.27994],[20.60454,55.40986],[18.57853,55.25302]]]}},{"type":"Feature","properties":{"id":"RW"},"geometry":{"type":"Polygon","coordinates":[[[28.86193,-2.53185],[28.87943,-2.55165],[28.89288,-2.55848],[28.90226,-2.62385],[28.89793,-2.66111],[28.94346,-2.69124],[29.00357,-2.70596],[29.04081,-2.7416],[29.0562,-2.58632],[29.32234,-2.6483],[29.36805,-2.82933],[29.88237,-2.75105],[29.95911,-2.33348],[30.14034,-2.43626],[30.42933,-2.31064],[30.54501,-2.41404],[30.83915,-2.35795],[30.89303,-2.08223],[30.80802,-1.91477],[30.84079,-1.64652],[30.71974,-1.43244],[30.57123,-1.33264],[30.50889,-1.16412],[30.45116,-1.10641],[30.47194,-1.0555],[30.35212,-1.06896],[30.16369,-1.34303],[29.912,-1.48269],[29.82657,-1.31187],[29.59061,-1.39016],[29.53062,-1.40499],[29.45038,-1.5054],[29.36322,-1.50887],[29.24323,-1.66826],[29.24458,-1.69663],[29.11847,-1.90576],[29.17562,-2.12278],[29.105,-2.27043],[29.00051,-2.29001],[28.95642,-2.37321],[28.89601,-2.37321],[28.86826,-2.41888],[28.86846,-2.44866],[28.89132,-2.47557],[28.89342,-2.49017],[28.88846,-2.50493],[28.87497,-2.50887],[28.86209,-2.5231],[28.86193,-2.53185]]]}},{"type":"Feature","properties":{"id":"EH"},"geometry":{"type":"Polygon","coordinates":[[[-17.35589,20.80492],[-17.0471,20.76408],[-17.0695,20.85742],[-17.06781,20.92697],[-17.0396,20.9961],[-17.0357,21.05368],[-16.99806,21.12142],[-16.95474,21.33997],[-13.01525,21.33343],[-13.08438,22.53866],[-13.15313,22.75649],[-13.10753,22.89493],[-13.00412,23.02297],[-12.5741,23.28975],[-12.36213,23.3187],[-12.14969,23.41935],[-12.00251,23.4538],[-12.0002,25.9986],[-8.66721,25.99918],[-8.66674,27.31569],[-8.66879,27.6666],[-8.77527,27.66663],[-8.71787,26.9898],[-9.08698,26.98639],[-9.56957,26.90042],[-9.81998,26.71379],[-10.68417,26.90984],[-11.35695,26.8505],[-11.23622,26.72023],[-11.38635,26.611],[-11.62052,26.05229],[-12.06001,26.04442],[-12.12281,25.13682],[-12.92147,24.39502],[-13.00628,24.01923],[-13.75627,23.77231],[-14.10361,22.75501],[-14.1291,22.41636],[-14.48112,22.00886],[-14.47329,21.63839],[-14.78487,21.36587],[-16.44269,21.39745],[-16.9978,21.36239],[-17.02707,21.34022],[-17.21511,21.34226],[-17.35589,20.80492]]]}},{"type":"Feature","properties":{"id":"SA"},"geometry":{"type":"Polygon","coordinates":[[[34.46254,27.99552],[34.51305,27.70027],[37.8565,22.00903],[39.63762,18.37348],[41.37609,16.19728],[42.15205,16.40211],[42.76801,16.40371],[42.94625,16.39721],[42.94351,16.49467],[42.97215,16.51093],[43.11601,16.53166],[43.15274,16.67248],[43.22066,16.65179],[43.21325,16.74416],[43.25857,16.75304],[43.26303,16.79479],[43.24801,16.80613],[43.22956,16.80613],[43.22012,16.83932],[43.18338,16.84852],[43.1398,16.90696],[43.19328,16.94703],[43.1813,16.98438],[43.18233,17.02673],[43.23967,17.03428],[43.17787,17.14717],[43.20156,17.25901],[43.32653,17.31179],[43.22533,17.38343],[43.29185,17.53224],[43.43005,17.56148],[43.70631,17.35762],[44.50126,17.47475],[46.31018,17.20464],[46.76494,17.29151],[47.00571,16.94765],[47.48245,17.10808],[47.58351,17.50366],[48.19996,18.20584],[49.04884,18.59899],[52.00311,19.00083],[54.99756,20.00083],[55.66469,21.99658],[55.2137,22.71065],[55.13599,22.63334],[52.56622,22.94341],[51.59617,24.12041],[51.58871,24.27256],[51.41644,24.39615],[51.58834,24.66608],[51.39468,24.62785],[51.29972,24.50747],[51.09638,24.46907],[50.92992,24.54396],[50.8133,24.74049],[50.57069,25.57887],[50.302,25.87592],[50.26923,26.08243],[50.38162,26.53976],[50.71771,26.73086],[50.37726,27.89227],[49.98877,27.87827],[49.00421,28.81495],[48.42991,28.53628],[47.70561,28.5221],[47.59863,28.66798],[47.58376,28.83382],[47.46202,29.0014],[46.5527,29.10283],[46.42415,29.05947],[44.72255,29.19736],[42.97796,30.48295],[42.97601,30.72204],[40.01521,32.05667],[39.29903,32.23259],[38.99233,31.99721],[36.99791,31.50081],[37.99354,30.49998],[37.66395,30.33245],[37.4971,29.99949],[36.75083,29.86903],[36.50005,29.49696],[36.07081,29.18469],[34.95987,29.35727],[34.88293,29.37455],[34.46254,27.99552]]]}},{"type":"Feature","properties":{"id":"CN-LN"},"geometry":{"type":"Polygon","coordinates":[[[118.88992,40.9576],[118.90296,40.75297],[119.12406,40.67647],[119.26277,40.53154],[119.55253,40.54876],[119.58137,40.36799],[119.73586,40.11431],[119.98931,39.77661],[120.97044,38.45359],[123.90497,38.79949],[124.17532,39.8232],[124.23201,39.9248],[124.35029,39.95639],[124.37089,40.03004],[124.3322,40.05573],[124.38556,40.11047],[124.40719,40.13655],[124.86913,40.45387],[125.71172,40.85223],[125.66574,40.91403],[125.56892,40.89327],[125.63552,40.95086],[125.78487,41.16185],[125.75225,41.23005],[125.63724,41.26877],[125.63449,41.33325],[125.57544,41.39174],[125.54283,41.39767],[125.49167,41.53222],[125.44738,41.67393],[125.32241,41.67034],[125.28671,41.9554],[125.41854,42.09159],[125.25443,42.31387],[125.18096,42.29813],[124.97909,42.77574],[124.84863,42.79086],[124.85961,43.17563],[124.45793,42.81907],[124.35836,42.88854],[124.41261,43.06738],[124.28832,43.14608],[124.28077,43.21268],[124.14619,43.2412],[123.79806,43.49627],[123.68682,43.3756],[123.55224,42.99862],[123.24737,42.98255],[122.80517,42.73087],[122.41241,42.8659],[121.92077,42.67032],[121.86309,42.53588],[121.66534,42.43967],[121.56646,42.51968],[121.28494,42.43359],[121.03774,42.27019],[120.5722,42.16747],[120.40878,41.98297],[120.18424,41.84245],[120.09498,41.68932],[120.02975,41.71341],[120.03662,41.81277],[119.83474,42.21428],[119.54498,42.29356],[119.50584,42.39709],[119.27032,42.25901],[119.23736,42.19596],[119.29916,42.12522],[119.3795,42.08803],[119.28268,41.78155],[119.32456,41.62519],[119.39392,41.58566],[119.36576,41.43191],[119.29435,41.32732],[119.23562,41.3149],[119.24972,41.27264],[119.07325,41.08608],[118.92631,41.06382],[119.01695,40.97523],[118.88992,40.9576]]]}},{"type":"Feature","properties":{"id":"SD"},"geometry":{"type":"Polygon","coordinates":[[[21.81432,12.81362],[21.89371,12.68001],[21.98711,12.63292],[22.15679,12.66634],[22.22684,12.74682],[22.46345,12.61925],[22.38873,12.45514],[22.50548,12.16769],[22.48369,12.02766],[22.64092,12.07485],[22.54907,11.64372],[22.7997,11.40424],[22.93124,11.41645],[22.97249,11.21955],[22.87758,10.91915],[23.02221,10.69235],[23.3128,10.45214],[23.67164,9.86923],[23.69155,9.67566],[24.09319,9.66572],[24.12744,9.73784],[24.49389,9.79962],[24.84653,9.80643],[24.97739,9.9081],[25.05688,10.06776],[25.0918,10.33718],[25.78141,10.42599],[25.93163,10.38159],[25.93241,10.17941],[26.21338,9.91545],[26.35815,9.57946],[26.70685,9.48735],[27.14427,9.62858],[27.90704,9.61323],[28.99983,9.67155],[29.06988,9.74826],[29.53844,9.75133],[29.54,10.07949],[29.94629,10.29245],[30.00389,10.28633],[30.53005,9.95992],[30.82893,9.71451],[30.84605,9.7498],[31.28504,9.75287],[31.77539,10.28939],[31.99177,10.65065],[32.46967,11.04662],[32.39358,11.18207],[32.39578,11.70208],[32.10079,11.95203],[32.73921,11.95203],[32.73921,12.22757],[33.25876,12.22111],[33.13988,11.43248],[33.26977,10.83632],[33.24645,10.77913],[33.52294,10.64382],[33.66604,10.44254],[33.80913,10.32994],[33.90159,10.17179],[33.96984,10.15446],[33.99185,9.99623],[33.96323,9.80972],[33.9082,9.762],[33.87958,9.49937],[34.10229,9.50238],[34.08717,9.55243],[34.13186,9.7492],[34.20484,9.9033],[34.22718,10.02506],[34.32102,10.11599],[34.34783,10.23914],[34.2823,10.53508],[34.4372,10.781],[34.59062,10.89072],[34.77383,10.74588],[34.77532,10.69027],[34.86618,10.74588],[34.86916,10.78832],[34.97491,10.86147],[34.97789,10.91559],[34.93172,10.95946],[35.01215,11.19626],[34.95704,11.24448],[35.09556,11.56278],[35.05832,11.71158],[35.11492,11.85156],[35.24302,11.91132],[35.70476,12.67101],[36.01458,12.72478],[36.14268,12.70879],[36.16651,12.88019],[36.13374,12.92665],[36.24545,13.36759],[36.38993,13.56459],[36.48824,13.83954],[36.44653,13.95666],[36.54376,14.25597],[36.44337,15.14963],[36.54276,15.23478],[36.69761,15.75323],[36.76371,15.80831],[36.92193,16.23451],[36.99777,17.07172],[37.42694,17.04041],[37.50967,17.32199],[38.13362,17.53906],[38.37133,17.66269],[38.45916,17.87167],[38.57727,17.98125],[39.63762,18.37348],[37.8565,22.00903],[34.0765,22.00501],[33.99686,21.76784],[33.57251,21.72406],[33.17563,22.00405],[24.99885,21.99535],[24.99794,19.99661],[23.99715,20.00038],[23.99539,19.49944],[23.99997,15.69575],[23.62785,15.7804],[23.38812,15.69649],[23.10792,15.71297],[22.93201,15.55107],[22.92579,15.47007],[22.99584,15.40105],[22.99584,15.22989],[22.66115,14.86308],[22.70474,14.69149],[22.38562,14.58907],[22.44944,14.24986],[22.55997,14.23024],[22.5553,14.11704],[22.22995,13.96754],[22.08674,13.77863],[22.29689,13.3731],[22.1599,13.19281],[22.02914,13.13976],[21.94819,13.05637],[21.81432,12.81362]]]}},{"type":"Feature","properties":{"id":"SS"},"geometry":{"type":"Polygon","coordinates":[[[23.44744,8.99128],[23.59065,8.99743],[23.51905,8.71749],[24.25691,8.69288],[24.13238,8.36959],[24.35965,8.26177],[24.85156,8.16933],[24.98855,7.96588],[25.25319,7.8487],[25.29214,7.66675],[25.20649,7.61115],[25.20337,7.50312],[25.35281,7.42595],[25.37461,7.33024],[25.90076,7.09549],[26.38022,6.63493],[26.32729,6.36272],[26.58259,6.1987],[26.51721,6.09655],[27.22705,5.71254],[27.22705,5.62889],[27.28621,5.56382],[27.23017,5.37167],[27.26886,5.25876],[27.44012,5.07349],[27.56656,4.89375],[27.65462,4.89375],[27.76469,4.79284],[27.79551,4.59976],[28.20719,4.35614],[28.6651,4.42638],[28.8126,4.48784],[29.03054,4.48784],[29.22207,4.34297],[29.43341,4.50101],[29.49726,4.7007],[29.82087,4.56246],[29.79666,4.37809],[30.06964,4.13221],[30.1621,4.10586],[30.22374,3.93896],[30.27658,3.95653],[30.47691,3.83353],[30.55396,3.84451],[30.57378,3.74567],[30.56277,3.62703],[30.78512,3.67097],[30.80713,3.60506],[30.85997,3.5743],[30.85153,3.48867],[30.97601,3.693],[31.16666,3.79853],[31.29476,3.8015],[31.50478,3.67814],[31.50776,3.63652],[31.72075,3.74354],[31.81459,3.82083],[31.86821,3.78664],[31.96205,3.6499],[31.95907,3.57408],[32.05187,3.589],[32.08491,3.56287],[32.08866,3.53543],[32.19888,3.50867],[32.20782,3.6053],[32.41337,3.748],[32.72021,3.77327],[32.89746,3.81339],[33.02852,3.89296],[33.18356,3.77812],[33.51264,3.75068],[33.9873,4.23316],[34.47601,4.72162],[35.34151,5.02364],[35.30992,4.90402],[35.47843,4.91872],[35.42366,4.76969],[35.51424,4.61643],[35.9419,4.61933],[35.82118,4.77382],[35.81968,5.10757],[35.8576,5.33413],[35.50792,5.42431],[35.29938,5.34042],[35.31188,5.50106],[35.13058,5.62118],[35.12611,5.68937],[35.00546,5.89387],[34.96227,6.26415],[35.01738,6.46991],[34.87736,6.60161],[34.77459,6.5957],[34.65096,6.72589],[34.53776,6.74808],[34.53925,6.82794],[34.47669,6.91076],[34.35753,6.91963],[34.19369,7.04382],[34.19369,7.12807],[34.01495,7.25664],[34.03878,7.27437],[34.02984,7.36449],[33.87642,7.5491],[33.71407,7.65983],[33.44745,7.7543],[33.32531,7.71297],[33.24637,7.77939],[33.04944,7.78989],[33.0006,7.90333],[33.08401,8.05822],[33.18083,8.13047],[33.1853,8.29264],[33.19721,8.40317],[33.3119,8.45474],[33.54575,8.47094],[33.66938,8.44442],[33.71407,8.3678],[33.87195,8.41938],[33.89579,8.4842],[34.01346,8.50041],[34.14453,8.60204],[34.14304,9.04654],[34.10229,9.50238],[33.87958,9.49937],[33.9082,9.762],[33.96323,9.80972],[33.99185,9.99623],[33.96984,10.15446],[33.90159,10.17179],[33.80913,10.32994],[33.66604,10.44254],[33.52294,10.64382],[33.24645,10.77913],[33.26977,10.83632],[33.13988,11.43248],[33.25876,12.22111],[32.73921,12.22757],[32.73921,11.95203],[32.10079,11.95203],[32.39578,11.70208],[32.39358,11.18207],[32.46967,11.04662],[31.99177,10.65065],[31.77539,10.28939],[31.28504,9.75287],[30.84605,9.7498],[30.82893,9.71451],[30.53005,9.95992],[30.00389,10.28633],[29.94629,10.29245],[29.54,10.07949],[29.53844,9.75133],[29.06988,9.74826],[28.99983,9.67155],[27.90704,9.61323],[27.14427,9.62858],[26.70685,9.48735],[26.35815,9.57946],[26.21338,9.91545],[25.93241,10.17941],[25.93163,10.38159],[25.78141,10.42599],[25.0918,10.33718],[25.05688,10.06776],[24.97739,9.9081],[24.84653,9.80643],[24.49389,9.79962],[24.12744,9.73784],[24.09319,9.66572],[23.69155,9.67566],[23.62179,9.53823],[23.64981,9.44303],[23.64358,9.28637],[23.56263,9.19418],[23.4848,9.16959],[23.44744,8.99128]]]}},{"type":"Feature","properties":{"id":"SN"},"geometry":{"type":"Polygon","coordinates":[[[-18.35085,14.63444],[-17.43598,13.59273],[-15.47902,13.58758],[-15.36504,13.79313],[-14.93719,13.80173],[-14.34721,13.46578],[-13.8955,13.59126],[-13.79409,13.34472],[-14.36795,13.23033],[-15.14917,13.57989],[-15.26908,13.37768],[-15.80478,13.34832],[-15.80355,13.16729],[-16.69343,13.16791],[-16.74676,13.06025],[-17.43966,13.04579],[-17.4623,11.92379],[-16.70562,12.34803],[-16.38191,12.36449],[-16.20591,12.46157],[-15.67302,12.42974],[-15.17582,12.6847],[-13.70523,12.68013],[-13.05296,12.64003],[-13.06603,12.49342],[-12.87336,12.51892],[-12.35415,12.32758],[-11.91331,12.42008],[-11.46267,12.44559],[-11.37536,12.40788],[-11.39935,12.97808],[-11.63025,13.39174],[-11.83345,13.33333],[-12.06897,13.71049],[-11.93043,13.84505],[-12.23936,14.76324],[-13.11029,15.52116],[-13.43135,16.09022],[-13.80075,16.13961],[-14.32144,16.61495],[-15.00557,16.64997],[-15.6509,16.50315],[-16.27016,16.51565],[-16.4429,16.20605],[-16.44814,16.09753],[-16.48967,16.0496],[-16.50854,16.09032],[-17.15288,16.07139],[-18.35085,14.63444]]]}},{"type":"Feature","properties":{"id":"SG"},"geometry":{"type":"Polygon","coordinates":[[[103.56591,1.19719],[103.66049,1.18825],[103.74084,1.12902],[104.03085,1.26954],[104.12282,1.27714],[104.08072,1.35998],[104.09162,1.39694],[104.08871,1.42015],[104.07348,1.43322],[104.04622,1.44691],[104.02277,1.4438],[104.00131,1.42405],[103.93384,1.42926],[103.89565,1.42841],[103.86383,1.46288],[103.81181,1.47953],[103.76395,1.45183],[103.74161,1.4502],[103.7219,1.46108],[103.67468,1.43166],[103.62738,1.35255],[103.56591,1.19719]]]}},{"type":"Feature","properties":{"id":"SL"},"geometry":{"type":"Polygon","coordinates":[[[-14.36218,8.64107],[-12.15048,6.15992],[-11.50429,6.92704],[-11.4027,6.97746],[-11.29417,7.21576],[-10.60422,7.7739],[-10.60492,8.04072],[-10.57523,8.04829],[-10.51554,8.1393],[-10.45023,8.15627],[-10.35227,8.15223],[-10.29839,8.21283],[-10.31635,8.28554],[-10.30084,8.30008],[-10.27575,8.48711],[-10.37257,8.48941],[-10.54891,8.31174],[-10.63934,8.35326],[-10.70565,8.29235],[-10.61422,8.5314],[-10.47707,8.67669],[-10.56197,8.81225],[-10.5783,9.06386],[-10.74484,9.07998],[-10.6534,9.29919],[-11.2118,10.00098],[-11.89624,9.99763],[-11.91023,9.93927],[-12.12634,9.87203],[-12.24262,9.92386],[-12.47254,9.86834],[-12.76788,9.3133],[-12.94095,9.26335],[-13.08953,9.0409],[-13.18586,9.0925],[-13.29911,9.04245],[-14.36218,8.64107]]]}},{"type":"Feature","properties":{"id":"SV"},"geometry":{"type":"Polygon","coordinates":[[[-90.55276,12.8866],[-88.11443,12.63306],[-87.7346,13.13228],[-87.55124,13.12523],[-87.69751,13.25228],[-87.73714,13.32715],[-87.80177,13.35689],[-87.84675,13.41078],[-87.83467,13.44655],[-87.77354,13.45767],[-87.73841,13.44169],[-87.72115,13.46083],[-87.71657,13.50577],[-87.78148,13.52906],[-87.73106,13.75443],[-87.68821,13.80829],[-87.7966,13.91353],[-88.00331,13.86948],[-88.07641,13.98447],[-88.23018,13.99915],[-88.25791,13.91108],[-88.48982,13.86458],[-88.49738,13.97224],[-88.70661,14.04317],[-88.73182,14.10919],[-88.815,14.11652],[-88.85785,14.17763],[-88.94608,14.20207],[-89.04187,14.33644],[-89.34776,14.43013],[-89.39028,14.44561],[-89.57441,14.41637],[-89.58814,14.33165],[-89.50614,14.26084],[-89.52397,14.22628],[-89.61844,14.21937],[-89.70756,14.1537],[-89.75569,14.07073],[-89.73251,14.04133],[-89.76103,14.02923],[-89.81807,14.07073],[-89.88937,14.0396],[-90.10505,13.85104],[-90.11344,13.73679],[-90.55276,12.8866]]]}},{"type":"Feature","properties":{"id":"SM"},"geometry":{"type":"Polygon","coordinates":[[[12.40415,43.95485],[12.40506,43.94325],[12.41165,43.93769],[12.41551,43.92984],[12.40733,43.92379],[12.41233,43.90956],[12.40935,43.9024],[12.41641,43.89991],[12.44184,43.90498],[12.45648,43.89369],[12.48771,43.89706],[12.49429,43.90973],[12.49247,43.91774],[12.49724,43.92248],[12.50269,43.92363],[12.50496,43.93017],[12.51553,43.94096],[12.51427,43.94897],[12.50655,43.95796],[12.50875,43.96198],[12.50622,43.97131],[12.51109,43.97201],[12.51064,43.98165],[12.5154,43.98508],[12.51463,43.99122],[12.50678,43.99113],[12.49406,43.98492],[12.47853,43.98052],[12.46205,43.97463],[12.44684,43.96597],[12.43662,43.95698],[12.42005,43.9578],[12.41414,43.95273],[12.40415,43.95485]]]}},{"type":"Feature","properties":{"id":"RS"},"geometry":{"type":"Polygon","coordinates":[[[18.81394,45.91329],[18.85783,45.85493],[18.90305,45.71863],[18.96691,45.66731],[18.88776,45.57253],[18.94562,45.53712],[19.07471,45.53086],[19.08364,45.48804],[18.99918,45.49333],[18.97446,45.37528],[19.10774,45.29547],[19.28208,45.23813],[19.41941,45.23475],[19.43589,45.17137],[19.19144,45.17863],[19.14063,45.12972],[19.07952,45.14668],[19.1011,45.01191],[19.05205,44.97692],[19.15573,44.95409],[19.06853,44.89915],[19.02871,44.92541],[18.98957,44.90645],[19.01994,44.85493],[19.18183,44.92055],[19.36722,44.88164],[19.32543,44.74058],[19.26388,44.65412],[19.16699,44.52197],[19.13369,44.52521],[19.12278,44.50132],[19.14837,44.45253],[19.14681,44.41463],[19.11785,44.40313],[19.10749,44.39421],[19.10704,44.38249],[19.10365,44.37795],[19.10298,44.36924],[19.11865,44.36712],[19.1083,44.3558],[19.11547,44.34218],[19.13556,44.338],[19.13332,44.31492],[19.16741,44.28648],[19.18328,44.28383],[19.20508,44.2917],[19.23306,44.26097],[19.26945,44.26957],[19.32464,44.27185],[19.34773,44.23244],[19.3588,44.18353],[19.40927,44.16722],[19.43905,44.13088],[19.47338,44.15034],[19.48386,44.14332],[19.47321,44.1193],[19.51167,44.08158],[19.55999,44.06894],[19.57467,44.04716],[19.61991,44.05254],[19.61836,44.01464],[19.56498,43.99922],[19.52515,43.95573],[19.38439,43.96611],[19.24363,44.01502],[19.23465,43.98764],[19.3986,43.79668],[19.5176,43.71403],[19.50455,43.58385],[19.42696,43.57987],[19.41941,43.54056],[19.36653,43.60921],[19.33426,43.58833],[19.2553,43.5938],[19.24774,43.53061],[19.22807,43.5264],[19.22229,43.47926],[19.44315,43.38846],[19.48171,43.32644],[19.52962,43.31623],[19.54598,43.25158],[19.62661,43.2286],[19.64063,43.19027],[19.76918,43.16044],[19.79255,43.11951],[19.92576,43.08539],[19.96549,43.11098],[19.98887,43.0538],[20.04729,43.02732],[20.05431,42.99571],[20.12325,42.96237],[20.14896,42.99058],[20.16415,42.97177],[20.34528,42.90676],[20.35692,42.8335],[20.40594,42.84853],[20.43734,42.83157],[20.53484,42.8885],[20.48692,42.93208],[20.59929,43.01067],[20.64557,43.00826],[20.69515,43.09641],[20.59929,43.20492],[20.68688,43.21335],[20.73811,43.25068],[20.82145,43.26769],[20.88685,43.21697],[20.83727,43.17842],[20.96287,43.12416],[21.00749,43.13984],[21.05378,43.10707],[21.08952,43.13471],[21.14465,43.11089],[21.16734,42.99694],[21.2041,43.02277],[21.23877,43.00848],[21.23534,42.95523],[21.2719,42.8994],[21.32974,42.90424],[21.36941,42.87397],[21.44047,42.87276],[21.39045,42.74888],[21.47498,42.74695],[21.59154,42.72643],[21.58755,42.70418],[21.6626,42.67813],[21.75025,42.70125],[21.79413,42.65923],[21.75672,42.62695],[21.7327,42.55041],[21.70522,42.54176],[21.7035,42.51899],[21.62556,42.45106],[21.64209,42.41081],[21.62887,42.37664],[21.59029,42.38042],[21.57021,42.3647],[21.53467,42.36809],[21.5264,42.33634],[21.56772,42.30946],[21.58992,42.25915],[21.70111,42.23789],[21.77176,42.2648],[21.84654,42.3247],[21.91595,42.30392],[21.94405,42.34669],[22.02908,42.29848],[22.16384,42.32103],[22.29605,42.37477],[22.29275,42.34913],[22.34773,42.31725],[22.45919,42.33822],[22.47498,42.3915],[22.51961,42.3991],[22.55669,42.50144],[22.43983,42.56851],[22.4997,42.74144],[22.43309,42.82057],[22.54302,42.87774],[22.74826,42.88701],[22.78397,42.98253],[22.89521,43.03625],[22.98104,43.11199],[23.00806,43.19279],[22.89727,43.22417],[22.82036,43.33665],[22.53397,43.47225],[22.47582,43.6558],[22.41043,43.69566],[22.35558,43.81281],[22.41449,44.00514],[22.61688,44.06534],[22.61711,44.16938],[22.67173,44.21564],[22.68166,44.28206],[22.56012,44.30712],[22.45436,44.47258],[22.54021,44.47836],[22.56493,44.53419],[22.61368,44.55719],[22.70981,44.51852],[22.76749,44.54446],[22.69196,44.61587],[22.61917,44.61489],[22.45301,44.7194],[22.30844,44.6619],[22.18315,44.48179],[22.13234,44.47444],[22.08016,44.49844],[21.99364,44.63395],[21.7795,44.66165],[21.71692,44.65349],[21.67504,44.67107],[21.61942,44.67059],[21.60019,44.75208],[21.55007,44.77304],[21.38802,44.78133],[21.35643,44.86364],[21.44013,44.87613],[21.48202,44.87199],[21.56328,44.89502],[21.54938,44.9327],[21.35855,45.01941],[21.4505,45.04294],[21.51299,45.15345],[21.48278,45.19557],[21.29398,45.24148],[21.20392,45.2677],[21.17612,45.32566],[21.09894,45.30144],[20.87948,45.42743],[20.86026,45.47295],[20.77217,45.49788],[20.83321,45.53567],[20.76798,45.60969],[20.80361,45.65875],[20.82364,45.77738],[20.78446,45.78522],[20.77416,45.75601],[20.70069,45.7493],[20.65645,45.82801],[20.54818,45.89939],[20.35862,45.99356],[20.26068,46.12332],[20.09713,46.17315],[20.03533,46.14509],[20.01816,46.17696],[19.93508,46.17553],[19.81491,46.1313],[19.66007,46.19005],[19.56113,46.16824],[19.52473,46.1171],[19.28826,45.99694],[19.14543,45.9998],[19.10388,46.04015],[19.0791,45.96458],[19.01284,45.96529],[18.99712,45.93537],[18.81394,45.91329]]]}},{"type":"Feature","properties":{"id":"SR"},"geometry":{"type":"Polygon","coordinates":[[[-58.0307,3.95513],[-57.35891,3.32121],[-56.70519,2.02964],[-56.55439,2.02003],[-56.47045,1.95135],[-55.99278,1.83137],[-55.89863,1.89861],[-55.92159,2.05236],[-56.13054,2.27723],[-55.96292,2.53188],[-55.71493,2.40342],[-55.01919,2.564],[-54.6084,2.32856],[-54.42864,2.42442],[-54.28534,2.67798],[-53.9849,3.58697],[-53.98914,3.627],[-54.05128,3.63557],[-54.19367,3.84387],[-54.38444,4.13222],[-54.4717,4.91964],[-54.26916,5.26909],[-54.01877,5.52789],[-54.01074,5.68785],[-53.7094,6.2264],[-56.84822,6.73257],[-57.31629,5.33714],[-57.22536,5.15605],[-57.37442,5.0208],[-57.8699,4.89394],[-58.0307,3.95513]]]}},{"type":"Feature","properties":{"id":"SK"},"geometry":{"type":"Polygon","coordinates":[[[16.83317,48.38138],[16.84243,48.35258],[16.90903,48.32519],[16.89461,48.31332],[16.97701,48.17385],[17.02919,48.13996],[17.05735,48.14179],[17.09168,48.09366],[17.07039,48.0317],[17.16001,48.00636],[17.23699,48.02094],[17.71215,47.7548],[18.02938,47.75665],[18.29305,47.73541],[18.56496,47.76588],[18.66521,47.76772],[18.74074,47.8157],[18.8506,47.82308],[18.76821,47.87469],[18.76134,47.97499],[18.82176,48.04206],[19.01952,48.07052],[19.23924,48.0595],[19.28182,48.08336],[19.47957,48.09437],[19.52489,48.19791],[19.63338,48.25006],[19.92452,48.1283],[20.24312,48.2784],[20.29943,48.26104],[20.5215,48.53336],[20.83248,48.5824],[21.11516,48.49546],[21.44063,48.58456],[21.6068,48.50365],[21.67134,48.3989],[21.72525,48.34628],[21.8279,48.33321],[21.83339,48.36242],[22.14689,48.4005],[22.16023,48.56548],[22.21379,48.6218],[22.34151,48.68893],[22.42934,48.92857],[22.48296,48.99172],[22.54338,49.01424],[22.56155,49.08865],[22.04427,49.22136],[21.96385,49.3437],[21.82927,49.39467],[21.77983,49.35443],[21.62328,49.4447],[21.43376,49.41433],[21.27858,49.45988],[21.19756,49.4054],[21.12477,49.43666],[21.041,49.41791],[21.09799,49.37176],[20.98733,49.30774],[20.9229,49.29626],[20.77971,49.35383],[20.72274,49.41813],[20.61666,49.41791],[20.5631,49.375],[20.46422,49.41612],[20.39939,49.3896],[20.31728,49.39914],[20.31453,49.34817],[20.21977,49.35265],[20.13738,49.31685],[20.08238,49.1813],[19.98494,49.22904],[19.90529,49.23532],[19.86409,49.19316],[19.75286,49.20751],[19.82237,49.27806],[19.78581,49.41701],[19.72127,49.39288],[19.6375,49.40897],[19.64162,49.45184],[19.57845,49.46077],[19.53313,49.52856],[19.52626,49.57311],[19.45348,49.61583],[19.37795,49.574],[19.36009,49.53747],[19.25435,49.53391],[19.18019,49.41165],[18.9742,49.39557],[18.97283,49.49914],[18.94536,49.52143],[18.84521,49.51672],[18.74761,49.492],[18.67757,49.50895],[18.6144,49.49824],[18.57183,49.51162],[18.53063,49.49022],[18.54848,49.47059],[18.44686,49.39467],[18.4084,49.40003],[18.4139,49.36517],[18.36446,49.3267],[18.18456,49.28909],[18.15022,49.24518],[18.1104,49.08624],[18.06885,49.03157],[17.91814,49.01784],[17.87831,48.92679],[17.77944,48.92318],[17.73126,48.87885],[17.7094,48.86721],[17.5295,48.81117],[17.45671,48.85004],[17.3853,48.80936],[17.29054,48.85546],[17.19355,48.87602],[17.11202,48.82925],[17.00215,48.70887],[16.93955,48.60371],[16.94611,48.53614],[16.85204,48.44968],[16.8497,48.38321],[16.83588,48.3844],[16.83317,48.38138]]]}},{"type":"Feature","properties":{"id":"SI"},"geometry":{"type":"Polygon","coordinates":[[[13.37671,46.29668],[13.42218,46.20758],[13.47587,46.22725],[13.56114,46.2054],[13.56682,46.18703],[13.64451,46.18966],[13.66472,46.17392],[13.64053,46.13587],[13.57072,46.09022],[13.50104,46.05986],[13.49568,46.04839],[13.50998,46.04498],[13.49702,46.01832],[13.47474,46.00546],[13.50104,45.98078],[13.52963,45.96588],[13.56759,45.96991],[13.58903,45.99009],[13.62074,45.98388],[13.63458,45.98947],[13.64307,45.98326],[13.6329,45.94894],[13.63815,45.93607],[13.61931,45.91782],[13.60857,45.89907],[13.59565,45.89446],[13.58644,45.88173],[13.57563,45.8425],[13.58858,45.83503],[13.59784,45.8072],[13.66986,45.79955],[13.8235,45.7176],[13.83332,45.70855],[13.83422,45.68703],[13.87933,45.65207],[13.9191,45.6322],[13.8695,45.60835],[13.86771,45.59898],[13.84106,45.58185],[13.78445,45.5825],[13.74587,45.59811],[13.7198,45.59352],[13.6076,45.64761],[13.45644,45.59464],[13.56979,45.4895],[13.62902,45.45898],[13.67398,45.4436],[13.7785,45.46787],[13.81742,45.43729],[13.88124,45.42637],[13.90771,45.45149],[13.97309,45.45258],[13.99488,45.47551],[13.96063,45.50825],[14.00578,45.52352],[14.07116,45.48752],[14.20348,45.46896],[14.22371,45.50388],[14.24239,45.50607],[14.26611,45.48239],[14.27681,45.4902],[14.32487,45.47142],[14.36693,45.48642],[14.49769,45.54424],[14.5008,45.60852],[14.53816,45.6205],[14.57397,45.67165],[14.60977,45.66403],[14.59576,45.62812],[14.69694,45.57366],[14.68605,45.53006],[14.71718,45.53442],[14.80124,45.49515],[14.81992,45.45913],[14.90554,45.47769],[14.92266,45.52788],[15.02385,45.48533],[15.05187,45.49079],[15.16862,45.42309],[15.27758,45.46678],[15.33051,45.45258],[15.38188,45.48752],[15.30249,45.53224],[15.29837,45.5841],[15.27747,45.60504],[15.31027,45.6303],[15.34695,45.63382],[15.34214,45.64702],[15.38952,45.63682],[15.4057,45.64727],[15.34919,45.71623],[15.30872,45.69014],[15.25423,45.72275],[15.40836,45.79491],[15.47531,45.79802],[15.47325,45.8253],[15.52234,45.82195],[15.57952,45.84953],[15.64185,45.82915],[15.66662,45.84085],[15.70411,45.8465],[15.68232,45.86819],[15.68383,45.88867],[15.67967,45.90455],[15.70636,45.92116],[15.70327,46.00015],[15.71246,46.01196],[15.72977,46.04682],[15.62317,46.09103],[15.6083,46.11992],[15.59909,46.14761],[15.64904,46.19229],[15.6434,46.21396],[15.67395,46.22478],[15.75436,46.21969],[15.75479,46.20336],[15.78817,46.21719],[15.79284,46.25811],[15.97965,46.30652],[16.07616,46.3463],[16.07314,46.36458],[16.05065,46.3833],[16.05281,46.39141],[16.14859,46.40547],[16.18824,46.38282],[16.30233,46.37837],[16.30162,46.40437],[16.27329,46.41467],[16.27398,46.42875],[16.25124,46.48067],[16.23961,46.49653],[16.26759,46.50566],[16.26733,46.51505],[16.29793,46.5121],[16.37193,46.55008],[16.38771,46.53608],[16.44036,46.5171],[16.5007,46.49644],[16.52604,46.47831],[16.59527,46.47524],[16.52604,46.5051],[16.52885,46.53303],[16.50139,46.56684],[16.39217,46.63673],[16.38594,46.6549],[16.41863,46.66238],[16.42641,46.69228],[16.37816,46.69975],[16.30966,46.7787],[16.31303,46.79838],[16.3408,46.80641],[16.34547,46.83836],[16.2941,46.87137],[16.2365,46.87775],[16.21892,46.86961],[16.15711,46.85434],[16.14365,46.8547],[16.10983,46.867],[16.05786,46.83927],[15.99054,46.82772],[15.99126,46.78199],[15.98432,46.74991],[15.99769,46.7266],[16.02808,46.71094],[16.04347,46.68694],[16.04036,46.6549],[15.99988,46.67947],[15.98512,46.68463],[15.94864,46.68769],[15.87691,46.7211],[15.8162,46.71897],[15.78518,46.70712],[15.76771,46.69863],[15.73823,46.70011],[15.72279,46.69548],[15.69523,46.69823],[15.67411,46.70735],[15.6543,46.70616],[15.6543,46.69228],[15.6365,46.6894],[15.63255,46.68069],[15.62317,46.67947],[15.59826,46.68908],[15.54533,46.66985],[15.55333,46.64988],[15.54431,46.6312],[15.46906,46.61321],[15.45514,46.63697],[15.41235,46.65556],[15.23711,46.63994],[15.14215,46.66131],[15.01451,46.641],[14.98024,46.6009],[14.96002,46.63459],[14.92283,46.60848],[14.87129,46.61],[14.86419,46.59411],[14.83549,46.56614],[14.81836,46.51046],[14.72185,46.49974],[14.66892,46.44936],[14.5942,46.43434],[14.56463,46.37208],[14.52176,46.42617],[14.45877,46.41717],[14.42608,46.44614],[14.314,46.43327],[14.28326,46.44315],[14.15989,46.43327],[14.12097,46.47724],[14.04002,46.49117],[14.00422,46.48474],[13.89837,46.52331],[13.7148,46.5222],[13.68684,46.43881],[13.59777,46.44137],[13.5763,46.42613],[13.5763,46.40915],[13.47019,46.3621],[13.43418,46.35992],[13.44808,46.33507],[13.37671,46.29668]]]}},{"type":"Feature","properties":{"id":"SE"},"geometry":{"type":"Polygon","coordinates":[[[10.40861,58.38489],[12.16597,56.60205],[12.07466,56.29488],[12.65312,56.04345],[12.6372,55.91371],[12.88472,55.63369],[12.60345,55.42675],[12.84405,55.13257],[14.28399,55.1553],[14.89259,55.5623],[15.79951,55.54655],[19.64795,57.06466],[19.84909,57.57876],[20.5104,59.15546],[19.08191,60.19152],[19.23413,60.61414],[20.15877,63.06556],[24.14112,65.39731],[24.15107,65.81427],[24.14798,65.83466],[24.15791,65.85385],[23.90497,66.15802],[23.71339,66.21299],[23.64982,66.30603],[23.67591,66.3862],[23.63776,66.43568],[23.85959,66.56434],[23.89488,66.772],[23.98059,66.79585],[23.98563,66.84149],[23.56214,67.17038],[23.58735,67.20752],[23.54701,67.25435],[23.75372,67.29914],[23.75372,67.43688],[23.39577,67.46974],[23.54701,67.59306],[23.45627,67.85297],[23.65793,67.9497],[23.40081,68.05545],[23.26469,68.15134],[23.15377,68.14759],[23.10336,68.26551],[22.73028,68.40881],[22.00429,68.50692],[21.03001,68.88969],[20.90649,68.89696],[20.85104,68.93142],[20.91658,68.96764],[20.78802,69.03087],[20.55258,69.06069],[20.0695,69.04469],[20.28444,68.93283],[20.33435,68.80174],[20.22027,68.67246],[19.95647,68.55546],[20.22027,68.48759],[19.93508,68.35911],[18.97255,68.52416],[18.63032,68.50849],[18.39503,68.58672],[18.1241,68.53721],[18.13836,68.20874],[17.90787,67.96537],[17.30416,68.11591],[16.7409,67.91037],[16.38441,67.52923],[16.12774,67.52106],[16.09922,67.4364],[16.39154,67.21653],[16.35589,67.06419],[15.37197,66.48217],[15.49318,66.28509],[15.05113,66.15572],[14.53778,66.12399],[14.50926,65.31786],[13.64276,64.58402],[14.11117,64.46674],[14.16051,64.18725],[13.98222,64.00953],[13.23411,64.09087],[12.74105,64.02171],[12.14928,63.59373],[12.19919,63.47935],[11.98529,63.27487],[12.19919,63.00104],[12.07085,62.6297],[12.29187,62.25699],[12.14746,61.7147],[12.40595,61.57226],[12.57707,61.56547],[12.86939,61.35427],[12.69115,61.06584],[12.2277,61.02442],[12.59133,60.50559],[12.52003,60.13846],[12.36317,59.99259],[12.15641,59.8926],[11.87121,59.86039],[11.92112,59.69531],[11.69297,59.59442],[11.8213,59.24985],[11.65732,58.90177],[11.45199,58.89604],[11.4601,58.99022],[11.34459,59.11672],[11.15367,59.07862],[11.08911,58.98745],[10.64958,58.89391],[10.40861,58.38489]]]}},{"type":"Feature","properties":{"id":"SZ"},"geometry":{"type":"Polygon","coordinates":[[[30.78927,-26.48271],[30.81101,-26.84722],[30.88826,-26.79622],[30.97757,-26.92706],[30.96088,-27.0245],[31.15027,-27.20151],[31.49834,-27.31549],[31.97592,-27.31675],[31.97463,-27.11057],[32.00893,-26.8096],[32.09664,-26.80721],[32.13315,-26.84345],[32.13409,-26.5317],[32.07352,-26.40185],[32.10435,-26.15656],[32.08599,-26.00978],[32.00916,-25.999],[31.974,-25.95387],[31.86881,-25.99973],[31.4175,-25.71886],[31.31237,-25.7431],[31.13073,-25.91558],[30.95819,-26.26303],[30.78927,-26.48271]]]}},{"type":"Feature","properties":{"id":"SY"},"geometry":{"type":"Polygon","coordinates":[[[35.48515,34.70851],[35.97386,34.63322],[35.98718,34.64977],[36.29165,34.62991],[36.32399,34.69334],[36.35135,34.68516],[36.35384,34.65447],[36.42941,34.62505],[36.46003,34.6378],[36.45299,34.59438],[36.41429,34.61175],[36.39846,34.55672],[36.3369,34.52629],[36.34745,34.5002],[36.4442,34.50165],[36.46179,34.46541],[36.55853,34.41609],[36.53039,34.3798],[36.56556,34.31881],[36.60778,34.31009],[36.58667,34.27667],[36.59195,34.2316],[36.62537,34.20251],[36.5128,34.09916],[36.50576,34.05982],[36.41078,34.05253],[36.28589,33.91981],[36.38263,33.86579],[36.3967,33.83365],[36.14517,33.85118],[36.06778,33.82927],[35.9341,33.6596],[36.05723,33.57904],[35.94465,33.52774],[35.94816,33.47886],[35.88668,33.43183],[35.82577,33.40479],[35.81324,33.36354],[35.77477,33.33609],[35.813,33.3172],[35.77513,33.27342],[35.81295,33.24841],[35.81647,33.2028],[35.83846,33.19397],[35.84285,33.16673],[35.81911,33.1336],[35.81911,33.11077],[35.84802,33.1031],[35.87188,32.98028],[35.89298,32.9456],[35.87012,32.91976],[35.84021,32.8725],[35.83758,32.82817],[35.78745,32.77938],[35.75983,32.74803],[35.88405,32.71321],[35.93307,32.71966],[35.96633,32.66237],[36.02239,32.65911],[36.08074,32.51463],[36.20379,32.52751],[36.20875,32.49529],[36.23948,32.50108],[36.40959,32.37908],[36.83946,32.31293],[38.79171,33.37328],[40.64314,34.31604],[40.97676,34.39788],[41.12388,34.65742],[41.2345,34.80049],[41.21654,35.1508],[41.26569,35.42708],[41.38184,35.62502],[41.37027,35.84095],[41.2564,36.06012],[41.28864,36.35368],[41.40058,36.52502],[41.81736,36.58782],[42.36697,37.0627],[42.35724,37.10998],[42.32313,37.17814],[42.34735,37.22548],[42.2824,37.2798],[42.26039,37.27017],[42.23683,37.2863],[42.21548,37.28026],[42.20454,37.28715],[42.22381,37.30238],[42.22257,37.31395],[42.2112,37.32491],[42.19301,37.31323],[42.18225,37.28569],[42.00894,37.17209],[41.515,37.08084],[41.21937,37.07665],[40.90856,37.13147],[40.69136,37.0996],[39.81589,36.75538],[39.21538,36.66834],[39.03217,36.70911],[38.74042,36.70629],[38.55908,36.84429],[38.38859,36.90064],[38.21064,36.91842],[37.81974,36.76055],[37.68048,36.75065],[37.49103,36.66904],[37.47253,36.63243],[37.21988,36.6736],[37.16177,36.66069],[37.10894,36.6704],[37.08279,36.63495],[37.02088,36.66422],[37.01647,36.69512],[37.04619,36.71101],[37.04399,36.73483],[36.99886,36.74012],[36.99557,36.75997],[36.66727,36.82901],[36.61581,36.74629],[36.62681,36.71189],[36.57398,36.65186],[36.58829,36.58295],[36.54206,36.49539],[36.6081,36.33772],[36.65653,36.33861],[36.68672,36.23677],[36.6125,36.22592],[36.50463,36.2419],[36.4617,36.20461],[36.39206,36.22088],[36.37474,36.01163],[36.33956,35.98687],[36.30099,36.00985],[36.28338,36.00273],[36.29769,35.96086],[36.27678,35.94839],[36.25366,35.96264],[36.19973,35.95195],[36.17441,35.92076],[36.1623,35.80925],[36.14029,35.81015],[36.13919,35.83692],[36.11827,35.85923],[35.99829,35.88242],[36.01844,35.92403],[36.00514,35.94113],[35.98499,35.94107],[35.931,35.92109],[35.51152,36.10954],[35.48515,34.70851]]]}},{"type":"Feature","properties":{"id":"TD"},"geometry":{"type":"Polygon","coordinates":[[[13.47559,14.40881],[13.6302,13.71094],[14.08251,13.0797],[14.46881,13.08259],[14.56101,12.91036],[14.55058,12.78256],[14.83314,12.62963],[14.90827,12.3269],[14.89019,12.16593],[14.96952,12.0925],[15.00146,12.1223],[15.0349,12.10698],[15.05786,12.0608],[15.04808,11.8731],[15.11579,11.79313],[15.06595,11.71126],[15.13149,11.5537],[15.0585,11.40481],[15.10021,11.04101],[15.04957,11.02347],[15.09127,10.87431],[15.06737,10.80921],[15.15532,10.62846],[15.14936,10.53915],[15.23724,10.47764],[15.30874,10.31063],[15.50535,10.1098],[15.68761,9.99344],[15.41408,9.92876],[15.24618,9.99246],[15.14043,9.99246],[15.05999,9.94845],[14.95722,9.97926],[14.80082,9.93818],[14.4673,10.00264],[14.20411,10.00055],[14.1317,9.82413],[14.01793,9.73169],[13.97544,9.6365],[14.37094,9.2954],[14.35707,9.19611],[14.83566,8.80557],[15.09484,8.65982],[15.20426,8.50892],[15.50743,7.79302],[15.59272,7.7696],[15.56964,7.58936],[15.49743,7.52179],[15.73118,7.52006],[15.79942,7.44149],[16.40703,7.68809],[16.41583,7.77971],[16.58315,7.88657],[16.59415,7.76444],[16.658,7.75353],[16.6668,7.67281],[16.8143,7.53971],[17.67288,7.98905],[17.93926,7.95853],[18.02731,8.01085],[18.6085,8.05009],[18.64153,8.08714],[18.62612,8.14163],[18.67455,8.22226],[18.79783,8.25929],[19.11044,8.68172],[18.86388,8.87971],[19.06421,9.00367],[20.36748,9.11019],[20.82979,9.44696],[21.26348,9.97642],[21.34934,9.95907],[21.52766,10.2105],[21.63553,10.217],[21.71479,10.29932],[21.72139,10.64136],[22.45889,11.00246],[22.87758,10.91915],[22.97249,11.21955],[22.93124,11.41645],[22.7997,11.40424],[22.54907,11.64372],[22.64092,12.07485],[22.48369,12.02766],[22.50548,12.16769],[22.38873,12.45514],[22.46345,12.61925],[22.22684,12.74682],[22.15679,12.66634],[21.98711,12.63292],[21.89371,12.68001],[21.81432,12.81362],[21.94819,13.05637],[22.02914,13.13976],[22.1599,13.19281],[22.29689,13.3731],[22.08674,13.77863],[22.22995,13.96754],[22.5553,14.11704],[22.55997,14.23024],[22.44944,14.24986],[22.38562,14.58907],[22.70474,14.69149],[22.66115,14.86308],[22.99584,15.22989],[22.99584,15.40105],[22.92579,15.47007],[22.93201,15.55107],[23.10792,15.71297],[23.38812,15.69649],[23.62785,15.7804],[23.99997,15.69575],[23.99539,19.49944],[15.99566,23.49639],[14.99751,23.00539],[15.19692,21.99339],[15.20213,21.49365],[15.28332,21.44557],[15.62515,20.95395],[15.57248,20.92138],[15.55382,20.86507],[15.56004,20.79488],[15.59841,20.74039],[15.6721,20.70069],[15.99632,20.35364],[15.75098,19.93002],[15.6032,18.77402],[15.50373,16.89649],[14.37425,15.72591],[13.86301,15.04043],[13.78991,14.87519],[13.809,14.72915],[13.67878,14.64013],[13.68573,14.55276],[13.48259,14.46704],[13.47559,14.40881]]]}},{"type":"Feature","properties":{"id":"IN-GA"},"geometry":{"type":"Polygon","coordinates":[[[73.34747,15.61245],[73.87481,14.75496],[74.16217,14.94976],[74.20543,14.92819],[74.29195,15.02869],[74.2741,15.09996],[74.31838,15.1805],[74.25075,15.25371],[74.33486,15.28352],[74.24903,15.49206],[74.25659,15.64551],[74.11617,15.65411],[74.01592,15.60253],[73.97472,15.62799],[73.94691,15.74236],[73.80477,15.74401],[73.68598,15.72484],[73.34747,15.61245]]]}},{"type":"Feature","properties":{"id":"TG"},"geometry":{"type":"Polygon","coordinates":[[[-0.14462,11.10811],[-0.05733,11.08628],[-0.0275,11.11202],[-0.00514,11.10763],[0.00342,11.08317],[0.02395,11.06229],[0.03355,10.9807],[-0.0063,10.96417],[-0.00908,10.91644],[-0.02685,10.8783],[-0.0228,10.81916],[-0.07183,10.76794],[-0.07327,10.71845],[-0.09141,10.7147],[-0.05945,10.63458],[0.12886,10.53149],[0.18846,10.4096],[0.29453,10.41546],[0.33028,10.30408],[0.39584,10.31112],[0.35293,10.09412],[0.41371,10.06361],[0.41252,10.02018],[0.36366,10.03309],[0.32075,9.72781],[0.34816,9.71607],[0.34816,9.66907],[0.32313,9.6491],[0.28261,9.69022],[0.26712,9.66437],[0.29334,9.59387],[0.36008,9.6256],[0.38153,9.58682],[0.23851,9.57389],[0.2409,9.52335],[0.30406,9.521],[0.31241,9.50337],[0.2254,9.47869],[0.25758,9.42696],[0.33148,9.44812],[0.36485,9.49749],[0.49118,9.48339],[0.56388,9.40697],[0.45424,9.04581],[0.52455,8.87746],[0.37319,8.75262],[0.47211,8.59945],[0.64731,8.48866],[0.73432,8.29529],[0.63897,8.25873],[0.5913,8.19622],[0.61156,8.18324],[0.6056,8.13959],[0.58891,8.12779],[0.62943,7.85751],[0.58295,7.62368],[0.51979,7.58706],[0.52455,7.45354],[0.57223,7.39326],[0.62943,7.41099],[0.65327,7.31643],[0.59606,7.01252],[0.52217,6.9723],[0.52098,6.94391],[0.56508,6.92971],[0.52853,6.82921],[0.57406,6.80348],[0.58176,6.76049],[0.6497,6.73682],[0.63659,6.63857],[0.74862,6.56517],[0.71048,6.53083],[0.89283,6.33779],[0.99652,6.33779],[1.03108,6.24064],[1.05969,6.22998],[1.09187,6.17074],[1.19966,6.17069],[1.19771,6.11522],[1.27574,5.93551],[1.67336,6.02702],[1.62913,6.24075],[1.79826,6.28221],[1.76906,6.43189],[1.58105,6.68619],[1.61812,6.74843],[1.55877,6.99737],[1.64249,6.99562],[1.61838,9.0527],[1.5649,9.16941],[1.41746,9.3226],[1.33675,9.54765],[1.36624,9.5951],[1.35507,9.99525],[0.77666,10.37665],[0.80358,10.71459],[0.8804,10.803],[0.91245,10.99597],[0.66104,10.99964],[0.4958,10.93269],[0.50521,10.98035],[0.48852,10.98561],[0.50388,11.01011],[-0.13493,11.14075],[-0.14462,11.10811]]]}},{"type":"Feature","properties":{"id":"TH"},"geometry":{"type":"Polygon","coordinates":[[[97.19814,8.18901],[99.31854,5.99868],[99.50117,6.44501],[99.91873,6.50233],[100.0756,6.4045],[100.12,6.42105],[100.19511,6.72559],[100.29651,6.68439],[100.30828,6.66462],[100.31618,6.66781],[100.31884,6.66423],[100.32671,6.66526],[100.32607,6.65933],[100.31929,6.65413],[100.35413,6.54932],[100.41152,6.52299],[100.41791,6.5189],[100.42351,6.51762],[100.43027,6.52389],[100.66986,6.45086],[100.74361,6.50811],[100.74822,6.46231],[100.81045,6.45086],[100.85884,6.24929],[101.10313,6.25617],[101.12618,6.19431],[101.06165,6.14161],[101.12388,6.11411],[101.087,5.9193],[101.02708,5.91013],[100.98815,5.79464],[101.14062,5.61613],[101.25755,5.71065],[101.25524,5.78633],[101.58019,5.93534],[101.69773,5.75881],[101.75074,5.79091],[101.80144,5.74505],[101.89188,5.8386],[101.91776,5.84269],[101.92819,5.85511],[101.94712,5.98421],[101.9714,6.00575],[101.97114,6.01992],[101.99209,6.04075],[102.01835,6.05407],[102.09182,6.14161],[102.07732,6.193],[102.08127,6.22679],[102.09086,6.23546],[102.46318,7.22462],[102.47649,9.66162],[102.52395,11.25257],[102.91449,11.65512],[102.90973,11.75613],[102.83957,11.8519],[102.78427,11.98746],[102.77026,12.06815],[102.70176,12.1686],[102.73134,12.37091],[102.78116,12.40284],[102.7796,12.43781],[102.57567,12.65358],[102.51963,12.66117],[102.4994,12.71736],[102.53053,12.77506],[102.49335,12.92711],[102.48694,12.97537],[102.52275,12.99813],[102.46011,13.08057],[102.43422,13.09061],[102.36146,13.26006],[102.36001,13.31142],[102.34611,13.35618],[102.35692,13.38274],[102.35563,13.47307],[102.361,13.50551],[102.33828,13.55613],[102.36859,13.57488],[102.44601,13.5637],[102.5358,13.56933],[102.57573,13.60461],[102.62483,13.60883],[102.58635,13.6286],[102.5481,13.6589],[102.56848,13.69366],[102.72727,13.77806],[102.77864,13.93374],[102.91251,14.01531],[102.93275,14.19044],[103.16469,14.33075],[103.39353,14.35639],[103.53518,14.42575],[103.71109,14.4348],[103.70175,14.38052],[103.93836,14.3398],[104.27616,14.39861],[104.55014,14.36091],[104.69335,14.42726],[104.97667,14.38806],[105.02804,14.23722],[105.08408,14.20402],[105.14012,14.23873],[105.17748,14.34432],[105.20894,14.34967],[105.43783,14.43865],[105.53864,14.55731],[105.5121,14.80802],[105.61162,15.00037],[105.46661,15.13132],[105.58043,15.32724],[105.50662,15.32054],[105.4692,15.33709],[105.47635,15.3796],[105.58191,15.41031],[105.60446,15.53301],[105.61756,15.68792],[105.46573,15.74742],[105.42285,15.76971],[105.37959,15.84074],[105.34115,15.92737],[105.38508,15.987],[105.42001,16.00657],[105.06204,16.09792],[105.00262,16.25627],[104.88057,16.37311],[104.73349,16.565],[104.76099,16.69302],[104.7397,16.81005],[104.76442,16.84752],[104.7373,16.91125],[104.73712,17.01404],[104.80716,17.19025],[104.80061,17.39367],[104.69867,17.53038],[104.45404,17.66788],[104.35432,17.82871],[104.2757,17.86139],[104.21776,17.99335],[104.10927,18.10826],[104.06533,18.21656],[103.97725,18.33631],[103.93916,18.33914],[103.85642,18.28666],[103.82449,18.33979],[103.699,18.34125],[103.60957,18.40528],[103.47773,18.42841],[103.41044,18.4486],[103.30977,18.4341],[103.24779,18.37807],[103.23818,18.34875],[103.29757,18.30475],[103.17093,18.2618],[103.14994,18.23172],[103.1493,18.17799],[103.07343,18.12351],[103.07823,18.03833],[103.0566,18.00144],[103.01998,17.97095],[102.9912,17.9949],[102.95812,18.0054],[102.86323,17.97531],[102.81988,17.94233],[102.79044,17.93612],[102.75954,17.89561],[102.68538,17.86653],[102.67543,17.84529],[102.69946,17.81686],[102.68194,17.80151],[102.59485,17.83537],[102.5896,17.84889],[102.61432,17.92273],[102.60971,17.95411],[102.59234,17.96127],[102.45523,17.97106],[102.11359,18.21532],[101.88485,18.02474],[101.78087,18.07559],[101.72294,17.92867],[101.44667,17.7392],[101.15108,17.47586],[100.96541,17.57926],[101.02185,17.87637],[101.1793,18.0544],[101.19118,18.2125],[101.15108,18.25624],[101.18227,18.34367],[101.06047,18.43247],[101.27585,18.68875],[101.22832,18.73377],[101.25803,18.89545],[101.35606,19.04716],[101.261,19.12717],[101.24911,19.33334],[101.20604,19.35296],[101.21347,19.46223],[101.26991,19.48324],[101.26545,19.59242],[101.08928,19.59748],[100.90302,19.61901],[100.77231,19.48324],[100.64606,19.55884],[100.58219,19.49164],[100.49604,19.53504],[100.398,19.75047],[100.5094,19.87904],[100.58808,20.15791],[100.55218,20.17741],[100.51052,20.14928],[100.47567,20.19133],[100.4537,20.19971],[100.44992,20.23644],[100.41473,20.25625],[100.37439,20.35156],[100.33383,20.4028],[100.25769,20.3992],[100.22076,20.31598],[100.16668,20.2986],[100.1712,20.24324],[100.11785,20.24787],[100.09337,20.26293],[100.09999,20.31614],[100.08404,20.36626],[99.95721,20.46301],[99.91616,20.44986],[99.90499,20.4487],[99.89692,20.44789],[99.89301,20.44311],[99.89168,20.44548],[99.88451,20.44596],[99.88211,20.44488],[99.86383,20.44371],[99.81096,20.33687],[99.68255,20.32077],[99.46008,20.39673],[99.46077,20.36198],[99.5569,20.20676],[99.52943,20.14811],[99.416,20.08614],[99.20328,20.12877],[99.0735,20.10298],[98.98679,19.7419],[98.83661,19.80931],[98.56065,19.67807],[98.51182,19.71303],[98.24884,19.67876],[98.13829,19.78541],[98.03314,19.80941],[98.04364,19.65755],[97.84715,19.55782],[97.88423,19.5041],[97.78769,19.39429],[97.84186,19.29526],[97.78606,19.26769],[97.84024,19.22217],[97.83479,19.09972],[97.73797,19.04261],[97.73654,18.9812],[97.66487,18.9371],[97.73836,18.88478],[97.76752,18.58097],[97.5258,18.4939],[97.36444,18.57138],[97.34522,18.54596],[97.50383,18.26844],[97.56219,18.33885],[97.64116,18.29778],[97.60841,18.23846],[97.73723,17.97912],[97.66794,17.88005],[97.76407,17.71595],[97.91829,17.54504],[98.11185,17.36829],[98.10439,17.33847],[98.34566,17.04822],[98.39441,17.06266],[98.52624,16.89979],[98.49603,16.8446],[98.53833,16.81934],[98.46994,16.73613],[98.50253,16.7139],[98.49713,16.69022],[98.51043,16.70107],[98.51579,16.69433],[98.51472,16.68521],[98.51833,16.676],[98.51113,16.64503],[98.5695,16.62826],[98.57912,16.55983],[98.63817,16.47424],[98.68074,16.27068],[98.84485,16.42354],[98.92656,16.36425],[98.8376,16.11706],[98.69585,16.13353],[98.57019,16.04578],[98.59853,15.87197],[98.541,15.65406],[98.58598,15.46821],[98.56027,15.33471],[98.4866,15.39154],[98.39351,15.34177],[98.41906,15.27103],[98.40522,15.25268],[98.30446,15.30667],[98.22,15.21327],[98.18821,15.13125],[98.24874,14.83013],[98.56762,14.37701],[98.97356,14.04868],[99.16695,13.72621],[99.20617,13.20575],[99.12225,13.19847],[99.10646,13.05804],[99.18748,12.9898],[99.18905,12.84799],[99.29254,12.68921],[99.409,12.60603],[99.47519,12.1353],[99.56445,12.14805],[99.53424,12.02317],[99.64891,11.82699],[99.64108,11.78948],[99.5672,11.62732],[99.47598,11.62434],[99.39485,11.3925],[99.31573,11.32081],[99.32756,11.28545],[99.06938,10.94857],[99.02337,10.97217],[98.99701,10.92962],[99.0069,10.85485],[98.86819,10.78336],[98.78511,10.68351],[98.77275,10.62548],[98.81944,10.52761],[98.7391,10.31488],[98.55174,9.92804],[98.52291,9.92389],[98.47298,9.95782],[98.33094,9.91973],[98.21525,9.56576],[97.63455,9.60854],[97.19814,8.18901]]]}},{"type":"Feature","properties":{"id":"IN-RJ"},"geometry":{"type":"Polygon","coordinates":[[[69.50904,26.74892],[69.88555,26.56836],[70.05584,26.60398],[70.17532,26.55362],[70.17532,26.24118],[70.08193,26.08094],[70.0985,25.93238],[70.2687,25.71156],[70.37444,25.67443],[70.53649,25.68928],[70.60378,25.71898],[70.67382,25.68186],[70.66695,25.39314],[70.89148,25.15064],[70.94002,24.92843],[71.09405,24.69017],[72.18292,24.60894],[72.44144,24.49214],[72.50083,24.40088],[72.69996,24.44527],[72.74116,24.35491],[72.97084,24.34866],[73.00277,24.48777],[73.2328,24.36476],[73.0783,24.20829],[73.2431,24.00319],[73.37425,24.13359],[73.42231,23.92601],[73.35639,23.77591],[73.6211,23.66024],[73.64135,23.4393],[73.83172,23.44749],[73.89953,23.33248],[73.99017,23.33405],[74.24182,23.19244],[74.3201,23.0573],[74.39769,23.11004],[74.53502,23.09868],[74.75063,23.22841],[74.69707,23.27572],[74.53605,23.30095],[74.61124,23.45694],[74.78702,23.54321],[74.93877,23.6376],[74.90924,23.86166],[74.98958,24.03235],[74.88143,24.21816],[74.91371,24.24226],[74.87182,24.27607],[74.75269,24.27576],[74.81002,24.41714],[74.90375,24.46058],[74.86907,24.48996],[74.74616,24.539],[74.80659,24.67322],[74.80522,24.79483],[74.90066,24.65107],[75.03318,24.75431],[74.84607,24.81135],[74.83543,24.98294],[75.03078,24.84563],[75.11352,24.88986],[75.17291,25.05356],[75.35659,25.03397],[75.30715,24.90138],[75.42629,24.8855],[75.18527,24.75057],[75.46989,24.68944],[75.58593,24.72562],[75.62919,24.68632],[75.78712,24.7699],[75.92101,24.51838],[75.88737,24.42401],[75.78987,24.47183],[75.73081,24.38118],[75.82076,24.24727],[75.7418,24.13766],[75.84377,24.10257],[75.70953,23.96617],[75.53203,24.0659],[75.45993,23.9144],[75.6879,23.7602],[75.73596,23.89651],[75.87879,23.88489],[75.98384,23.93574],[75.96324,24.02357],[76.0889,24.08564],[76.18846,24.33364],[76.21215,24.21283],[76.35875,24.25103],[76.91768,24.13735],[76.94343,24.20751],[76.83494,24.3718],[76.82224,24.544],[76.91802,24.54181],[76.94789,24.4509],[77.05879,24.52838],[77.06737,24.64639],[76.95304,24.75805],[76.85211,24.74745],[76.78481,24.83098],[76.94274,24.86401],[76.85554,25.03646],[77.00248,25.07316],[77.27439,25.11482],[77.31044,25.07938],[77.39559,25.11979],[77.41069,25.22109],[77.35507,25.2776],[77.35645,25.42932],[77.27508,25.42746],[77.19646,25.30647],[77.0763,25.33813],[76.93691,25.28071],[76.83734,25.32944],[76.68079,25.34309],[76.59942,25.39304],[76.52046,25.5319],[76.48544,25.71795],[76.59187,25.87745],[76.79408,25.94353],[76.91493,26.09533],[78.21029,26.82407],[78.26042,26.92941],[78.14575,26.94961],[78.04275,26.87001],[77.78045,26.92819],[77.45704,26.74315],[77.41722,26.85776],[77.75848,27.00469],[77.49412,27.08297],[77.67127,27.17341],[77.6493,27.24303],[77.44331,27.3931],[77.28126,27.80689],[77.03235,27.78623],[76.96884,27.65555],[76.88575,27.67075],[76.96317,28.14269],[76.86635,28.22681],[76.80747,28.21562],[76.79958,28.16796],[76.65607,28.09954],[76.66225,28.01774],[76.54792,27.97393],[76.49333,28.15404],[76.27,28.17538],[76.36184,28.12543],[76.30262,28.09999],[76.35618,28.07591],[76.34519,28.02804],[76.31635,28.01925],[76.24958,28.07031],[76.22451,28.04986],[76.17216,28.05546],[76.20614,28.02486],[76.17267,28.01849],[76.22177,27.81296],[75.99449,27.85789],[75.91964,27.93329],[76.04032,28.08561],[75.93097,28.0959],[76.01886,28.16948],[76.09336,28.1498],[75.94333,28.36663],[75.80223,28.40227],[75.56877,28.61225],[75.4656,28.92689],[75.51744,28.9733],[75.51315,29.01504],[75.44448,29.01609],[75.36294,29.14301],[75.41084,29.19982],[75.33462,29.28909],[75.08159,29.22889],[75.05207,29.281],[74.80453,29.39563],[74.59785,29.32472],[74.57382,29.45992],[74.62188,29.52985],[74.57244,29.56449],[74.61158,29.76199],[74.50035,29.74053],[74.46464,29.78106],[74.55116,29.85553],[74.52094,29.94303],[73.88992,29.96921],[73.92219,30.06196],[73.97225,30.19829],[73.80299,30.06969],[73.58665,30.01848],[73.3962,29.94707],[73.28094,29.56646],[73.05886,29.1878],[73.01337,29.16422],[72.94272,29.02487],[72.40402,28.78283],[72.29495,28.66367],[72.20329,28.3869],[71.9244,28.11555],[71.89921,27.96035],[70.79054,27.68423],[70.60927,28.02178],[70.37307,28.01208],[70.12502,27.8057],[70.03136,27.56627],[69.58519,27.18109],[69.50904,26.74892]]]}},{"type":"Feature","properties":{"id":"TM"},"geometry":{"type":"Polygon","coordinates":[[[51.7708,40.29239],[53.89734,37.3464],[54.24565,37.32047],[54.36211,37.34912],[54.58664,37.45809],[54.67247,37.43532],[54.77822,37.51597],[54.81804,37.61285],[54.77684,37.62264],[54.851,37.75739],[55.13412,37.94705],[55.44152,38.08564],[55.76561,38.12238],[55.97847,38.08024],[56.33278,38.08132],[56.32454,38.18502],[56.43303,38.26054],[56.62255,38.24005],[56.73928,38.27887],[57.03453,38.18717],[57.21169,38.28965],[57.37236,38.09321],[57.35042,37.98546],[57.79534,37.89299],[58.21399,37.77281],[58.22999,37.6856],[58.39959,37.63134],[58.47786,37.6433],[58.5479,37.70526],[58.6921,37.64548],[58.9338,37.67374],[59.22905,37.51161],[59.33507,37.53146],[59.39797,37.47892],[59.39385,37.34257],[59.55178,37.13594],[59.74678,37.12499],[60.00768,37.04102],[60.34767,36.63214],[61.14516,36.64644],[61.18187,36.55348],[61.1393,36.38782],[61.22719,36.12759],[61.12007,35.95992],[61.22444,35.92879],[61.26152,35.80749],[61.22719,35.67038],[61.27371,35.61482],[61.58742,35.43803],[61.77693,35.41341],[61.97743,35.4604],[62.05709,35.43803],[62.15871,35.33278],[62.29191,35.25964],[62.29878,35.13312],[62.48006,35.28796],[62.62288,35.22067],[62.74098,35.25432],[62.90853,35.37086],[63.0898,35.43131],[63.12276,35.53196],[63.10079,35.63024],[63.23262,35.67487],[63.10318,35.81782],[63.12276,35.86208],[63.29579,35.85985],[63.53475,35.90881],[63.56496,35.95106],[63.98519,36.03773],[64.05385,36.10433],[64.43288,36.24401],[64.57295,36.34362],[64.62514,36.44311],[64.61141,36.6351],[64.97945,37.21913],[65.51778,37.23881],[65.64263,37.34388],[65.64137,37.45061],[65.72274,37.55438],[66.30993,37.32409],[66.55743,37.35409],[66.52303,37.39827],[66.65761,37.45497],[66.52852,37.58568],[66.53676,37.80084],[66.67684,37.96776],[66.56697,38.0435],[66.41042,38.02403],[66.24013,38.16238],[65.83913,38.25733],[65.55873,38.29052],[64.32576,38.98691],[64.19086,38.95561],[63.70778,39.22349],[63.6913,39.27666],[62.43337,39.98528],[62.34273,40.43206],[62.11751,40.58242],[61.87856,41.12257],[61.4446,41.29407],[61.39732,41.19873],[61.33199,41.14946],[61.22212,41.14946],[61.03261,41.25691],[60.5078,41.21694],[60.06581,41.4363],[60.18117,41.60082],[60.06032,41.76287],[60.08504,41.80997],[60.33223,41.75058],[59.95046,41.97966],[60.0356,42.01028],[60.04659,42.08982],[59.96419,42.1428],[60.00539,42.212],[59.94633,42.27655],[59.4341,42.29738],[59.2955,42.37064],[59.17317,42.52248],[58.93422,42.5407],[58.6266,42.79314],[58.57991,42.64988],[58.27504,42.69632],[58.14321,42.62159],[58.29427,42.56497],[58.51674,42.30348],[58.40688,42.29535],[58.3492,42.43335],[57.99214,42.50021],[57.90975,42.4374],[57.92897,42.24047],[57.84932,42.18555],[57.6296,42.16519],[57.30275,42.14076],[57.03633,41.92043],[56.96218,41.80383],[57.03359,41.41777],[57.13796,41.36625],[57.03423,41.25435],[56.00314,41.32584],[55.45471,41.25609],[54.95182,41.92424],[54.20635,42.38477],[52.97575,42.1308],[52.47884,41.78034],[52.26048,41.69249],[51.7708,40.29239]]]}},{"type":"Feature","properties":{"id":"TL"},"geometry":{"type":"Polygon","coordinates":[[[124.04286,-9.34243],[124.10539,-9.41206],[124.14517,-9.42324],[124.21247,-9.36904],[124.28115,-9.42189],[124.28115,-9.50453],[124.3535,-9.48493],[124.35258,-9.43002],[124.38554,-9.3582],[124.45971,-9.30263],[124.46701,-9.13002],[124.94011,-8.85617],[124.97742,-9.08128],[125.11764,-8.96359],[125.18632,-9.03142],[125.18907,-9.16434],[125.09434,-9.19669],[125.04044,-9.17093],[124.97892,-9.19281],[125.09025,-9.46406],[125.68138,-9.85176],[127.55165,-9.05052],[127.53551,-8.41485],[127.21788,-8.22363],[125.87691,-8.31789],[125.65946,-8.06136],[125.31127,-8.22976],[124.92337,-8.75859],[124.33472,-9.11416],[124.04628,-9.22671],[124.04286,-9.34243]]]}},{"type":"Feature","properties":{"id":"TN"},"geometry":{"type":"Polygon","coordinates":[[[7.52851,34.06493],[7.54088,33.7726],[7.73687,33.42114],[7.83028,33.18851],[8.11433,33.10175],[8.1179,33.05086],[8.31895,32.83483],[8.35999,32.50101],[9.07483,32.07865],[9.55544,30.23971],[9.76848,30.34366],[9.88152,30.34074],[10.29516,30.90337],[10.12239,31.42098],[10.31364,31.72648],[10.48497,31.72956],[10.62788,31.96629],[10.7315,31.97235],[11.04234,32.2145],[11.53898,32.4138],[11.57828,32.48013],[11.46037,32.6307],[11.51549,33.09826],[11.55852,33.1409],[11.56255,33.16754],[11.66543,33.34642],[12.02012,35.25036],[11.2718,37.6713],[7.89009,38.19924],[8.59123,37.14286],[8.64044,36.9401],[8.62972,36.86499],[8.67706,36.8364],[8.57613,36.78062],[8.46537,36.7706],[8.47609,36.66607],[8.16167,36.48817],[8.18936,36.44939],[8.40731,36.42208],[8.2626,35.91733],[8.26472,35.73669],[8.35371,35.66373],[8.36086,35.47774],[8.30329,35.29884],[8.47318,35.23376],[8.3555,35.10007],[8.30727,34.95378],[8.25189,34.92009],[8.29655,34.72798],[8.20482,34.57575],[7.86264,34.3987],[7.81242,34.21841],[7.74207,34.16492],[7.66174,34.20167],[7.52851,34.06493]]]}},{"type":"Feature","properties":{"id":"TR"},"geometry":{"type":"Polygon","coordinates":[[[25.61285,40.17161],[25.94257,39.39358],[26.43357,39.43096],[26.70773,39.0312],[26.61814,38.81372],[26.21136,38.65436],[26.32173,38.48731],[26.24183,38.44695],[26.21136,38.17558],[27.05537,37.9131],[27.16428,37.72343],[26.99377,37.69034],[26.95583,37.64989],[27.14757,37.32],[27.20312,36.94571],[27.45627,36.9008],[27.24613,36.71622],[27.46117,36.53789],[27.89482,36.69898],[27.95037,36.46155],[28.23708,36.56812],[29.30783,36.01033],[29.48192,36.18377],[29.61002,36.1731],[29.61805,36.14179],[29.69611,36.10365],[29.73302,35.92555],[32.82353,35.70297],[35.51152,36.10954],[35.931,35.92109],[35.98499,35.94107],[36.00514,35.94113],[36.01844,35.92403],[35.99829,35.88242],[36.11827,35.85923],[36.13919,35.83692],[36.14029,35.81015],[36.1623,35.80925],[36.17441,35.92076],[36.19973,35.95195],[36.25366,35.96264],[36.27678,35.94839],[36.29769,35.96086],[36.28338,36.00273],[36.30099,36.00985],[36.33956,35.98687],[36.37474,36.01163],[36.39206,36.22088],[36.4617,36.20461],[36.50463,36.2419],[36.6125,36.22592],[36.68672,36.23677],[36.65653,36.33861],[36.6081,36.33772],[36.54206,36.49539],[36.58829,36.58295],[36.57398,36.65186],[36.62681,36.71189],[36.61581,36.74629],[36.66727,36.82901],[36.99557,36.75997],[36.99886,36.74012],[37.04399,36.73483],[37.04619,36.71101],[37.01647,36.69512],[37.02088,36.66422],[37.08279,36.63495],[37.10894,36.6704],[37.16177,36.66069],[37.21988,36.6736],[37.47253,36.63243],[37.49103,36.66904],[37.68048,36.75065],[37.81974,36.76055],[38.21064,36.91842],[38.38859,36.90064],[38.55908,36.84429],[38.74042,36.70629],[39.03217,36.70911],[39.21538,36.66834],[39.81589,36.75538],[40.69136,37.0996],[40.90856,37.13147],[41.21937,37.07665],[41.515,37.08084],[42.00894,37.17209],[42.18225,37.28569],[42.19301,37.31323],[42.2112,37.32491],[42.22257,37.31395],[42.22381,37.30238],[42.20454,37.28715],[42.21548,37.28026],[42.23683,37.2863],[42.26039,37.27017],[42.2824,37.2798],[42.34735,37.22548],[42.32313,37.17814],[42.35724,37.10998],[42.56725,37.14878],[42.78887,37.38615],[42.93705,37.32015],[43.11403,37.37436],[43.30083,37.30629],[43.33508,37.33105],[43.50787,37.24436],[43.56702,37.25675],[43.63085,37.21957],[43.7009,37.23692],[43.8052,37.22825],[43.82699,37.19477],[43.84878,37.22205],[43.90949,37.22453],[44.02002,37.33229],[44.13521,37.32486],[44.2613,37.25055],[44.27998,37.16501],[44.22239,37.15756],[44.18503,37.09551],[44.25975,36.98119],[44.30645,36.97373],[44.35937,37.02843],[44.35315,37.04955],[44.38117,37.05825],[44.42631,37.05825],[44.63179,37.19229],[44.76698,37.16162],[44.78319,37.1431],[44.7868,37.16644],[44.75986,37.21549],[44.81021,37.2915],[44.58449,37.45018],[44.61401,37.60165],[44.56887,37.6429],[44.62096,37.71985],[44.55498,37.783],[44.45948,37.77065],[44.3883,37.85433],[44.22509,37.88859],[44.42476,38.25763],[44.50115,38.33939],[44.44386,38.38295],[44.38309,38.36117],[44.3119,38.37887],[44.3207,38.49799],[44.32058,38.62752],[44.28065,38.6465],[44.26155,38.71427],[44.30322,38.81581],[44.18863,38.93881],[44.20946,39.13975],[44.1043,39.19842],[44.03667,39.39223],[44.22452,39.4169],[44.29818,39.378],[44.37921,39.4131],[44.42832,39.4131],[44.41849,39.56659],[44.48111,39.61579],[44.47298,39.68788],[44.6137,39.78393],[44.65422,39.72163],[44.71806,39.71124],[44.81043,39.62677],[44.80977,39.65768],[44.75779,39.7148],[44.61845,39.8281],[44.46635,39.97733],[44.26973,40.04866],[44.1778,40.02845],[44.1057,40.03555],[43.92307,40.01787],[43.65688,40.11199],[43.65221,40.14889],[43.71136,40.16673],[43.59928,40.34019],[43.60862,40.43267],[43.54791,40.47413],[43.63664,40.54159],[43.7425,40.66805],[43.74872,40.7365],[43.67712,40.84846],[43.67712,40.93084],[43.58683,40.98961],[43.47319,41.02251],[43.44984,41.0988],[43.4717,41.12611],[43.44973,41.17666],[43.36118,41.2028],[43.23096,41.17536],[43.1945,41.25242],[43.13373,41.25503],[43.21707,41.30331],[43.02956,41.37891],[42.8785,41.50516],[42.84899,41.47265],[42.78995,41.50126],[42.84471,41.58912],[42.72794,41.59714],[42.59202,41.58183],[42.51772,41.43606],[42.26387,41.49346],[41.95134,41.52466],[41.81939,41.43621],[41.7124,41.47417],[41.7148,41.4932],[41.54366,41.52185],[40.89217,41.72528],[34.8305,42.4581],[28.32297,41.98371],[28.02971,41.98066],[27.91479,41.97902],[27.83492,41.99709],[27.81235,41.94803],[27.69949,41.97515],[27.55191,41.90928],[27.52379,41.93756],[27.45478,41.96591],[27.27411,42.10409],[27.22376,42.10152],[27.19251,42.06028],[27.08486,42.08735],[27.03277,42.0809],[26.95638,42.00741],[26.79143,41.97386],[26.62996,41.97644],[26.56051,41.92995],[26.57961,41.90024],[26.53968,41.82653],[26.36952,41.82265],[26.33589,41.76802],[26.32952,41.73637],[26.35957,41.71149],[26.47958,41.67037],[26.5209,41.62592],[26.59196,41.60491],[26.59742,41.48058],[26.61767,41.42281],[26.62997,41.34613],[26.5837,41.32131],[26.5209,41.33993],[26.39861,41.25053],[26.32259,41.24929],[26.31928,41.07386],[26.3606,41.02027],[26.33297,40.98388],[26.35894,40.94292],[26.32259,40.94042],[26.28623,40.93005],[26.29441,40.89119],[26.26169,40.9168],[26.20856,40.86048],[26.21351,40.83298],[26.15685,40.80709],[26.12854,40.77339],[26.12495,40.74283],[26.08638,40.73214],[26.0754,40.72772],[26.03489,40.73051],[25.94795,40.72797],[26.04292,40.3958],[25.61285,40.17161]]]}},{"type":"Feature","properties":{"id":"TW"},"geometry":{"type":"Polygon","coordinates":[[[118.09488,24.38193],[118.179,24.33015],[118.41371,24.06775],[120.69238,21.52331],[121.8109,21.77688],[121.75634,23.51406],[122.32924,25.44232],[122.26612,25.98197],[120.49232,25.22863],[118.56434,24.49266],[118.42453,24.54644],[118.35291,24.51645],[118.28244,24.51231],[118.09488,24.38193]]]}},{"type":"Feature","properties":{"id":"TZ"},"geometry":{"type":"Polygon","coordinates":[[[29.43673,-4.44845],[29.52552,-6.2731],[30.2567,-7.14121],[30.79243,-8.27382],[31.00796,-8.58615],[31.37533,-8.60769],[31.57147,-8.70619],[31.57147,-8.81388],[31.71158,-8.91386],[31.81587,-8.88618],[31.94663,-8.93846],[31.94196,-9.02303],[31.98866,-9.07069],[32.08206,-9.04609],[32.16146,-9.05993],[32.25486,-9.13371],[32.43543,-9.11988],[32.49147,-9.14754],[32.53661,-9.24281],[32.75611,-9.28583],[32.76233,-9.31963],[32.95389,-9.40138],[32.99397,-9.36712],[33.14925,-9.49322],[33.31581,-9.48554],[33.48052,-9.62442],[33.76677,-9.58516],[33.93298,-9.71647],[33.9638,-9.62206],[33.95829,-9.54066],[34.03865,-9.49398],[34.54499,-10.0678],[34.51911,-10.12279],[34.57581,-10.56271],[34.65946,-10.6828],[34.67047,-10.93796],[34.61161,-11.01611],[34.63305,-11.11731],[34.79375,-11.32245],[34.91153,-11.39799],[34.96296,-11.57354],[35.63599,-11.55927],[35.82767,-11.41081],[36.19094,-11.57593],[36.19094,-11.70008],[36.62068,-11.72884],[36.80309,-11.56836],[37.3936,-11.68949],[37.76614,-11.53352],[37.8388,-11.3123],[37.93618,-11.26228],[38.21598,-11.27289],[38.47258,-11.4199],[38.88996,-11.16978],[39.24395,-11.17433],[39.58249,-10.96043],[40.00295,-10.80255],[40.44265,-10.4618],[40.74206,-10.25691],[40.14328,-4.64201],[39.62121,-4.68136],[39.44306,-4.93877],[39.21631,-4.67835],[37.81321,-3.69179],[37.75036,-3.54243],[37.63099,-3.50723],[37.5903,-3.42735],[37.71745,-3.304],[37.67199,-3.06222],[34.0824,-1.02264],[34.03084,-1.05101],[34.02286,-1.00779],[33.93107,-0.99298],[30.80408,-0.99911],[30.76635,-0.9852],[30.70631,-1.01175],[30.64166,-1.06601],[30.47194,-1.0555],[30.45116,-1.10641],[30.50889,-1.16412],[30.57123,-1.33264],[30.71974,-1.43244],[30.84079,-1.64652],[30.80802,-1.91477],[30.89303,-2.08223],[30.83915,-2.35795],[30.54501,-2.41404],[30.41789,-2.66266],[30.52747,-2.65841],[30.40662,-2.86151],[30.4987,-2.9573],[30.57926,-2.89791],[30.6675,-2.98987],[30.83823,-2.97837],[30.84165,-3.25152],[30.45915,-3.56532],[30.22042,-4.01738],[30.03323,-4.26631],[29.88172,-4.35743],[29.82885,-4.36153],[29.77289,-4.41733],[29.75109,-4.45836],[29.63827,-4.44681],[29.43673,-4.44845]]]}},{"type":"Feature","properties":{"id":"UG"},"geometry":{"type":"Polygon","coordinates":[[[29.58388,-0.89821],[29.59061,-1.39016],[29.82657,-1.31187],[29.912,-1.48269],[30.16369,-1.34303],[30.35212,-1.06896],[30.47194,-1.0555],[30.64166,-1.06601],[30.70631,-1.01175],[30.76635,-0.9852],[30.80408,-0.99911],[33.93107,-0.99298],[33.9264,-0.54188],[33.98449,-0.13079],[33.90936,0.10581],[34.10067,0.36372],[34.08727,0.44713],[34.11408,0.48884],[34.13493,0.58118],[34.20196,0.62289],[34.27345,0.63182],[34.31516,0.75693],[34.40041,0.80266],[34.43349,0.85254],[34.52369,1.10692],[34.57427,1.09868],[34.58029,1.14712],[34.67562,1.21265],[34.80223,1.22754],[34.82606,1.26626],[34.82606,1.30944],[34.7918,1.36752],[34.87819,1.5596],[34.92734,1.56109],[34.9899,1.6668],[34.98692,1.97348],[34.90947,2.42447],[34.95267,2.47209],[34.77244,2.70272],[34.78137,2.76223],[34.73967,2.85447],[34.65774,2.8753],[34.60114,2.93034],[34.56242,3.11478],[34.45815,3.18319],[34.40006,3.37949],[34.41794,3.44342],[34.39112,3.48802],[34.44922,3.51627],[34.45815,3.67385],[34.15429,3.80464],[34.06046,4.15235],[33.9873,4.23316],[33.51264,3.75068],[33.18356,3.77812],[33.02852,3.89296],[32.89746,3.81339],[32.72021,3.77327],[32.41337,3.748],[32.20782,3.6053],[32.19888,3.50867],[32.08866,3.53543],[32.08491,3.56287],[32.05187,3.589],[31.95907,3.57408],[31.96205,3.6499],[31.86821,3.78664],[31.81459,3.82083],[31.72075,3.74354],[31.50776,3.63652],[31.50478,3.67814],[31.29476,3.8015],[31.16666,3.79853],[30.97601,3.693],[30.85153,3.48867],[30.94081,3.50847],[30.93486,3.40737],[30.84251,3.26908],[30.77101,3.04897],[30.8574,2.9508],[30.8857,2.83923],[30.75612,2.5863],[30.74271,2.43601],[30.83059,2.42559],[30.91102,2.33332],[30.96911,2.41071],[31.06593,2.35862],[31.07934,2.30207],[31.12104,2.27676],[31.1985,2.29462],[31.20148,2.2217],[31.28042,2.17853],[31.30127,2.11006],[30.48503,1.21675],[30.24671,1.14974],[30.22139,0.99635],[30.1484,0.89805],[29.98307,0.84295],[29.95477,0.64486],[29.97413,0.52124],[29.87284,0.39166],[29.81922,0.16824],[29.77454,0.16675],[29.7224,0.07291],[29.72687,-0.08051],[29.65091,-0.46777],[29.67474,-0.47969],[29.67176,-0.55714],[29.62708,-0.71055],[29.63006,-0.8997],[29.58388,-0.89821]]]}},{"type":"Feature","properties":{"id":"UA"},"geometry":{"type":"Polygon","coordinates":[[[22.14689,48.4005],[22.2083,48.42534],[22.38133,48.23726],[22.49806,48.25189],[22.59007,48.15121],[22.58733,48.10813],[22.66835,48.09162],[22.73427,48.12005],[22.81804,48.11363],[22.87847,48.04665],[22.84276,47.98602],[22.89849,47.95851],[22.94301,47.96672],[22.92241,48.02002],[23.0158,47.99338],[23.08858,48.00716],[23.1133,48.08061],[23.15999,48.12188],[23.27397,48.08245],[23.33577,48.0237],[23.4979,47.96858],[23.52803,48.01818],[23.5653,48.00499],[23.63894,48.00293],[23.66262,47.98786],[23.75188,47.99705],[23.80904,47.98142],[23.8602,47.9329],[23.89352,47.94512],[23.94192,47.94868],[23.96337,47.96672],[23.98553,47.96076],[24.00801,47.968],[24.02999,47.95087],[24.06466,47.95317],[24.11281,47.91487],[24.22566,47.90231],[24.34926,47.9244],[24.43578,47.97131],[24.61994,47.95062],[24.70632,47.84428],[24.81893,47.82031],[24.88896,47.7234],[25.11144,47.75203],[25.23778,47.89403],[25.63878,47.94924],[25.77723,47.93919],[26.05901,47.9897],[26.17711,47.99246],[26.33504,48.18418],[26.55202,48.22445],[26.62823,48.25804],[26.6839,48.35828],[26.79239,48.29071],[26.82809,48.31629],[26.71274,48.40388],[26.85556,48.41095],[26.93384,48.36558],[27.03821,48.37653],[27.0231,48.42485],[27.08078,48.43214],[27.13434,48.37288],[27.27855,48.37534],[27.32159,48.4434],[27.37604,48.44398],[27.37741,48.41026],[27.44333,48.41209],[27.46942,48.454],[27.5889,48.49224],[27.59027,48.46311],[27.6658,48.44034],[27.74422,48.45926],[27.79225,48.44244],[27.81902,48.41874],[27.87533,48.4037],[27.88391,48.36699],[27.95883,48.32368],[28.04527,48.32661],[28.09873,48.3124],[28.07504,48.23494],[28.17666,48.25963],[28.19314,48.20749],[28.2856,48.23202],[28.32508,48.23384],[28.35519,48.24957],[28.36996,48.20543],[28.34912,48.1787],[28.30586,48.1597],[28.30609,48.14018],[28.34009,48.13147],[28.38712,48.17567],[28.43701,48.15832],[28.42454,48.12047],[28.48428,48.0737],[28.53921,48.17453],[28.69896,48.13106],[28.85232,48.12506],[28.8414,48.03392],[28.9306,47.96255],[29.1723,47.99013],[29.19839,47.89261],[29.27804,47.88893],[29.20663,47.80367],[29.27255,47.79953],[29.22242,47.73607],[29.22414,47.60012],[29.11743,47.55001],[29.18603,47.43387],[29.3261,47.44664],[29.39889,47.30179],[29.47854,47.30366],[29.48678,47.36043],[29.5733,47.36508],[29.59665,47.25521],[29.54996,47.24962],[29.57696,47.13581],[29.49732,47.12878],[29.53044,47.07851],[29.61038,47.09932],[29.62137,47.05069],[29.57056,46.94766],[29.72986,46.92234],[29.75458,46.8604],[29.87405,46.88199],[29.98814,46.82358],[29.94522,46.80055],[29.9743,46.75325],[29.94409,46.56002],[29.88916,46.54302],[30.02511,46.45132],[30.16794,46.40967],[30.09103,46.38694],[29.94114,46.40114],[29.88329,46.35851],[29.74496,46.45605],[29.66359,46.4215],[29.6763,46.36041],[29.5939,46.35472],[29.49914,46.45889],[29.35357,46.49505],[29.24886,46.37912],[29.23547,46.55435],[29.02409,46.49582],[29.01241,46.46177],[28.9306,46.45699],[29.004,46.31495],[28.98478,46.31803],[28.94953,46.25852],[29.06656,46.19716],[28.94643,46.09176],[29.00613,46.04962],[28.98004,46.00385],[28.74383,45.96664],[28.78503,45.83475],[28.69852,45.81753],[28.70401,45.78019],[28.52823,45.73803],[28.47879,45.66994],[28.51587,45.6613],[28.54196,45.58062],[28.49252,45.56716],[28.51449,45.49982],[28.43072,45.48538],[28.41836,45.51715],[28.30201,45.54744],[28.21139,45.46895],[28.28504,45.43907],[28.34554,45.32102],[28.5735,45.24759],[28.71358,45.22631],[28.78911,45.24179],[28.81383,45.3384],[28.94292,45.28045],[28.96077,45.33164],[29.24779,45.43388],[29.42632,45.44545],[29.59798,45.38857],[29.68175,45.26885],[29.65428,45.25629],[29.69272,45.19227],[30.04414,45.08461],[31.62627,45.50633],[33.54017,46.0123],[33.59087,46.06013],[33.57318,46.10317],[33.61467,46.13561],[33.63854,46.14147],[33.61517,46.22615],[33.646,46.23028],[33.74047,46.18555],[33.79715,46.20482],[33.85234,46.19863],[33.91549,46.15938],[34.05272,46.10838],[34.07311,46.11769],[34.12929,46.10494],[34.181,46.06804],[34.25111,46.0532],[34.33912,46.06114],[34.41221,46.00245],[34.44155,45.95995],[34.48729,45.94267],[34.52011,45.95097],[34.55889,45.99347],[34.60861,45.99347],[34.66679,45.97136],[34.75479,45.90705],[34.80153,45.90047],[34.79905,45.81009],[34.96015,45.75634],[35.23066,45.79231],[37.62608,46.82615],[38.12112,46.86078],[38.3384,46.98085],[38.22955,47.12069],[38.23049,47.2324],[38.32112,47.2585],[38.33074,47.30508],[38.22225,47.30788],[38.28954,47.39255],[38.28679,47.53552],[38.35062,47.61631],[38.76379,47.69346],[38.79628,47.81109],[38.87979,47.87719],[39.73935,47.82876],[39.82213,47.96396],[39.77544,48.04206],[39.88256,48.04482],[39.83724,48.06501],[39.94847,48.22811],[40.00752,48.22445],[39.99241,48.31768],[39.97325,48.31399],[39.9693,48.29904],[39.95248,48.29972],[39.91465,48.26743],[39.90041,48.3049],[39.84273,48.30947],[39.84136,48.33321],[39.94847,48.35055],[39.88794,48.44226],[39.86196,48.46633],[39.84548,48.57821],[39.79764,48.58668],[39.67226,48.59368],[39.71765,48.68673],[39.73104,48.7325],[39.79466,48.83739],[39.97182,48.79398],[40.08168,48.87443],[40.03636,48.91957],[39.98967,48.86901],[39.78368,48.91596],[39.74874,48.98675],[39.72649,48.9754],[39.71353,48.98959],[39.6683,48.99454],[39.6836,49.05121],[39.93437,49.05709],[40.01988,49.1761],[40.22176,49.25683],[40.18331,49.34996],[40.14912,49.37681],[40.1141,49.38798],[40.03087,49.45452],[40.03636,49.52321],[40.16683,49.56865],[40.13249,49.61672],[39.84548,49.56064],[39.65047,49.61761],[39.59142,49.73758],[39.44496,49.76067],[39.27968,49.75976],[39.1808,49.88911],[38.9391,49.79524],[38.90477,49.86787],[38.73311,49.90238],[38.68677,50.00904],[38.65688,49.97176],[38.35408,50.00664],[38.32524,50.08866],[38.18517,50.08161],[38.21675,49.98104],[38.02999,49.90592],[38.02999,49.94482],[37.90776,50.04194],[37.79515,50.08425],[37.75807,50.07896],[37.61113,50.21976],[37.62879,50.24481],[37.62486,50.29966],[37.47243,50.36277],[37.48204,50.46079],[37.08468,50.34935],[36.91762,50.34963],[36.69377,50.26982],[36.64571,50.218],[36.56655,50.2413],[36.58371,50.28563],[36.47817,50.31457],[36.30101,50.29088],[36.20763,50.3943],[36.06893,50.45205],[35.8926,50.43829],[35.80388,50.41356],[35.73659,50.35489],[35.61711,50.35707],[35.58003,50.45117],[35.47463,50.49247],[35.39464,50.64751],[35.48116,50.66405],[35.47704,50.77274],[35.41367,50.80227],[35.39307,50.92145],[35.32598,50.94524],[35.40837,51.04119],[35.31774,51.08434],[35.20375,51.04723],[35.12685,51.16191],[35.14058,51.23162],[34.97304,51.2342],[34.82472,51.17483],[34.6874,51.18],[34.6613,51.25053],[34.38802,51.2746],[34.31661,51.23936],[34.23009,51.26429],[34.33446,51.363],[34.22048,51.4187],[34.30562,51.5205],[34.17599,51.63253],[34.07765,51.67065],[34.42922,51.72852],[34.41136,51.82793],[34.09413,52.00835],[34.11199,52.14087],[34.05239,52.20132],[33.78789,52.37204],[33.55718,52.30324],[33.48027,52.31499],[33.51323,52.35779],[33.18913,52.3754],[32.89937,52.2461],[32.85405,52.27888],[32.69475,52.25535],[32.54781,52.32423],[32.3528,52.32842],[32.38988,52.24946],[32.33083,52.23685],[32.34044,52.1434],[32.2777,52.10266],[32.23331,52.08085],[32.08813,52.03319],[31.92159,52.05144],[31.96141,52.08015],[31.85018,52.11305],[31.81722,52.09955],[31.7822,52.11406],[31.38326,52.12991],[31.25142,52.04131],[31.13332,52.1004],[30.95589,52.07775],[30.90897,52.00699],[30.76443,51.89739],[30.68804,51.82806],[30.51946,51.59649],[30.64992,51.35014],[30.56203,51.25655],[30.36153,51.33984],[30.34642,51.42555],[30.17888,51.51025],[29.77376,51.4461],[29.7408,51.53417],[29.54372,51.48372],[29.49773,51.39814],[29.42357,51.4187],[29.32881,51.37843],[29.25191,51.49828],[29.25603,51.57089],[29.20659,51.56918],[29.16402,51.64679],[29.1187,51.65872],[28.99098,51.56833],[28.95528,51.59222],[28.81795,51.55552],[28.76027,51.48802],[28.78224,51.45294],[28.75615,51.41442],[28.73143,51.46236],[28.69161,51.44695],[28.64429,51.5664],[28.47051,51.59734],[28.37592,51.54505],[28.23452,51.66988],[28.10658,51.57857],[27.95827,51.56065],[27.91844,51.61952],[27.85253,51.62293],[27.76052,51.47604],[27.67125,51.50854],[27.71932,51.60672],[27.55727,51.63486],[27.51058,51.5854],[27.47212,51.61184],[27.24828,51.60161],[27.26613,51.65957],[27.20948,51.66713],[27.20602,51.77291],[26.99422,51.76933],[26.9489,51.73788],[26.80043,51.75777],[26.69759,51.82284],[26.46962,51.80501],[26.39367,51.87315],[26.19084,51.86781],[26.00408,51.92967],[25.83217,51.92587],[25.80574,51.94556],[25.73673,51.91973],[25.46163,51.92205],[25.20228,51.97143],[24.98784,51.91273],[24.37123,51.88222],[24.29021,51.80841],[24.3163,51.75063],[24.13075,51.66979],[23.99907,51.58369],[23.8741,51.59734],[23.91118,51.63316],[23.7766,51.66809],[23.60906,51.62122],[23.6736,51.50255],[23.62751,51.50512],[23.69905,51.40871],[23.63858,51.32182],[23.80678,51.18405],[23.90376,51.07697],[23.92217,51.00836],[24.04576,50.90196],[24.14524,50.86128],[24.0952,50.83262],[23.99254,50.83847],[23.95925,50.79271],[24.0595,50.71625],[24.0996,50.60752],[24.07048,50.5071],[24.03668,50.44507],[23.99563,50.41289],[23.79445,50.40481],[23.71382,50.38248],[23.67635,50.33385],[23.28221,50.0957],[22.99329,49.84249],[22.83179,49.69875],[22.80261,49.69098],[22.78304,49.65543],[22.64534,49.53094],[22.69444,49.49378],[22.748,49.32759],[22.72009,49.20288],[22.86336,49.10513],[22.89122,49.00725],[22.56155,49.08865],[22.54338,49.01424],[22.48296,48.99172],[22.42934,48.92857],[22.34151,48.68893],[22.21379,48.6218],[22.16023,48.56548],[22.14689,48.4005]]]}},{"type":"Feature","properties":{"id":"UY"},"geometry":{"type":"Polygon","coordinates":[[[-58.44442,-33.84033],[-58.34425,-34.15035],[-57.83001,-34.69099],[-55.71154,-35.78518],[-53.54511,-34.54062],[-53.18243,-33.86894],[-53.37138,-33.74313],[-53.39593,-33.75169],[-53.44031,-33.69344],[-53.52794,-33.68908],[-53.53459,-33.16843],[-53.1111,-32.71147],[-53.37671,-32.57005],[-53.39572,-32.58596],[-53.76024,-32.0751],[-54.17384,-31.86168],[-55.50821,-30.91349],[-55.50841,-30.9027],[-55.51862,-30.89828],[-55.52712,-30.89997],[-55.53276,-30.90218],[-55.53431,-30.89714],[-55.54572,-30.89051],[-55.55218,-30.88193],[-55.55373,-30.8732],[-55.5634,-30.8686],[-55.58866,-30.84117],[-55.87388,-31.05053],[-56.4619,-30.38457],[-56.4795,-30.3899],[-56.49267,-30.39471],[-56.90236,-30.02578],[-57.22502,-30.26121],[-57.65132,-30.19229],[-57.61478,-30.25165],[-57.64859,-30.35095],[-57.89115,-30.49572],[-57.8024,-30.77193],[-57.89476,-30.95994],[-57.86729,-31.06352],[-57.9908,-31.34924],[-57.98127,-31.3872],[-58.07569,-31.44916],[-58.0023,-31.53084],[-58.00076,-31.65016],[-58.20252,-31.86966],[-58.10036,-32.25338],[-58.22362,-32.52416],[-58.1224,-32.98842],[-58.40475,-33.11777],[-58.44442,-33.84033]]]}},{"type":"Feature","properties":{"id":"IN-UT"},"geometry":{"type":"Polygon","coordinates":[[[77.5542,30.40278],[77.92945,30.24661],[77.70629,29.8722],[77.80792,29.67075],[77.94181,29.71489],[77.98507,29.5418],[78.33732,29.79536],[78.49044,29.73874],[78.52683,29.6248],[78.91067,29.45335],[78.71429,29.32053],[79.00268,29.12127],[79.0686,29.15276],[79.29416,28.95858],[79.40711,28.92854],[79.41089,28.85339],[79.66529,28.85369],[79.79026,28.88736],[79.96673,28.70233],[80.06957,28.82763],[80.05743,28.91479],[80.18085,29.13649],[80.23178,29.11626],[80.26602,29.13938],[80.24112,29.21414],[80.28626,29.20327],[80.31428,29.30784],[80.24322,29.44299],[80.37939,29.57098],[80.41858,29.63581],[80.38428,29.68513],[80.36803,29.73865],[80.41554,29.79451],[80.43458,29.80466],[80.48997,29.79566],[80.56247,29.86661],[80.56957,29.88176],[80.60226,29.95732],[80.67076,29.95732],[80.8778,30.13384],[80.93695,30.18229],[81.03953,30.20059],[80.83343,30.32023],[80.54504,30.44936],[80.20721,30.58541],[79.93255,30.88288],[79.59884,30.93943],[79.22805,31.34963],[79.14016,31.43403],[79.01931,31.42817],[78.77898,31.31209],[78.99581,31.10821],[78.292,31.28676],[77.80174,31.05822],[77.69016,30.76927],[77.76946,30.63111],[77.7238,30.59359],[77.80757,30.52086],[77.5542,30.40278]]]}},{"type":"Feature","properties":{"id":"VA"},"geometry":{"type":"Polygon","coordinates":[[[12.44582,41.90194],[12.44834,41.90095],[12.45181,41.90056],[12.45446,41.90028],[12.45435,41.90143],[12.45626,41.90172],[12.45691,41.90125],[12.4577,41.90115],[12.45834,41.90174],[12.45826,41.90281],[12.45755,41.9033],[12.45762,41.9058],[12.45561,41.90629],[12.45543,41.90738],[12.45091,41.90625],[12.44984,41.90545],[12.44815,41.90326],[12.44582,41.90194]]]}},{"type":"Feature","properties":{"id":"VE"},"geometry":{"type":"Polygon","coordinates":[[[-73.36905,9.16636],[-73.02119,9.27584],[-72.94052,9.10663],[-72.77415,9.10165],[-72.65474,8.61428],[-72.4042,8.36513],[-72.36987,8.19976],[-72.35163,8.01163],[-72.39137,8.03534],[-72.47213,7.96106],[-72.48801,7.94329],[-72.48183,7.92909],[-72.47042,7.92306],[-72.45806,7.91141],[-72.46183,7.90682],[-72.44454,7.86031],[-72.46763,7.79518],[-72.47827,7.65604],[-72.45321,7.57232],[-72.47415,7.48928],[-72.43132,7.40034],[-72.19437,7.37034],[-72.04895,7.03837],[-71.82441,7.04314],[-71.44118,7.02116],[-71.42212,7.03854],[-71.37234,7.01588],[-71.03941,6.98163],[-70.7596,7.09799],[-70.10716,6.96516],[-69.41843,6.1072],[-67.60654,6.2891],[-67.4625,6.20625],[-67.43513,5.98835],[-67.58558,5.84537],[-67.63914,5.64963],[-67.59141,5.5369],[-67.83341,5.31104],[-67.85358,4.53249],[-67.62671,3.74303],[-67.50067,3.75812],[-67.30945,3.38393],[-67.85862,2.86727],[-67.85862,2.79173],[-67.65696,2.81691],[-67.21967,2.35778],[-66.85795,1.22998],[-66.28507,0.74585],[-65.6727,1.01353],[-65.50158,0.92086],[-65.57288,0.62856],[-65.11657,1.12046],[-64.38932,1.5125],[-64.34654,1.35569],[-64.08274,1.64792],[-64.06135,1.94722],[-63.39827,2.16098],[-63.39114,2.4317],[-64.0257,2.48156],[-64.02908,2.79797],[-64.48379,3.7879],[-64.84028,4.24665],[-64.72977,4.28931],[-64.57648,4.12576],[-64.14512,4.12932],[-63.99183,3.90172],[-63.86082,3.94796],[-63.70218,3.91417],[-63.67099,4.01731],[-63.50611,3.83592],[-63.42233,3.89995],[-63.4464,3.9693],[-63.21111,3.96219],[-62.98296,3.59935],[-62.7655,3.73099],[-62.74411,4.03331],[-62.57656,4.04754],[-62.44822,4.18621],[-62.13094,4.08309],[-61.54629,4.2822],[-61.48569,4.43149],[-61.29675,4.44216],[-61.31457,4.54167],[-61.15703,4.49839],[-60.98303,4.54167],[-60.86539,4.70512],[-60.5802,4.94312],[-60.73204,5.20931],[-61.4041,5.95304],[-61.15058,6.19558],[-61.20762,6.58174],[-61.13632,6.70922],[-60.54873,6.8631],[-60.39419,6.94847],[-60.28074,7.1162],[-60.44116,7.20817],[-60.54098,7.14804],[-60.63367,7.25061],[-60.59802,7.33194],[-60.71923,7.55817],[-60.64793,7.56877],[-60.51959,7.83373],[-60.38056,7.8302],[-60.02407,8.04557],[-59.97059,8.20791],[-59.83156,8.23261],[-59.80661,8.28906],[-59.85562,8.35213],[-59.98508,8.53046],[-59.54058,8.6862],[-60.89962,9.81445],[-62.08693,10.04435],[-61.62505,11.18974],[-63.73917,11.92623],[-63.25348,15.97077],[-63.85239,16.0256],[-65.4181,12.36848],[-67.89186,12.4116],[-68.01417,11.77722],[-68.33524,11.78151],[-68.99639,11.79035],[-69.4514,12.18025],[-70.24399,12.38063],[-70.34259,12.92535],[-71.19849,12.65801],[-70.92579,11.96275],[-71.3275,11.85],[-71.9675,11.65536],[-72.24983,11.14138],[-72.4767,11.1117],[-72.88002,10.44309],[-72.98085,9.85253],[-73.36905,9.16636]]]}},{"type":"Feature","properties":{"id":"VN"},"geometry":{"type":"Polygon","coordinates":[[[102.14099,22.40092],[102.18712,22.30403],[102.51734,22.02676],[102.49092,21.99002],[102.62301,21.91447],[102.67145,21.65894],[102.74189,21.66713],[102.82115,21.73667],[102.81894,21.83888],[102.85637,21.84501],[102.86077,21.71213],[102.97965,21.74076],[102.98846,21.58936],[102.86297,21.4255],[102.94223,21.46034],[102.88939,21.3107],[102.80794,21.25736],[102.89825,21.24707],[102.97745,21.05821],[103.03469,21.05821],[103.12055,20.89994],[103.21497,20.89832],[103.38032,20.79501],[103.45737,20.82382],[103.68633,20.66324],[103.73478,20.6669],[103.82282,20.8732],[103.98024,20.91531],[104.11121,20.96779],[104.27412,20.91433],[104.63957,20.6653],[104.38199,20.47155],[104.40621,20.3849],[104.47886,20.37459],[104.66158,20.47774],[104.72102,20.40554],[104.62195,20.36633],[104.61315,20.24452],[104.86852,20.14121],[104.91695,20.15567],[104.9874,20.09573],[104.8465,19.91783],[104.8355,19.80395],[104.68359,19.72729],[104.64837,19.62365],[104.53169,19.61743],[104.41281,19.70035],[104.23229,19.70242],[104.06498,19.66926],[104.05617,19.61743],[104.10832,19.51575],[104.06058,19.43484],[103.87125,19.31854],[104.5361,18.97747],[104.64617,18.85668],[105.12829,18.70453],[105.19654,18.64196],[105.1327,18.58355],[105.10408,18.43533],[105.15942,18.38691],[105.38366,18.15315],[105.46292,18.22008],[105.64784,17.96687],[105.60381,17.89356],[105.76612,17.67147],[105.85744,17.63221],[106.09019,17.36399],[106.18991,17.28227],[106.24444,17.24714],[106.29287,17.3018],[106.31929,17.20509],[106.43597,17.01362],[106.50862,16.9673],[106.55045,17.0031],[106.54824,16.92729],[106.51963,16.92097],[106.52183,16.87884],[106.55265,16.86831],[106.55485,16.68704],[106.59013,16.62259],[106.58267,16.6012],[106.61477,16.60713],[106.66052,16.56892],[106.65832,16.47816],[106.74418,16.41904],[106.84104,16.55415],[106.88727,16.52671],[106.88067,16.43594],[106.96638,16.34938],[106.97385,16.30204],[107.02597,16.31132],[107.09091,16.3092],[107.15035,16.26271],[107.14595,16.17816],[107.25822,16.13587],[107.33968,16.05549],[107.44975,16.08511],[107.46296,16.01106],[107.39471,15.88829],[107.34188,15.89464],[107.21419,15.83747],[107.21859,15.74638],[107.27143,15.71459],[107.27583,15.62769],[107.34408,15.62345],[107.3815,15.49832],[107.50699,15.48771],[107.53341,15.40496],[107.62367,15.42193],[107.60605,15.37524],[107.62587,15.2266],[107.58844,15.20111],[107.61926,15.13949],[107.61486,15.0566],[107.46516,15.00982],[107.48277,14.93751],[107.59285,14.87795],[107.51579,14.79282],[107.54361,14.69092],[107.55371,14.628],[107.52102,14.59034],[107.52569,14.54665],[107.48521,14.40346],[107.44941,14.41552],[107.39493,14.32655],[107.40427,14.24509],[107.33577,14.11832],[107.37158,14.07906],[107.35757,14.02319],[107.38247,13.99147],[107.44318,13.99751],[107.46498,13.91593],[107.45252,13.78897],[107.53503,13.73908],[107.61909,13.52577],[107.62843,13.3668],[107.49144,13.01215],[107.49611,12.88926],[107.55993,12.7982],[107.5755,12.52177],[107.55059,12.36824],[107.4463,12.29373],[107.42917,12.24657],[107.34511,12.33327],[107.15831,12.27547],[106.99953,12.08983],[106.92325,12.06548],[106.79405,12.0807],[106.70687,11.96956],[106.4111,11.97413],[106.4687,11.86751],[106.44068,11.86294],[106.44535,11.8279],[106.41577,11.76999],[106.45158,11.68616],[106.44691,11.66787],[106.37219,11.69836],[106.30525,11.67549],[106.26478,11.72122],[106.18539,11.75171],[106.13158,11.73283],[106.06708,11.77761],[106.02038,11.77457],[106.00792,11.7197],[105.95188,11.63738],[105.88962,11.67854],[105.8507,11.66635],[105.80867,11.60536],[105.81645,11.56876],[105.87328,11.55953],[105.88962,11.43605],[105.86782,11.28343],[106.10444,11.07879],[106.1527,11.10476],[106.1757,11.07301],[106.20095,10.97795],[106.14301,10.98176],[106.18539,10.79451],[106.06708,10.8098],[105.94535,10.9168],[105.93403,10.83853],[105.84603,10.85873],[105.86376,10.89839],[105.77751,11.03671],[105.50045,10.94586],[105.42884,10.96878],[105.34011,10.86179],[105.11449,10.96332],[105.08326,10.95656],[105.02722,10.89236],[105.09571,10.72722],[104.95094,10.64003],[104.87933,10.52833],[104.59018,10.53073],[104.49869,10.4057],[104.47963,10.43046],[104.43778,10.42386],[103.99198,10.48391],[102.47649,9.66162],[104.81582,8.03101],[109.55486,8.10026],[110.2534,15.19951],[107.44022,18.66249],[108.26073,20.07614],[108.10003,21.47338],[108.0569,21.53604],[108.02926,21.54997],[107.97932,21.54503],[107.97383,21.53961],[107.97074,21.54072],[107.96774,21.53601],[107.95232,21.5388],[107.92652,21.58906],[107.90006,21.5905],[107.86114,21.65128],[107.80355,21.66141],[107.66967,21.60787],[107.56537,21.61945],[107.54047,21.5934],[107.49065,21.59774],[107.49532,21.62958],[107.47197,21.6672],[107.41593,21.64839],[107.38636,21.59774],[107.35989,21.60063],[107.35834,21.6672],[107.29296,21.74674],[107.24625,21.7077],[107.20734,21.71493],[107.10771,21.79879],[107.02615,21.81981],[107.00964,21.85948],[107.06101,21.88982],[107.05634,21.92303],[106.99252,21.95191],[106.97228,21.92592],[106.92714,21.93459],[106.9178,21.97357],[106.81038,21.97934],[106.74345,22.00965],[106.72551,21.97923],[106.69276,21.96013],[106.68274,21.99811],[106.70142,22.02409],[106.6983,22.15102],[106.67495,22.1885],[106.69986,22.22309],[106.6516,22.33977],[106.55976,22.34841],[106.57221,22.37],[106.55665,22.46498],[106.58395,22.474],[106.61269,22.60301],[106.65316,22.5757],[106.71698,22.58432],[106.72321,22.63606],[106.76293,22.73491],[106.82404,22.7881],[106.83685,22.8098],[106.81271,22.8226],[106.78422,22.81532],[106.71128,22.85982],[106.71387,22.88296],[106.6734,22.89587],[106.6516,22.86862],[106.60179,22.92884],[106.55976,22.92311],[106.51306,22.94891],[106.49749,22.91164],[106.34961,22.86718],[106.27022,22.87722],[106.19705,22.98475],[106.00179,22.99049],[105.99568,22.94178],[105.90119,22.94168],[105.8726,22.92756],[105.72382,23.06641],[105.57594,23.075],[105.56037,23.16806],[105.49966,23.20669],[105.42805,23.30824],[105.40782,23.28107],[105.32376,23.39684],[105.22569,23.27249],[105.17276,23.28679],[105.11672,23.25247],[105.07002,23.26248],[104.98712,23.19176],[104.96532,23.20463],[104.9486,23.17235],[104.91435,23.18666],[104.87992,23.17141],[104.87382,23.12854],[104.79478,23.12934],[104.8334,23.01484],[104.86765,22.95178],[104.84942,22.93631],[104.77114,22.90017],[104.72755,22.81984],[104.65283,22.83419],[104.60457,22.81841],[104.58122,22.85571],[104.47225,22.75813],[104.35593,22.69353],[104.25683,22.76534],[104.27084,22.8457],[104.11384,22.80363],[104.03734,22.72945],[104.01088,22.51823],[103.99247,22.51958],[103.97384,22.50634],[103.96783,22.51173],[103.96352,22.50584],[103.95191,22.5134],[103.94513,22.52553],[103.93286,22.52703],[103.87904,22.56683],[103.64506,22.79979],[103.56255,22.69499],[103.57812,22.65764],[103.52675,22.59155],[103.43646,22.70648],[103.43179,22.75816],[103.32282,22.8127],[103.28079,22.68063],[103.18895,22.64471],[103.15782,22.59873],[103.17961,22.55705],[103.07843,22.50097],[103.0722,22.44775],[102.9321,22.48659],[102.8636,22.60735],[102.60675,22.73376],[102.57095,22.7036],[102.51802,22.77969],[102.46665,22.77108],[102.42618,22.69212],[102.38415,22.67919],[102.41061,22.64184],[102.25339,22.4607],[102.26428,22.41321],[102.16621,22.43336],[102.14099,22.40092]]]}},{"type":"Feature","properties":{"id":"YE"},"geometry":{"type":"Polygon","coordinates":[[[41.29956,15.565],[42.63806,13.58268],[43.29075,12.79154],[43.32909,12.59711],[43.90659,12.3823],[50.51849,13.0483],[51.12877,12.56479],[52.253,11.68582],[55.69862,12.12478],[53.32998,16.16312],[53.09917,16.67084],[52.81185,17.28568],[52.74267,17.29519],[52.78009,17.35124],[52.00311,19.00083],[49.04884,18.59899],[48.19996,18.20584],[47.58351,17.50366],[47.48245,17.10808],[47.00571,16.94765],[46.76494,17.29151],[46.31018,17.20464],[44.50126,17.47475],[43.70631,17.35762],[43.43005,17.56148],[43.29185,17.53224],[43.22533,17.38343],[43.32653,17.31179],[43.20156,17.25901],[43.17787,17.14717],[43.23967,17.03428],[43.18233,17.02673],[43.1813,16.98438],[43.19328,16.94703],[43.1398,16.90696],[43.18338,16.84852],[43.22012,16.83932],[43.22956,16.80613],[43.24801,16.80613],[43.26303,16.79479],[43.25857,16.75304],[43.21325,16.74416],[43.22066,16.65179],[43.15274,16.67248],[43.11601,16.53166],[42.97215,16.51093],[42.94351,16.49467],[42.94625,16.39721],[42.76801,16.40371],[42.15205,16.40211],[41.37609,16.19728],[41.29956,15.565]]]}},{"type":"Feature","properties":{"id":"ZM"},"geometry":{"type":"Polygon","coordinates":[[[21.97988,-13.00148],[22.00323,-16.18028],[22.17217,-16.50269],[23.20038,-17.47563],[23.47474,-17.62877],[24.23619,-17.47489],[24.32811,-17.49082],[24.38712,-17.46818],[24.5621,-17.52963],[24.70864,-17.49501],[25.00198,-17.58221],[25.26433,-17.79571],[25.51646,-17.86232],[25.6827,-17.81987],[25.85738,-17.91403],[25.85892,-17.97726],[26.08925,-17.98168],[26.0908,-17.93021],[26.21601,-17.88608],[26.55918,-17.99638],[26.68403,-18.07411],[26.74314,-18.0199],[26.89926,-17.98756],[27.14196,-17.81398],[27.30736,-17.60487],[27.61377,-17.34378],[27.62795,-17.24365],[27.83141,-16.96274],[28.73725,-16.5528],[28.76199,-16.51575],[28.81454,-16.48611],[28.8501,-16.04537],[28.9243,-15.93987],[29.01298,-15.93805],[29.21955,-15.76589],[29.4437,-15.68702],[29.8317,-15.6126],[30.35574,-15.6513],[30.41902,-15.62269],[30.22098,-14.99447],[33.24249,-14.00019],[33.16749,-13.93992],[33.07568,-13.98447],[33.02977,-14.05022],[32.99042,-13.95689],[32.88985,-13.82956],[32.79015,-13.80755],[32.76962,-13.77224],[32.84528,-13.71576],[32.7828,-13.64805],[32.68654,-13.64268],[32.66468,-13.60019],[32.68436,-13.55769],[32.73683,-13.57682],[32.84176,-13.52794],[32.86113,-13.47292],[33.0078,-13.19492],[32.98289,-13.12671],[33.02181,-12.88707],[32.96733,-12.88251],[32.94397,-12.76868],[33.05917,-12.59554],[33.18837,-12.61377],[33.28177,-12.54692],[33.37517,-12.54085],[33.54485,-12.35996],[33.47636,-12.32498],[33.3705,-12.34931],[33.25998,-12.14242],[33.33937,-11.91252],[33.32692,-11.59248],[33.24252,-11.59302],[33.23663,-11.40637],[33.29267,-11.43536],[33.29267,-11.3789],[33.39697,-11.15296],[33.25998,-10.88862],[33.28022,-10.84428],[33.47636,-10.78465],[33.70675,-10.56896],[33.54797,-10.36077],[33.53863,-10.20148],[33.31297,-10.05133],[33.37902,-9.9104],[33.36581,-9.81063],[33.31517,-9.82364],[33.2095,-9.61099],[33.12144,-9.58929],[33.10163,-9.66525],[33.05485,-9.61316],[33.00256,-9.63053],[33.00476,-9.5133],[32.95389,-9.40138],[32.76233,-9.31963],[32.75611,-9.28583],[32.53661,-9.24281],[32.49147,-9.14754],[32.43543,-9.11988],[32.25486,-9.13371],[32.16146,-9.05993],[32.08206,-9.04609],[31.98866,-9.07069],[31.94196,-9.02303],[31.94663,-8.93846],[31.81587,-8.88618],[31.71158,-8.91386],[31.57147,-8.81388],[31.57147,-8.70619],[31.37533,-8.60769],[31.00796,-8.58615],[30.79243,-8.27382],[28.88917,-8.4831],[28.9711,-8.66935],[28.38526,-9.23393],[28.36562,-9.30091],[28.52636,-9.35379],[28.51627,-9.44726],[28.56208,-9.49122],[28.68532,-9.78],[28.62795,-9.92942],[28.65032,-10.65133],[28.37241,-11.57848],[28.48357,-11.87532],[29.18592,-12.37921],[29.4992,-12.43843],[29.48404,-12.23604],[29.8139,-12.14898],[29.81551,-13.44683],[29.65078,-13.41844],[29.60531,-13.21685],[29.01918,-13.41353],[28.33199,-12.41375],[27.59932,-12.22123],[27.21025,-11.76157],[27.22541,-11.60323],[27.04351,-11.61312],[26.88687,-12.01868],[26.01777,-11.91488],[25.33058,-11.65767],[25.34069,-11.19707],[24.42612,-11.44975],[24.34528,-11.06816],[24.00027,-10.89356],[24.02603,-11.15368],[23.98804,-12.13149],[24.06672,-12.29058],[23.90937,-12.844],[24.03339,-12.99091],[21.97988,-13.00148]]]}},{"type":"Feature","properties":{"id":"ZW"},"geometry":{"type":"Polygon","coordinates":[[[25.23909,-17.90832],[25.31799,-18.07091],[25.39972,-18.12691],[25.53465,-18.39041],[25.68859,-18.56165],[25.79217,-18.6355],[25.82353,-18.82808],[25.94326,-18.90362],[25.99837,-19.02943],[25.96226,-19.08152],[26.17227,-19.53709],[26.72246,-19.92707],[27.21278,-20.08244],[27.29831,-20.28935],[27.28865,-20.49873],[27.69361,-20.48531],[27.72972,-20.51735],[27.69171,-21.08409],[27.91407,-21.31621],[28.01669,-21.57624],[28.29416,-21.59037],[28.49942,-21.66634],[28.58114,-21.63455],[29.07763,-21.81877],[29.04023,-21.85864],[29.02191,-21.90647],[29.02191,-21.95665],[29.04108,-22.00563],[29.08495,-22.04867],[29.14501,-22.07275],[29.1974,-22.07472],[29.24648,-22.05967],[29.3533,-22.18363],[29.37703,-22.19581],[29.64609,-22.12917],[29.76848,-22.14128],[29.92242,-22.19408],[30.13147,-22.30841],[30.2265,-22.2961],[30.28351,-22.35587],[30.38614,-22.34533],[30.48686,-22.31368],[30.6294,-22.32599],[30.86696,-22.28907],[31.08932,-22.34884],[31.16344,-22.32599],[31.30611,-22.422],[31.38336,-22.36919],[32.41234,-21.31246],[32.48236,-21.32873],[32.37115,-21.133],[32.51644,-20.91929],[32.48122,-20.63319],[32.55167,-20.56312],[32.66174,-20.56106],[32.85987,-20.27841],[32.85987,-20.16686],[32.93032,-20.03868],[33.01178,-20.02007],[33.06461,-19.77787],[32.95013,-19.67219],[32.84666,-19.68462],[32.84446,-19.48343],[32.78282,-19.47513],[32.77966,-19.36098],[32.85107,-19.29238],[32.87088,-19.09279],[32.84006,-19.0262],[32.72118,-19.02204],[32.69917,-18.94293],[32.73439,-18.92628],[32.70137,-18.84712],[32.82465,-18.77419],[32.9017,-18.7992],[32.95013,-18.69079],[32.88629,-18.58023],[32.88629,-18.51344],[33.02278,-18.4696],[33.03159,-18.35054],[32.94133,-17.99705],[33.0492,-17.60298],[32.98536,-17.55891],[32.96554,-17.48964],[33.0426,-17.3468],[33.00517,-17.30477],[32.96554,-17.11971],[32.84113,-16.92259],[32.91051,-16.89446],[32.97655,-16.70689],[32.78943,-16.70267],[32.69917,-16.66893],[32.71017,-16.59932],[32.42838,-16.4727],[32.28529,-16.43892],[32.02772,-16.43892],[31.91324,-16.41569],[31.90223,-16.34388],[31.67988,-16.19595],[31.42451,-16.15154],[31.30563,-16.01193],[31.13171,-15.98019],[30.97761,-16.05848],[30.91597,-15.99924],[30.42568,-15.9962],[30.41902,-15.62269],[30.35574,-15.6513],[29.8317,-15.6126],[29.4437,-15.68702],[29.21955,-15.76589],[29.01298,-15.93805],[28.9243,-15.93987],[28.8501,-16.04537],[28.81454,-16.48611],[28.76199,-16.51575],[28.73725,-16.5528],[27.83141,-16.96274],[27.62795,-17.24365],[27.61377,-17.34378],[27.30736,-17.60487],[27.14196,-17.81398],[26.89926,-17.98756],[26.74314,-18.0199],[26.68403,-18.07411],[26.55918,-17.99638],[26.21601,-17.88608],[26.0908,-17.93021],[26.08925,-17.98168],[25.85892,-17.97726],[25.85738,-17.91403],[25.6827,-17.81987],[25.51646,-17.86232],[25.26433,-17.79571],[25.23909,-17.90832]]]}},{"type":"Feature","properties":{"id":"US-AK"},"geometry":{"type":"Polygon","coordinates":[[[-179.55295,50.81807],[-133.92876,54.62289],[-130.61931,54.70835],[-130.64499,54.76912],[-130.44184,54.85377],[-130.27203,54.97174],[-130.18765,55.07744],[-130.08035,55.21556],[-129.97513,55.28029],[-130.15373,55.74895],[-130.00857,55.91344],[-130.00093,56.00325],[-130.10173,56.12178],[-130.33965,56.10849],[-130.77769,56.36185],[-131.8271,56.62247],[-133.38523,58.42773],[-133.84645,58.73543],[-134.27175,58.8634],[-134.48059,59.13231],[-134.55699,59.1297],[-134.7047,59.2458],[-135.00267,59.28745],[-135.03069,59.56208],[-135.48007,59.79937],[-136.31566,59.59083],[-136.22381,59.55526],[-136.33727,59.44466],[-136.47323,59.46617],[-136.52365,59.16752],[-136.82619,59.16198],[-137.4925,58.89415],[-137.60623,59.24465],[-138.62145,59.76431],[-138.71149,59.90728],[-139.05365,59.99655],[-139.20603,60.08896],[-139.05831,60.35205],[-139.68991,60.33693],[-139.98024,60.18027],[-140.45648,60.30919],[-140.5227,60.22077],[-141.00116,60.30648],[-141.00555,72.20369],[-168.25765,71.99091],[-168.95635,65.98512],[-169.03888,65.48473],[-172.76104,63.77445],[-179.55295,57.62081],[-179.55295,50.81807]]]}},{"type":"Feature","properties":{"id":"GL"},"geometry":{"type":"Polygon","coordinates":[[[-74.12379,75.70014],[-53.68108,62.9266],[-45.64471,55.43944],[-25.70385,67.46637],[-10.71459,70.09565],[-9.68082,72.73731],[-3.52068,82.6752],[-34.32457,84.11035],[-59.93819,82.31398],[-63.1988,81.66522],[-67.48417,80.75493],[-73.91222,78.42484],[-74.12379,75.70014]]]}},{"type":"Feature","properties":{"id":"IN-HP"},"geometry":{"type":"Polygon","coordinates":[[[75.58284,32.07384],[75.71948,32.06541],[75.89904,31.94808],[75.96977,31.81281],[75.92822,31.80522],[76.16546,31.39526],[76.18743,31.28999],[76.34056,31.34278],[76.31996,31.40082],[76.37283,31.43569],[76.50672,31.27913],[76.64817,31.21015],[76.61384,31.00144],[76.7704,30.9087],[76.90017,30.89751],[76.92386,30.83415],[76.99802,30.80024],[77.22083,30.49246],[77.5542,30.40278],[77.80757,30.52086],[77.7238,30.59359],[77.76946,30.63111],[77.69016,30.76927],[77.80174,31.05822],[78.292,31.28676],[78.99581,31.10821],[78.77898,31.31209],[78.71032,31.50197],[78.84516,31.60631],[78.69933,31.78723],[78.78036,31.99478],[78.74404,32.00384],[78.68754,32.10256],[78.49609,32.2762],[78.4645,32.45367],[78.38897,32.53938],[78.32565,32.75263],[77.97477,32.58905],[77.88345,32.7942],[77.66784,32.97007],[77.32795,32.82305],[76.76902,33.26337],[76.31584,33.1341],[75.95329,32.88362],[75.77888,32.9355],[75.92513,32.64689],[75.82936,32.52664],[75.93921,32.41474],[75.7521,32.28364],[75.64756,32.24707],[75.62885,32.10148],[75.58284,32.07384]]]}},{"type":"Feature","properties":{"id":"LK"},"geometry":{"type":"Polygon","coordinates":[[[79.37385,8.98767],[79.9245,5.46963],[82.74996,6.54691],[80.48418,10.20786],[79.42124,9.80115],[79.45362,9.159],[79.37385,8.98767]]]}},{"type":"Feature","properties":{"id":"CN-GZ"},"geometry":{"type":"Polygon","coordinates":[[[103.61377,27.00591],[103.68621,27.06248],[103.78063,26.949],[103.7051,26.83173],[103.8153,26.53417],[104.00104,26.51389],[104.15279,26.67323],[104.34162,26.62444],[104.40925,26.73028],[104.47963,26.58422],[104.56031,26.59405],[104.68666,26.3691],[104.59258,26.31926],[104.47002,26.02007],[104.309,25.65947],[104.44633,25.47861],[104.52152,25.52695],[104.66022,25.2745],[104.78622,25.28195],[104.83119,25.172],[104.72545,25.19096],[104.71206,25.00348],[104.60151,24.89889],[104.52804,24.73498],[104.73884,24.62017],[105.02792,24.79982],[105.10345,24.94186],[105.21606,24.9985],[105.4502,24.91135],[105.49621,24.80917],[105.8052,24.70254],[105.93875,24.72936],[106.016,24.63079],[106.1856,24.78954],[106.19522,24.87491],[106.13994,24.95618],[106.43726,25.02121],[106.63913,25.13533],[106.63621,25.16734],[106.69595,25.18148],[106.89216,25.18723],[106.91757,25.25214],[106.99722,25.245],[106.96083,25.44203],[107.07206,25.56226],[107.23171,25.57682],[107.35153,25.39428],[107.43118,25.28909],[107.48233,25.30414],[107.46568,25.21736],[107.6061,25.2641],[107.6497,25.32106],[107.69313,25.19282],[107.75218,25.24314],[107.77622,25.11979],[108.11096,25.21363],[108.18923,25.45319],[108.34648,25.535],[108.61976,25.30306],[108.60671,25.49348],[108.73237,25.64709],[108.80721,25.53067],[109.07501,25.53376],[109.06745,25.72877],[108.88275,25.68299],[109.03312,25.79865],[109.19929,25.7665],[109.26521,25.71702],[109.47669,26.03334],[109.45129,26.30449],[109.28649,26.28664],[109.38056,26.57931],[109.28581,26.70022],[109.51652,26.75726],[109.49043,27.06524],[109.40734,27.15264],[109.13268,27.06462],[108.87451,27.00652],[108.78936,27.08908],[109.0472,27.29643],[109.04651,27.33578],[109.10659,27.3495],[109.14642,27.45344],[109.2937,27.42541],[109.45197,27.5722],[109.46708,27.69508],[109.33799,27.78198],[109.31533,27.9856],[109.38056,28.03683],[109.29336,28.05531],[109.39807,28.27717],[109.26881,28.3145],[109.28495,28.3772],[109.2616,28.39034],[109.25302,28.4711],[109.26993,28.49849],[109.26898,28.50437],[109.24324,28.4969],[109.22753,28.48317],[109.20075,28.48642],[109.17054,28.4582],[109.15981,28.42597],[109.08531,28.18884],[109.02385,28.20972],[108.99707,28.15828],[108.88137,28.22182],[108.75091,28.2073],[108.72001,28.29228],[108.76739,28.31465],[108.77872,28.42492],[108.70731,28.50098],[108.63178,28.46506],[108.68911,28.40136],[108.65959,28.3343],[108.60706,28.32523],[108.56414,28.37931],[108.60603,28.44092],[108.56552,28.5423],[108.60877,28.54924],[108.63109,28.6457],[108.52981,28.65112],[108.45874,28.62852],[108.33103,28.68637],[108.38012,28.80647],[108.28708,29.09577],[108.22837,29.02405],[108.18477,29.07207],[108.14323,29.05737],[108.0622,29.09037],[108.00899,29.04206],[107.88574,29.01114],[107.84042,28.96309],[107.75184,29.21031],[107.67219,29.14916],[107.4044,29.19472],[107.36457,29.00333],[107.44148,28.93845],[107.26089,28.76103],[107.1833,28.88977],[106.98211,28.86031],[106.96838,28.76585],[106.88804,28.80436],[106.81182,28.7514],[106.86881,28.62611],[106.81251,28.58934],[106.76238,28.62732],[106.77406,28.55919],[106.70677,28.44997],[106.55914,28.51153],[106.65183,28.66649],[106.5715,28.70624],[106.46781,28.84106],[106.44927,28.78631],[106.51245,28.67673],[106.47743,28.53567],[106.36756,28.52662],[105.96588,28.7496],[105.88142,28.602],[105.68778,28.57547],[105.61981,28.43488],[105.65895,28.308],[105.8876,28.23785],[105.9233,28.1277],[106.13857,28.16948],[106.20826,28.13497],[106.35864,27.83907],[105.92262,27.72973],[105.60676,27.69751],[105.31219,27.71088],[105.21057,27.37603],[105.08079,27.42114],[104.86312,27.28362],[104.85145,27.34523],[104.6046,27.30528],[104.50984,27.40712],[104.37217,27.47233],[104.18334,27.2708],[103.94165,27.45375],[103.83865,27.27202],[103.62716,27.11872],[103.61377,27.00591]]]}},{"type":"Feature","properties":{"id":"IS"},"geometry":{"type":"Polygon","coordinates":[[[-25.70385,67.46637],[-25.58144,62.99867],[-12.08632,63.06873],[-12.20873,67.52551],[-25.70385,67.46637]]]}},{"type":"Feature","properties":{"id":"BS"},"geometry":{"type":"Polygon","coordinates":[[[-80.16442,23.44484],[-73.62304,20.6935],[-72.94479,20.79216],[-72.41726,22.40371],[-76.80329,26.86841],[-78.4311,27.53866],[-79.36558,27.02964],[-80.16442,23.44484]]]}},{"type":"Feature","properties":{"id":"VI"},"geometry":{"type":"Polygon","coordinates":[[[-65.27974,17.56928],[-64.35558,17.48384],[-64.646,18.10286],[-64.64067,18.36478],[-64.86049,18.39954],[-65.02435,18.73231],[-65.27974,17.56928]]]}},{"type":"Feature","properties":{"id":"MV"},"geometry":{"type":"Polygon","coordinates":[[[72.15131,7.6285],[72.64576,-1.32013],[74.20495,-1.22734],[74.03744,7.70688],[72.15131,7.6285]]]}},{"type":"Feature","properties":{"id":"IO"},"geometry":{"type":"Polygon","coordinates":[[[70.64391,-7.71751],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[73.19979,-4.96078],[70.64754,-4.95745],[70.64391,-7.71751]]]}},{"type":"Feature","properties":{"id":"CN-ZJ"},"geometry":{"type":"Polygon","coordinates":[[[118.0323,29.10057],[118.25134,28.92523],[118.3509,28.8164],[118.4254,28.68486],[118.40343,28.57457],[118.47381,28.47925],[118.43364,28.41193],[118.48308,28.32856],[118.42121,28.29239],[118.47759,28.23876],[118.53973,28.28594],[118.79035,28.24572],[118.71002,27.98773],[118.79722,27.94164],[118.89678,27.64643],[118.89541,27.47294],[119.12132,27.44186],[119.25247,27.43425],[119.61845,27.67988],[119.65999,27.5381],[119.76985,27.30741],[120.03662,27.34371],[120.13275,27.42053],[120.25909,27.43089],[120.42011,27.26531],[120.40603,27.20273],[120.43281,27.17219],[121.03532,26.8787],[123.5458,31.01942],[121.26434,30.68516],[121.21456,30.78726],[121.12152,30.78018],[121.12529,30.86509],[121.03122,30.82265],[120.98865,30.89603],[120.98659,31.01939],[120.89424,31.01822],[120.84754,30.99173],[120.7703,30.9988],[120.73459,30.96289],[120.67485,30.957],[120.70747,30.88572],[120.64739,30.85095],[120.5801,30.85566],[120.49873,30.75953],[120.42835,30.91459],[120.35007,30.88896],[120.36208,30.97054],[119.91439,31.17051],[119.63149,31.1329],[119.58412,30.97407],[119.57382,30.85743],[119.43134,30.64145],[119.38671,30.69018],[119.24663,30.61575],[119.23393,30.53062],[119.31152,30.53032],[119.38705,30.37761],[119.22912,30.28871],[118.89404,30.35391],[118.90914,30.20508],[118.85627,30.16709],[118.89816,30.02332],[118.75808,29.76258],[118.48548,29.52447],[118.33717,29.48503],[118.20052,29.38178],[118.07281,29.2852],[118.0323,29.10057]]]}},{"type":"Feature","properties":{"id":"RE"},"geometry":{"type":"Polygon","coordinates":[[[54.32269,-20.37973],[54.43368,-22.02482],[56.73473,-21.9174],[56.62373,-20.2711],[54.32269,-20.37973]]]}},{"type":"Feature","properties":{"id":"MU"},"geometry":{"type":"Polygon","coordinates":[[[56.09755,-9.55401],[56.62373,-20.2711],[56.73473,-21.9174],[64.11105,-21.5783],[63.47388,-9.1938],[56.09755,-9.55401]]]}},{"type":"Feature","properties":{"id":"SC"},"geometry":{"type":"Polygon","coordinates":[[[45.39948,-9.09441],[46.52682,-10.83678],[48.86266,-10.8109],[51.51407,-10.78153],[57.144,-7.697],[56.94974,-2.9998],[53.06458,-3.53165],[45.39948,-9.09441]]]}},{"type":"Feature","properties":{"id":"KM"},"geometry":{"type":"Polygon","coordinates":[[[42.93552,-11.11413],[42.99868,-12.65261],[44.75722,-12.58368],[44.69407,-11.04481],[42.93552,-11.11413]]]}},{"type":"Feature","properties":{"id":"FO"},"geometry":{"type":"Polygon","coordinates":[[[-8.51774,62.35338],[-6.51083,60.95272],[-5.70102,62.77194],[-8.51774,62.35338]]]}},{"type":"Feature","properties":{"id":"CN-XJ"},"geometry":{"type":"Polygon","coordinates":[[[73.5004,39.38402],[73.55396,39.3543],[73.54572,39.27567],[73.60638,39.24534],[73.75823,39.023],[73.81728,39.04007],[73.82964,38.91517],[73.7445,38.93867],[73.7033,38.84782],[73.80656,38.66449],[73.79806,38.61106],[73.97933,38.52945],[74.17022,38.65504],[74.51217,38.47034],[74.69619,38.42947],[74.69894,38.22155],[74.80331,38.19889],[74.82665,38.07359],[74.9063,38.03033],[74.92416,37.83428],[75.00935,37.77486],[74.8912,37.67576],[74.94338,37.55501],[75.06011,37.52779],[75.15899,37.41443],[75.09719,37.37297],[75.12328,37.31839],[74.88887,37.23275],[74.80605,37.21565],[74.49981,37.24518],[74.56453,37.03023],[75.13839,37.02622],[75.40481,36.95382],[75.45562,36.71971],[75.72737,36.7529],[75.92391,36.56986],[76.0324,36.41198],[76.00906,36.17511],[75.93028,36.13136],[76.15325,35.9264],[76.14913,35.82848],[76.33453,35.84296],[76.50961,35.8908],[76.77323,35.66062],[76.84539,35.67356],[76.96624,35.5932],[77.44277,35.46132],[77.70232,35.46244],[77.80532,35.52058],[78.11664,35.48022],[78.03466,35.3785],[78.00033,35.23954],[78.22692,34.88771],[78.18435,34.7998],[78.27781,34.61484],[78.54964,34.57283],[78.56475,34.50835],[78.74465,34.45174],[79.05364,34.32482],[79.05418,34.4154],[79.83283,34.48958],[80.45287,35.45172],[81.66961,35.24337],[82.01568,35.34201],[82.45238,35.7309],[82.966,35.62716],[83.1253,35.39688],[84.19784,35.35881],[85.26489,35.80333],[85.57662,35.64055],[86.09298,35.8679],[86.2619,36.19995],[88.53332,36.48755],[88.76438,36.29077],[88.94119,36.35716],[89.691,36.0935],[89.95056,36.08018],[90.01922,36.2631],[90.86379,36.02577],[91.10961,36.10792],[91.02035,36.54053],[90.7196,36.59347],[90.84869,36.93342],[91.31149,37.02887],[91.06567,37.48575],[90.5136,37.74465],[90.52322,38.31903],[90.31585,38.22955],[90.14076,38.33734],[90.18127,38.39764],[90.09406,38.49014],[90.44975,38.49928],[92.40874,39.03625],[92.92785,40.58058],[93.76556,40.66605],[94.80789,41.53428],[95.19103,41.7498],[95.35171,41.54559],[96.18324,41.97225],[96.04934,42.38796],[96.37926,42.72055],[96.35658,42.90363],[95.89543,43.2528],[95.52594,43.99353],[95.32891,44.02407],[95.39772,44.2805],[95.01191,44.25274],[94.71959,44.35284],[94.10003,44.71016],[93.51161,44.95964],[91.64048,45.07408],[90.89169,45.19667],[90.65114,45.49314],[90.70907,45.73437],[91.03026,46.04194],[90.99672,46.14207],[90.89639,46.30711],[91.07696,46.57315],[91.0147,46.58171],[91.03649,46.72916],[90.84035,46.99525],[90.76108,46.99399],[90.48542,47.30438],[90.48854,47.41826],[90.33598,47.68303],[90.10871,47.7375],[90.06512,47.88177],[89.76624,47.82745],[89.55453,48.0423],[89.0711,47.98528],[88.93186,48.10263],[88.8011,48.11302],[88.58316,48.21893],[88.58939,48.34531],[87.96361,48.58478],[88.0788,48.71436],[87.73822,48.89582],[87.88171,48.95853],[87.81333,49.17354],[87.48983,49.13794],[87.478,49.07403],[87.28386,49.11626],[86.87238,49.12432],[86.73568,48.99918],[86.75343,48.70331],[86.38069,48.46064],[85.73581,48.3939],[85.5169,48.05493],[85.61067,47.49753],[85.69696,47.2898],[85.54294,47.06171],[85.22443,47.04816],[84.93995,46.87399],[84.73077,47.01394],[83.92184,46.98912],[83.04622,47.19053],[82.21792,45.56619],[82.58474,45.40027],[82.51374,45.1755],[81.73278,45.3504],[80.11169,45.03352],[79.8987,44.89957],[80.38384,44.63073],[80.40229,44.23319],[80.40031,44.10986],[80.75156,43.44948],[80.69718,43.32589],[80.77771,43.30065],[80.78817,43.14235],[80.62913,43.141],[80.3735,43.01557],[80.58999,42.9011],[80.38169,42.83142],[80.26886,42.8366],[80.16892,42.61137],[80.26841,42.23797],[80.17807,42.21166],[80.17842,42.03211],[79.92977,42.04113],[78.3732,41.39603],[78.15757,41.38565],[78.12873,41.23091],[77.81287,41.14307],[77.76206,41.01574],[77.52723,41.00227],[77.3693,41.0375],[77.28004,41.0033],[76.99302,41.0696],[76.75681,40.95354],[76.5261,40.46114],[76.33659,40.3482],[75.96168,40.38064],[75.91361,40.2948],[75.69663,40.28642],[75.5854,40.66874],[75.22834,40.45382],[75.08243,40.43945],[74.82013,40.52197],[74.78168,40.44886],[74.85996,40.32857],[74.69875,40.34668],[74.35063,40.09742],[74.25533,40.13191],[73.97049,40.04378],[73.83006,39.76136],[73.9051,39.75073],[73.92354,39.69565],[73.94683,39.60733],[73.87018,39.47879],[73.59831,39.46425],[73.59241,39.40843],[73.5004,39.38402]]]}},{"type":"Feature","properties":{"id":"PM"},"geometry":{"type":"Polygon","coordinates":[[[-56.70773,46.51478],[-55.8643,46.64935],[-56.25228,47.31192],[-56.67989,47.3339],[-56.70773,46.51478]]]}},{"type":"Feature","properties":{"id":"JP"},"geometry":{"type":"Polygon","coordinates":[[[122.20217,23.54946],[136.0511,20.05526],[155.16731,23.60141],[145.97944,43.07828],[145.76215,43.50342],[145.23667,43.76813],[145.82343,44.571],[140.9182,45.92937],[133.61399,37.41],[129.2669,34.87122],[127.42045,32.33183],[123.1407,27.24938],[122.20217,23.54946]]]}},{"type":"Feature","properties":{"id":"IN-KL"},"geometry":{"type":"Polygon","coordinates":[[[74.66307,12.69394],[75.18012,11.478],[75.52551,11.6993],[75.53821,11.76384],[75.55864,11.72317],[75.53787,11.6872],[75.19042,11.45378],[76.72283,7.82138],[77.22358,8.44758],[77.20024,8.50734],[77.27954,8.52296],[77.17655,8.7385],[77.26135,8.843],[77.15045,9.01496],[77.42065,9.51543],[77.16865,9.61632],[77.24693,9.80447],[77.25997,10.02971],[77.20298,10.11759],[77.27645,10.13382],[77.21465,10.36423],[76.96403,10.221],[76.82327,10.3237],[76.80679,10.63159],[76.87236,10.63226],[76.85691,10.68051],[76.8988,10.77327],[76.84112,10.81138],[76.81777,10.86163],[76.6492,10.924],[76.7237,11.20736],[76.43531,11.19456],[76.53934,11.35079],[76.23344,11.51871],[76.23722,11.58699],[76.37145,11.59136],[76.42948,11.66568],[76.18949,11.87608],[76.11757,11.85105],[76.11259,11.97887],[76.00307,11.93185],[75.86059,11.95502],[75.78987,12.08296],[75.54302,12.20279],[75.49392,12.29103],[75.43109,12.31249],[75.37067,12.45602],[75.43659,12.47144],[75.39882,12.50161],[75.34698,12.46105],[75.27214,12.5502],[75.33393,12.57534],[75.28106,12.61856],[75.23471,12.56662],[75.19798,12.6132],[75.14579,12.63648],[75.16124,12.67969],[75.11438,12.67752],[75.08777,12.70029],[75.06872,12.66228],[75.04108,12.67484],[75.0531,12.71804],[74.98323,12.73998],[75.01327,12.79137],[74.86049,12.76057],[74.66307,12.69394]]]}},{"type":"Feature","properties":{"id":"TV"},"geometry":{"type":"Polygon","coordinates":[[[174,-11.5],[179.99999,-11.5],[179.99999,-5],[174,-5],[174,-11.5]]]}},{"type":"Feature","properties":{"id":"VU"},"geometry":{"type":"Polygon","coordinates":[[[162.93363,-17.28904],[173.07304,-22.54607],[168.14096,-12.74443],[165.31108,-12.67903],[162.93363,-17.28904]]]}},{"type":"Feature","properties":{"id":"SB"},"geometry":{"type":"Polygon","coordinates":[[[154.74815,-7.33315],[160.37269,-13.44534],[165.31108,-12.67903],[168.14096,-12.74443],[171.12712,-12.81344],[171.21374,-9.22564],[159.32766,-4.77078],[157.60997,-5.69776],[156.03296,-6.55528],[156.03993,-6.65703],[155.92557,-6.84664],[155.69784,-6.92661],[155.60735,-6.92266],[154.74815,-7.33315]]]}},{"type":"Feature","properties":{"id":"MP"},"geometry":{"type":"Polygon","coordinates":[[[143.82485,13.92273],[146.25931,13.85876],[146.6755,21.00809],[144.18594,21.03576],[143.82485,13.92273]]]}},{"type":"Feature","properties":{"id":"MH"},"geometry":{"type":"Polygon","coordinates":[[[159.04653,10.59067],[161.58988,8.892633],[165.35175,6.367],[169,3.9],[173.53711,5.70687],[169.29099,15.77133],[159.04653,10.59067]]]}},{"type":"Feature","properties":{"id":"FM"},"geometry":{"type":"Polygon","coordinates":[[[136.04605,12.45908],[136.27107,6.73747],[148.49862,1.920402],[154.49668,-0.44964],[156.88247,-1.39237],[161.87397,3.186785],[165.35175,6.367],[161.58988,8.892633],[159.04653,10.59067],[153.83475,11.01511],[148.42679,11.45488],[136.04605,12.45908]]]}},{"type":"Feature","properties":{"id":"MY"},"geometry":{"type":"Polygon","coordinates":[[[99.31854,5.99868],[99.75778,3.86466],[103.03657,1.30383],[103.56591,1.19719],[103.62738,1.35255],[103.67468,1.43166],[103.7219,1.46108],[103.74161,1.4502],[103.76395,1.45183],[103.81181,1.47953],[103.86383,1.46288],[103.89565,1.42841],[103.93384,1.42926],[104.00131,1.42405],[104.02277,1.4438],[104.04622,1.44691],[104.07348,1.43322],[104.08871,1.42015],[104.09162,1.39694],[104.08072,1.35998],[104.12282,1.27714],[104.34728,1.33529],[104.56723,1.44271],[105.01437,3.24936],[108.10426,5.42408],[109.71058,2.32059],[109.64506,2.08014],[109.62558,1.99182],[109.53794,1.91771],[109.57923,1.80624],[109.66397,1.79972],[109.66397,1.60425],[110.35354,0.98869],[110.49182,0.88088],[110.62374,0.873],[111.22979,1.08326],[111.55434,0.97864],[111.82846,0.99349],[111.94553,1.12016],[112.15679,1.17004],[112.2127,1.44135],[112.48648,1.56516],[113.021,1.57819],[113.01448,1.42832],[113.64677,1.23933],[114.03788,1.44787],[114.57892,1.5],[114.80706,1.92351],[114.80706,2.21665],[115.1721,2.49671],[115.11343,2.82879],[115.53713,3.14776],[115.58276,3.93499],[115.90217,4.37708],[117.25801,4.35108],[117.47313,4.18857],[117.67641,4.16535],[117.89538,4.16637],[118.07935,4.15511],[118.8663,4.44172],[118.75416,4.59798],[119.44841,5.09568],[119.34756,5.53889],[117.89159,6.25755],[117.43832,7.3895],[117.17735,7.52841],[116.79524,7.43869],[115.02521,5.35005],[115.16236,5.01011],[115.15092,4.87604],[115.20737,4.8256],[115.27819,4.63661],[115.2851,4.42295],[115.36346,4.33563],[115.31275,4.30806],[115.09978,4.39123],[115.07737,4.53418],[115.04064,4.63706],[115.02278,4.74137],[115.02955,4.82087],[115.05038,4.90275],[114.99417,4.88201],[114.96982,4.81146],[114.88841,4.81905],[114.8266,4.75062],[114.77303,4.72871],[114.83189,4.42387],[114.88039,4.4257],[114.78539,4.12205],[114.64211,4.00694],[114.49922,4.13108],[114.4416,4.27588],[114.32176,4.2552],[114.32176,4.34942],[114.26876,4.49878],[114.15813,4.57],[114.07448,4.58441],[114.08532,4.64632],[109.55486,8.10026],[104.81582,8.03101],[102.46318,7.22462],[102.09086,6.23546],[102.08127,6.22679],[102.07732,6.193],[102.09182,6.14161],[102.01835,6.05407],[101.99209,6.04075],[101.97114,6.01992],[101.9714,6.00575],[101.94712,5.98421],[101.92819,5.85511],[101.91776,5.84269],[101.89188,5.8386],[101.80144,5.74505],[101.75074,5.79091],[101.69773,5.75881],[101.58019,5.93534],[101.25524,5.78633],[101.25755,5.71065],[101.14062,5.61613],[100.98815,5.79464],[101.02708,5.91013],[101.087,5.9193],[101.12388,6.11411],[101.06165,6.14161],[101.12618,6.19431],[101.10313,6.25617],[100.85884,6.24929],[100.81045,6.45086],[100.74822,6.46231],[100.74361,6.50811],[100.66986,6.45086],[100.43027,6.52389],[100.42351,6.51762],[100.41791,6.5189],[100.41152,6.52299],[100.35413,6.54932],[100.31929,6.65413],[100.32607,6.65933],[100.32671,6.66526],[100.31884,6.66423],[100.31618,6.66781],[100.30828,6.66462],[100.29651,6.68439],[100.19511,6.72559],[100.12,6.42105],[100.0756,6.4045],[99.91873,6.50233],[99.50117,6.44501],[99.31854,5.99868]]]}},{"type":"Feature","properties":{"id":"ID"},"geometry":{"type":"Polygon","coordinates":[[[93.82619,5.95573],[96.82918,-7.16134],[122.91521,-11.65621],[125.68138,-9.85176],[125.09025,-9.46406],[124.97892,-9.19281],[125.04044,-9.17093],[125.09434,-9.19669],[125.18907,-9.16434],[125.18632,-9.03142],[125.11764,-8.96359],[124.97742,-9.08128],[124.94011,-8.85617],[124.46701,-9.13002],[124.45971,-9.30263],[124.38554,-9.3582],[124.35258,-9.43002],[124.3535,-9.48493],[124.28115,-9.50453],[124.28115,-9.42189],[124.21247,-9.36904],[124.14517,-9.42324],[124.10539,-9.41206],[124.04286,-9.34243],[124.04628,-9.22671],[124.33472,-9.11416],[124.92337,-8.75859],[125.31127,-8.22976],[125.65946,-8.06136],[125.87691,-8.31789],[127.21788,-8.22363],[127.53551,-8.41485],[127.55165,-9.05052],[139.41724,-8.97063],[140.88922,-9.34945],[141.00782,-9.1242],[141.01763,-6.90181],[140.85295,-6.72996],[140.99813,-6.3233],[141.00167,0.68547],[134.40878,1.79674],[128.97621,3.08804],[126.69413,6.02692],[124.97752,4.82064],[118.41402,3.99509],[118.07935,4.15511],[117.89538,4.16637],[117.67641,4.16535],[117.47313,4.18857],[117.25801,4.35108],[115.90217,4.37708],[115.58276,3.93499],[115.53713,3.14776],[115.11343,2.82879],[115.1721,2.49671],[114.80706,2.21665],[114.80706,1.92351],[114.57892,1.5],[114.03788,1.44787],[113.64677,1.23933],[113.01448,1.42832],[113.021,1.57819],[112.48648,1.56516],[112.2127,1.44135],[112.15679,1.17004],[111.94553,1.12016],[111.82846,0.99349],[111.55434,0.97864],[111.22979,1.08326],[110.62374,0.873],[110.49182,0.88088],[110.35354,0.98869],[109.66397,1.60425],[109.66397,1.79972],[109.57923,1.80624],[109.53794,1.91771],[109.62558,1.99182],[109.64506,2.08014],[109.71058,2.32059],[108.10426,5.42408],[105.01437,3.24936],[104.56723,1.44271],[104.34728,1.33529],[104.12282,1.27714],[104.03085,1.26954],[103.74084,1.12902],[103.66049,1.18825],[103.56591,1.19719],[103.03657,1.30383],[99.75778,3.86466],[97.65314,5.70549],[94.98735,6.60903],[93.82619,5.95573]]]}},{"type":"Feature","properties":{"id":"IN-BR"},"geometry":{"type":"Polygon","coordinates":[[[83.34125,25.0125],[83.39824,24.78735],[83.5033,24.73747],[83.49883,24.52651],[83.75015,24.50245],[83.94584,24.54899],[83.9994,24.63141],[84.49756,24.28546],[84.57447,24.41151],[84.81994,24.529],[84.89307,24.36304],[85.66108,24.61518],[85.7373,24.8154],[86.12869,24.71876],[86.12491,24.61143],[86.32163,24.5824],[86.28833,24.46027],[86.45004,24.36586],[86.60316,24.60457],[87.05532,24.61237],[87.10235,24.84812],[87.15866,24.89391],[87.1511,25.02246],[87.21668,25.09243],[87.28946,25.09741],[87.32688,25.22016],[87.47348,25.19686],[87.48962,25.29933],[87.53906,25.27947],[87.58369,25.35271],[87.65373,25.28009],[87.86521,25.27139],[87.78865,25.34495],[87.76514,25.42529],[87.92667,25.53717],[88.0688,25.48047],[88.03859,25.54182],[88.04923,25.68794],[87.89989,25.76866],[87.90058,25.85304],[87.78625,25.87559],[87.84393,26.04537],[87.95036,26.06511],[88.0046,26.14341],[88.29093,26.3568],[88.23034,26.37679],[88.26158,26.4263],[88.19137,26.47549],[88.23394,26.55413],[88.14313,26.51097],[88.09963,26.54195],[88.09414,26.43732],[88.00895,26.36029],[87.90115,26.44923],[87.89085,26.48565],[87.84193,26.43663],[87.7918,26.46737],[87.76004,26.40711],[87.67893,26.43501],[87.66803,26.40294],[87.59175,26.38342],[87.55274,26.40596],[87.51571,26.43106],[87.46566,26.44058],[87.37314,26.40815],[87.34568,26.34787],[87.26568,26.37294],[87.26587,26.40592],[87.24682,26.4143],[87.18863,26.40558],[87.14751,26.40542],[87.09147,26.45039],[87.0707,26.58571],[87.04691,26.58685],[87.01559,26.53228],[86.95912,26.52076],[86.94543,26.52076],[86.82898,26.43919],[86.76797,26.45892],[86.74025,26.42386],[86.69124,26.45169],[86.62686,26.46891],[86.61313,26.48658],[86.57073,26.49825],[86.54258,26.53819],[86.49726,26.54218],[86.31564,26.61925],[86.26235,26.61886],[86.22513,26.58863],[86.13596,26.60651],[86.02729,26.66756],[85.8492,26.56667],[85.85126,26.60866],[85.83126,26.61134],[85.76907,26.63076],[85.72315,26.67471],[85.73483,26.79613],[85.66239,26.84822],[85.61621,26.86721],[85.59461,26.85161],[85.5757,26.85955],[85.56471,26.84133],[85.47752,26.79292],[85.34302,26.74954],[85.21159,26.75933],[85.18046,26.80519],[85.19291,26.86909],[85.15883,26.86966],[85.02635,26.85381],[85.05592,26.88991],[85.00536,26.89523],[84.97186,26.9149],[84.96687,26.95599],[84.85754,26.98984],[84.82913,27.01989],[84.793,26.9968],[84.64496,27.04669],[84.69166,27.21294],[84.62161,27.33885],[84.29315,27.39],[84.25735,27.44941],[84.21376,27.45218],[84.10791,27.52399],[84.02229,27.43836],[83.93306,27.44939],[83.86182,27.4241],[83.85595,27.35797],[83.83048,27.29552],[84.02687,27.08847],[84.23973,26.86511],[84.30599,26.75082],[84.39594,26.61554],[84.1254,26.6318],[84.05296,26.54891],[83.87992,26.5225],[83.90258,26.44905],[84.17243,26.37495],[84.17106,26.26139],[84.01245,26.23676],[84.02309,26.13848],[84.15183,26.03334],[84.27955,25.94538],[84.54082,25.85737],[84.66613,25.74022],[84.4749,25.68485],[84.38804,25.76959],[84.32899,25.70588],[84.0921,25.72908],[84.05845,25.64833],[83.88061,25.51765],[83.871,25.49333],[83.81452,25.45459],[83.84593,25.43645],[83.76422,25.3859],[83.74242,25.40792],[83.65917,25.36745],[83.64852,25.34464],[83.49077,25.28614],[83.4621,25.25152],[83.41335,25.24966],[83.34897,25.17744],[83.34125,25.0125]]]}},{"type":"Feature","properties":{"id":"PW"},"geometry":{"type":"Polygon","coordinates":[[[128.97621,3.08804],[134.40878,1.79674],[136.27107,6.73747],[136.04605,12.45908],[128.97621,3.08804]]]}},{"type":"Feature","properties":{"id":"PH"},"geometry":{"type":"Polygon","coordinates":[[[116.28201,8.2483],[116.79524,7.43869],[117.17735,7.52841],[117.43832,7.3895],[117.89159,6.25755],[119.34756,5.53889],[119.44841,5.09568],[118.75416,4.59798],[118.8663,4.44172],[118.07935,4.15511],[118.41402,3.99509],[124.97752,4.82064],[126.69413,6.02692],[129.01382,8.04729],[121.8109,21.77688],[120.69238,21.52331],[116.28201,8.2483]]]}},{"type":"Feature","properties":{"id":"CN-GX"},"geometry":{"type":"Polygon","coordinates":[[[104.4659,24.65044],[104.56718,24.44527],[104.70588,24.31018],[104.71481,24.43777],[105.0238,24.43839],[105.19615,24.34209],[105.16868,24.14988],[105.49552,24.01949],[105.57174,24.13798],[105.64796,24.03831],[106.00364,24.12858],[106.15608,23.90592],[106.14097,23.5772],[106.00089,23.4519],[105.8773,23.53314],[105.59509,23.31766],[105.56037,23.16806],[105.57594,23.075],[105.72382,23.06641],[105.8726,22.92756],[105.90119,22.94168],[105.99568,22.94178],[106.00179,22.99049],[106.19705,22.98475],[106.27022,22.87722],[106.34961,22.86718],[106.49749,22.91164],[106.51306,22.94891],[106.55976,22.92311],[106.60179,22.92884],[106.6516,22.86862],[106.6734,22.89587],[106.71387,22.88296],[106.71128,22.85982],[106.78422,22.81532],[106.81271,22.8226],[106.83685,22.8098],[106.82404,22.7881],[106.76293,22.73491],[106.72321,22.63606],[106.71698,22.58432],[106.65316,22.5757],[106.61269,22.60301],[106.58395,22.474],[106.55665,22.46498],[106.57221,22.37],[106.55976,22.34841],[106.6516,22.33977],[106.69986,22.22309],[106.67495,22.1885],[106.6983,22.15102],[106.70142,22.02409],[106.68274,21.99811],[106.69276,21.96013],[106.72551,21.97923],[106.74345,22.00965],[106.81038,21.97934],[106.9178,21.97357],[106.92714,21.93459],[106.97228,21.92592],[106.99252,21.95191],[107.05634,21.92303],[107.06101,21.88982],[107.00964,21.85948],[107.02615,21.81981],[107.10771,21.79879],[107.20734,21.71493],[107.24625,21.7077],[107.29296,21.74674],[107.35834,21.6672],[107.35989,21.60063],[107.38636,21.59774],[107.41593,21.64839],[107.47197,21.6672],[107.49532,21.62958],[107.49065,21.59774],[107.54047,21.5934],[107.56537,21.61945],[107.66967,21.60787],[107.80355,21.66141],[107.86114,21.65128],[107.90006,21.5905],[107.92652,21.58906],[107.95232,21.5388],[107.96774,21.53601],[107.97074,21.54072],[107.97383,21.53961],[107.97932,21.54503],[108.02926,21.54997],[108.0569,21.53604],[108.10003,21.47338],[108.26073,20.07614],[109.78912,21.47351],[109.73968,21.6054],[109.76371,21.67178],[109.8904,21.65008],[109.92216,21.71198],[109.94585,21.84397],[109.98481,21.87185],[110.19458,21.90148],[110.24642,21.88332],[110.28573,21.91756],[110.32127,21.89351],[110.3841,21.89096],[110.39234,21.91199],[110.36556,21.93476],[110.34702,22.19567],[110.62442,22.15274],[110.67317,22.17659],[110.64193,22.23349],[110.78475,22.27353],[110.67626,22.47576],[110.73738,22.46307],[110.74424,22.56931],[110.99452,22.63682],[111.31965,22.85086],[111.35158,22.96123],[111.42711,23.03013],[111.34094,23.19654],[111.47724,23.62313],[111.63276,23.64641],[111.65645,23.83308],[111.79515,23.8133],[111.92596,23.97339],[111.87171,24.10978],[112.05711,24.35522],[111.93145,24.69506],[112.04166,24.77987],[111.68666,24.78486],[111.52633,24.63952],[111.42745,24.6832],[111.46179,25.02526],[111.28326,25.14528],[110.98525,24.9157],[110.97015,25.10922],[111.31347,25.47613],[111.30042,25.70155],[111.37596,25.73001],[111.48239,25.88208],[111.25579,25.85922],[111.19125,25.94693],[111.29287,26.24662],[111.09306,26.31188],[110.96328,26.3851],[110.9262,26.26694],[110.75317,26.25277],[110.61172,26.33465],[110.31028,25.96915],[110.09811,26.01914],[109.97486,26.19241],[109.81109,26.04259],[109.82276,25.88023],[109.68749,25.88517],[109.72663,26.00001],[109.47669,26.03334],[109.26521,25.71702],[109.19929,25.7665],[109.03312,25.79865],[108.88275,25.68299],[109.06745,25.72877],[109.07501,25.53376],[108.80721,25.53067],[108.73237,25.64709],[108.60671,25.49348],[108.61976,25.30306],[108.34648,25.535],[108.18923,25.45319],[108.11096,25.21363],[107.77622,25.11979],[107.75218,25.24314],[107.69313,25.19282],[107.6497,25.32106],[107.6061,25.2641],[107.46568,25.21736],[107.48233,25.30414],[107.43118,25.28909],[107.35153,25.39428],[107.23171,25.57682],[107.07206,25.56226],[106.96083,25.44203],[106.99722,25.245],[106.91757,25.25214],[106.89216,25.18723],[106.69595,25.18148],[106.63621,25.16734],[106.63913,25.13533],[106.43726,25.02121],[106.13994,24.95618],[106.19522,24.87491],[106.1856,24.78954],[106.016,24.63079],[105.93875,24.72936],[105.8052,24.70254],[105.49621,24.80917],[105.4502,24.91135],[105.21606,24.9985],[105.10345,24.94186],[105.02792,24.79982],[104.73884,24.62017],[104.52804,24.73498],[104.4659,24.65044]]]}},{"type":"Feature","properties":{"id":"JE"},"geometry":{"type":"Polygon","coordinates":[[[-2.56423,49.22209],[-2.5234,48.91595],[-2.00491,48.86706],[-1.83944,49.23037],[-2.09454,49.46288],[-2.56423,49.22209]]]}},{"type":"Feature","properties":{"id":"GG"},"geometry":{"type":"Polygon","coordinates":[[[-3.06097,49.47231],[-2.9511,49.31141],[-2.56423,49.22209],[-2.09454,49.46288],[-2.02963,49.91866],[-2.39388,49.94612],[-3.06097,49.47231]]]}},{"type":"Feature","properties":{"id":"IM"},"geometry":{"type":"Polygon","coordinates":[[[-5.83481,53.87749],[-5.37267,53.63269],[-3.64906,54.12723],[-4.1819,54.57861],[-4.86305,54.44148],[-5.83481,53.87749]]]}},{"type":"Feature","properties":{"id":"CN-HB"},"geometry":{"type":"Polygon","coordinates":[[[108.3657,29.84064],[108.4917,29.71966],[108.6098,29.86863],[108.67641,29.85255],[108.68499,29.70132],[108.90918,29.60331],[108.86455,29.46411],[108.93836,29.43601],[108.90918,29.32651],[108.97647,29.33085],[109.05029,29.4055],[109.10419,29.36691],[109.10745,29.21855],[109.2346,29.11829],[109.45678,29.55613],[109.53609,29.62331],[109.70088,29.60928],[109.78637,29.76556],[110.14514,29.78374],[110.37036,29.63345],[110.64742,29.77272],[110.4943,29.91923],[110.52246,30.06523],[110.75386,30.04888],[110.75729,30.12523],[110.94646,30.06493],[111.24481,30.04383],[111.40274,29.91268],[111.51912,29.93232],[111.6053,29.89006],[111.79447,29.91209],[111.95548,29.82843],[112.06706,29.73188],[112.10861,29.66001],[112.2377,29.65673],[112.28713,29.50147],[112.46566,29.6433],[112.68093,29.59734],[112.80796,29.74917],[112.8955,29.79328],[112.9264,29.69014],[112.94769,29.47188],[113.15505,29.46112],[113.51898,29.82813],[113.56704,29.67254],[113.7363,29.58928],[113.62403,29.52118],[113.7569,29.44767],[113.59588,29.25914],[113.69132,29.2187],[113.69132,29.06637],[113.81835,29.10837],[113.85749,29.04056],[113.9447,29.05196],[114.0525,29.20761],[114.23034,29.2193],[114.25231,29.31993],[114.88128,29.38397],[114.95063,29.55852],[115.16693,29.50176],[115.11199,29.68029],[115.26786,29.63674],[115.40107,29.67492],[115.50064,29.8323],[115.65994,29.85851],[115.93803,29.71906],[116.12823,29.82754],[116.08222,30.12434],[115.91949,30.30472],[115.90061,30.50992],[115.7698,30.6701],[115.85082,30.75865],[115.84156,30.8312],[116.07776,30.96966],[115.87692,31.1423],[115.77461,31.10527],[115.69667,31.20604],[115.57823,31.14465],[115.36165,31.40228],[115.21018,31.58204],[114.83184,31.45092],[114.58465,31.71648],[114.18708,31.85452],[113.9859,31.75736],[113.84582,31.84314],[113.7284,32.08432],[113.75038,32.26855],[113.71467,32.43329],[113.41804,32.27784],[113.2505,32.39213],[112.55149,32.39851],[112.31323,32.32862],[112.0008,32.45864],[111.67293,32.62636],[111.57474,32.59455],[111.46488,32.73732],[111.29665,32.85622],[111.18163,33.10534],[110.9801,33.2605],[110.7003,33.09384],[110.55713,33.26653],[110.46958,33.17721],[110.22926,33.15882],[110.16197,33.20996],[110.03974,33.19158],[109.61299,33.2783],[109.42794,33.15479],[109.79324,33.0691],[109.75925,32.91273],[109.79049,32.87929],[109.86259,32.911],[110.02481,32.87482],[110.03391,32.86041],[110.13725,32.81238],[110.19218,32.62029],[110.04524,32.55144],[109.71393,32.61508],[109.55703,32.48543],[109.49901,32.3028],[109.62364,32.10177],[109.58381,31.72933],[109.72595,31.7083],[109.73522,31.58497],[110.12695,31.41108],[110.20042,31.15405],[110.1139,31.09527],[110.16952,30.9829],[110.06652,30.79729],[109.92919,30.91106],[109.53025,30.66154],[109.35138,30.48625],[109.30469,30.63584],[109.13749,30.52086],[109.08256,30.59418],[109.11861,30.6385],[109.04273,30.65637],[108.96995,30.62934],[108.83056,30.50311],[108.7413,30.4972],[108.6589,30.60098],[108.63796,30.53979],[108.56998,30.48211],[108.41789,30.49306],[108.40072,30.38827],[108.58131,30.25669],[108.5202,29.8722],[108.3657,29.84064]]]}},{"type":"Feature","properties":{"id":"AX"},"geometry":{"type":"Polygon","coordinates":[[[19.08191,60.19152],[20.5104,59.15546],[21.35468,59.67511],[21.02509,60.12142],[21.15143,60.54555],[20.96741,60.71528],[19.23413,60.61414],[19.08191,60.19152]]]}},{"type":"Feature","properties":{"id":"VG"},"geometry":{"type":"Polygon","coordinates":[[[-65.02435,18.73231],[-64.86049,18.39954],[-64.64067,18.36478],[-64.646,18.10286],[-63.95092,18.07976],[-63.90607,18.93262],[-64.62855,18.98678],[-65.02435,18.73231]]]}},{"type":"Feature","properties":{"id":"IN-MH"},"geometry":{"type":"Polygon","coordinates":[[[72.4768,20.16425],[73.34747,15.61245],[73.68598,15.72484],[73.80477,15.74401],[73.94691,15.74236],[73.97472,15.62799],[74.01592,15.60253],[74.11617,15.65411],[74.20165,15.77903],[74.34345,15.76417],[74.33967,15.85171],[74.46292,16.04746],[74.36576,16.03822],[74.41486,16.10255],[74.48249,16.09694],[74.50241,16.22489],[74.32662,16.31816],[74.35958,16.37746],[74.26483,16.51904],[74.37057,16.53748],[74.4691,16.65888],[74.6284,16.57762],[74.69398,16.7138],[74.90341,16.77101],[74.92744,16.93793],[75.07713,16.95566],[75.20553,16.83214],[75.27969,16.95697],[75.68206,16.95303],[75.58439,17.34883],[75.63846,17.49346],[75.69425,17.40994],[75.82609,17.42517],[75.892,17.36816],[75.92428,17.32835],[76.05525,17.36178],[76.07276,17.33081],[76.11928,17.37242],[76.18606,17.30459],[76.17439,17.34654],[76.24031,17.37652],[76.27326,17.32917],[76.31429,17.33998],[76.36545,17.30885],[76.40596,17.3526],[76.37592,17.37259],[76.37128,17.42943],[76.33043,17.4558],[76.34674,17.49468],[76.32974,17.57939],[76.40201,17.60426],[76.48424,17.71206],[76.5172,17.71925],[76.52166,17.75833],[76.56423,17.76928],[76.57402,17.70061],[76.61178,17.77631],[76.6892,17.68082],[76.69898,17.72792],[76.79134,17.82289],[76.7383,17.88269],[76.76868,17.90001],[76.88129,17.89413],[76.92043,17.91291],[76.91305,17.96779],[76.91596,18.03489],[76.94978,18.03848],[76.94532,18.08042],[76.9666,18.09592],[76.91871,18.12431],[76.98205,18.19355],[77.00351,18.16053],[77.03733,18.18189],[77.05355,18.15955],[77.11758,18.15775],[77.11741,18.19869],[77.17234,18.28453],[77.20822,18.2785],[77.24899,18.40714],[77.30718,18.44053],[77.324,18.41838],[77.36108,18.45225],[77.37138,18.40095],[77.41653,18.39427],[77.35988,18.30857],[77.42752,18.31118],[77.49343,18.26847],[77.5082,18.31183],[77.56785,18.28849],[77.60965,18.5522],[77.73994,18.55204],[77.74251,18.68267],[77.85684,18.81905],[77.94353,18.82604],[77.74251,19.02577],[77.84379,19.18359],[77.86422,19.3207],[78.00653,19.30158],[78.14695,19.22898],[78.19141,19.42903],[78.3011,19.46885],[78.26694,19.69544],[78.35861,19.75604],[78.27449,19.90476],[78.82793,19.75749],[78.84613,19.65778],[78.95839,19.66037],[78.94432,19.54943],[79.19048,19.46044],[79.23408,19.61219],[79.47578,19.49928],[79.63165,19.57887],[79.804,19.60054],[79.86991,19.5009],[79.93858,19.47792],[79.97051,19.35358],[79.96261,18.86145],[79.9094,18.82474],[80.11196,18.6869],[80.27503,18.72104],[80.36636,18.83091],[80.24825,18.95565],[80.55587,19.40313],[80.72341,19.27355],[80.8937,19.47306],[80.51467,19.92687],[80.54488,20.07528],[80.38284,20.23256],[80.60806,20.32273],[80.5799,20.6694],[80.54214,20.92681],[80.46935,20.92681],[80.42129,21.09923],[80.64308,21.25418],[80.65784,21.32967],[80.56377,21.36037],[80.40824,21.3738],[80.38696,21.49619],[80.28499,21.59742],[80.20946,21.6354],[80.13427,21.61115],[80.06732,21.55528],[79.95025,21.5572],[79.91489,21.52207],[79.75662,21.60062],[79.53861,21.54634],[79.4914,21.67306],[79.33021,21.70719],[79.14585,21.62583],[78.91136,21.59295],[78.9347,21.49268],[78.43654,21.50099],[78.37989,21.62296],[78.00842,21.41887],[77.56484,21.38019],[77.49893,21.76332],[77.23422,21.7158],[77.07733,21.72474],[76.90429,21.60349],[76.80198,21.59519],[76.65847,21.28201],[76.38656,21.07809],[76.17301,21.08386],[76.15653,21.2625],[75.96324,21.39618],[75.22304,21.40928],[75.11455,21.45306],[75.07575,21.55209],[74.91783,21.63062],[74.64317,21.65583],[74.51408,21.72314],[74.52609,21.90769],[74.43717,22.03282],[74.14947,21.95323],[73.83636,21.84684],[73.79379,21.62041],[73.85662,21.49939],[74.10724,21.5639],[74.21161,21.53101],[74.30911,21.5655],[74.33332,21.50945],[74.26328,21.46265],[74.11085,21.44492],[74.06776,21.48118],[74.05265,21.41983],[74.01712,21.42047],[73.94897,21.29737],[73.83619,21.26953],[73.83447,21.19337],[73.74315,21.16568],[73.74538,21.14279],[73.58573,21.15591],[73.7325,21.10163],[73.91635,20.92232],[73.93901,20.73588],[73.88854,20.72946],[73.7495,20.5624],[73.6695,20.56208],[73.44978,20.71726],[73.4333,20.2055],[73.29769,20.20453],[73.29048,20.1549],[73.23383,20.14266],[73.19675,20.05625],[72.98492,20.11784],[72.9808,20.21323],[72.8754,20.22869],[72.80021,20.12622],[72.4768,20.16425]]]}},{"type":"Feature","properties":{"id":"AQ"},"geometry":{"type":"Polygon","coordinates":[[[-180,-85],[180,-85],[180,-60],[-180,-60],[-180,-85]]]}},{"type":"Feature","properties":{"id":"AW"},"geometry":{"type":"Polygon","coordinates":[[[-70.34259,12.92535],[-70.24399,12.38063],[-69.4514,12.18025],[-69.5195,12.75292],[-70.34259,12.92535]]]}},{"type":"Feature","properties":{"id":"CX"},"geometry":{"type":"Polygon","coordinates":[[[105.29647,-10.80676],[105.9699,-10.81333],[105.97626,-10.18257],[105.30283,-10.176],[105.29647,-10.80676]]]}},{"type":"Feature","properties":{"id":"CC"},"geometry":{"type":"Polygon","coordinates":[[[96.53724,-12.46709],[97.24513,-12.47233],[97.25212,-11.57036],[96.54423,-11.5651],[96.53724,-12.46709]]]}},{"type":"Feature","properties":{"id":"TK"},"geometry":{"type":"Polygon","coordinates":[[[-174.18707,-7.54408],[-174.17993,-10.13616],[-167.75195,-10.12005],[-167.75329,-7.52784],[-174.18707,-7.54408]]]}},{"type":"Feature","properties":{"id":"CK"},"geometry":{"type":"Polygon","coordinates":[[[-167.75329,-7.52784],[-167.75195,-10.12005],[-167.73854,-14.92809],[-167.73129,-23.22266],[-156.46451,-23.21255],[-156.4957,-12.32002],[-156.50903,-7.4975],[-167.75329,-7.52784]]]}},{"type":"Feature","properties":{"id":"IN-PB"},"geometry":{"type":"Polygon","coordinates":[[[73.88992,29.96921],[74.52094,29.94303],[74.63115,29.90494],[74.69467,29.9695],[74.80144,29.99092],[74.87285,29.96296],[74.98821,29.86357],[75.09155,29.91774],[75.1015,29.81294],[75.14099,29.77063],[75.17806,29.83826],[75.20072,29.832],[75.22991,29.75603],[75.1966,29.69073],[75.15747,29.66747],[75.21858,29.61495],[75.22716,29.54598],[75.31162,29.58122],[75.27934,29.60779],[75.34046,29.66747],[75.33908,29.68984],[75.37891,29.71161],[75.39813,29.76378],[75.44448,29.80788],[75.60173,29.7456],[75.65288,29.77987],[75.69339,29.75573],[75.70232,29.81145],[75.83003,29.81354],[75.86162,29.75424],[75.9423,29.73069],[76.04049,29.74798],[76.08169,29.80877],[76.11602,29.80222],[76.24168,29.85761],[76.2355,29.88649],[76.18057,29.88947],[76.2276,30.12656],[76.43394,30.15195],[76.44664,30.10385],[76.50089,30.0783],[76.60903,30.07978],[76.6238,30.14497],[76.63873,30.20493],[76.56045,30.26381],[76.73375,30.36458],[76.69864,30.39257],[76.75769,30.43727],[76.80747,30.41196],[76.83151,30.43328],[76.84885,30.40604],[76.88438,30.38516],[76.877,30.36265],[76.88833,30.35569],[76.92987,30.39686],[76.91579,30.40145],[76.88798,30.44038],[76.92026,30.52618],[76.89571,30.53579],[76.89931,30.55206],[76.91193,30.60504],[76.87133,30.63997],[76.85846,30.6416],[76.85923,30.65762],[76.84902,30.66693],[76.83794,30.65755],[76.81949,30.66139],[76.81468,30.68693],[76.8025,30.67527],[76.78945,30.67054],[76.76095,30.68619],[76.7513,30.68512],[76.73894,30.70136],[76.73014,30.72479],[76.71967,30.73202],[76.71306,30.7419],[76.69014,30.74699],[76.69066,30.75968],[76.70808,30.77045],[76.72285,30.77067],[76.73864,30.7908],[76.75975,30.79928],[76.777,30.78483],[76.76911,30.77683],[76.78104,30.77392],[76.79632,30.78623],[76.80413,30.779],[76.79254,30.76647],[76.82825,30.76411],[76.7704,30.9087],[76.61384,31.00144],[76.64817,31.21015],[76.50672,31.27913],[76.37283,31.43569],[76.31996,31.40082],[76.34056,31.34278],[76.18743,31.28999],[76.16546,31.39526],[75.92822,31.80522],[75.96977,31.81281],[75.89904,31.94808],[75.71948,32.06541],[75.58284,32.07384],[75.62885,32.10148],[75.64756,32.24707],[75.7521,32.28364],[75.93921,32.41474],[75.82936,32.52664],[75.78506,32.47095],[75.57083,32.36836],[75.49941,32.28074],[75.28259,32.36556],[75.38046,32.26836],[75.25649,32.10187],[75.00793,32.03786],[74.9269,32.0658],[74.86236,32.04485],[74.79919,31.95983],[74.58907,31.87824],[74.47771,31.72227],[74.57498,31.60382],[74.61517,31.55698],[74.59319,31.50197],[74.64713,31.45605],[74.59773,31.4136],[74.53223,31.30321],[74.51629,31.13829],[74.56023,31.08303],[74.60281,31.10419],[74.60006,31.13711],[74.6852,31.12771],[74.67971,31.05479],[74.5616,31.04153],[73.88993,30.36305],[73.95736,30.28466],[73.97225,30.19829],[73.88992,29.96921]]]}},{"type":"Feature","properties":{"id":"FK"},"geometry":{"type":"Polygon","coordinates":[[[-62.78369,-53.1401],[-58.84651,-53.93403],[-55.76919,-51.15168],[-62.3754,-50.36819],[-62.78369,-53.1401]]]}},{"type":"Feature","properties":{"id":"GS"},"geometry":{"type":"Polygon","coordinates":[[[-43.57991,-52.56305],[-40.68557,-57.40649],[-26.52505,-59.90465],[-23.50385,-54.792],[-43.57991,-52.56305]]]}},{"type":"Feature","properties":{"id":"GU"},"geometry":{"type":"Polygon","coordinates":[[[143.82485,13.92273],[144.61642,12.82462],[146.25931,13.85876],[143.82485,13.92273]]]}},{"type":"Feature","properties":{"id":"HM"},"geometry":{"type":"Polygon","coordinates":[[[71.08716,-53.87687],[75.44182,-53.99822],[72.87012,-51.48322],[71.08716,-53.87687]]]}},{"type":"Feature","properties":{"id":"HK"},"geometry":{"type":"Polygon","coordinates":[[[113.81621,22.2163],[113.83338,22.1826],[113.92195,22.13873],[114.50148,22.15017],[114.44998,22.55977],[114.25154,22.55977],[114.22888,22.5436],[114.22185,22.55343],[114.20655,22.55706],[114.18338,22.55444],[114.17247,22.55944],[114.1597,22.56041],[114.15123,22.55163],[114.1482,22.54091],[114.13823,22.54319],[114.12665,22.54003],[114.11656,22.53415],[114.11181,22.52878],[114.1034,22.5352],[114.09692,22.53435],[114.09048,22.53716],[114.08606,22.53276],[114.07817,22.52997],[114.07267,22.51855],[114.06272,22.51617],[114.05729,22.51104],[114.05438,22.5026],[114.03113,22.5065],[113.86771,22.42972],[113.81621,22.2163]]]}},{"type":"Feature","properties":{"id":"PR"},"geometry":{"type":"Polygon","coordinates":[[[-68.20301,17.83927],[-65.27974,17.56928],[-65.02435,18.73231],[-67.99519,18.97186],[-68.20301,17.83927]]]}},{"type":"Feature","properties":{"id":"MO"},"geometry":{"type":"Polygon","coordinates":[[[113.52659,22.18271],[113.54093,22.15497],[113.54942,22.14519],[113.54839,22.10909],[113.57191,22.07696],[113.63011,22.10782],[113.60504,22.20464],[113.57123,22.20416],[113.56865,22.20973],[113.5508,22.21672],[113.54333,22.21688],[113.54093,22.21314],[113.53593,22.2137],[113.53301,22.21235],[113.53552,22.20607],[113.52659,22.18271]]]}},{"type":"Feature","properties":{"id":"CN-CQ"},"geometry":{"type":"Polygon","coordinates":[[[105.29193,29.58122],[105.37261,29.42285],[105.44128,29.40131],[105.43888,29.31783],[105.65036,29.24357],[105.7101,29.30047],[105.74958,29.01894],[105.90408,28.9054],[106.0033,28.97811],[106.26113,28.83715],[106.36756,28.52662],[106.47743,28.53567],[106.51245,28.67673],[106.44927,28.78631],[106.46781,28.84106],[106.5715,28.70624],[106.65183,28.66649],[106.55914,28.51153],[106.70677,28.44997],[106.77406,28.55919],[106.76238,28.62732],[106.81251,28.58934],[106.86881,28.62611],[106.81182,28.7514],[106.88804,28.80436],[106.96838,28.76585],[106.98211,28.86031],[107.1833,28.88977],[107.26089,28.76103],[107.44148,28.93845],[107.36457,29.00333],[107.4044,29.19472],[107.67219,29.14916],[107.75184,29.21031],[107.84042,28.96309],[107.88574,29.01114],[108.00899,29.04206],[108.0622,29.09037],[108.14323,29.05737],[108.18477,29.07207],[108.22837,29.02405],[108.28708,29.09577],[108.38012,28.80647],[108.33103,28.68637],[108.45874,28.62852],[108.52981,28.65112],[108.63109,28.6457],[108.60877,28.54924],[108.56552,28.5423],[108.60603,28.44092],[108.56414,28.37931],[108.60706,28.32523],[108.65959,28.3343],[108.68911,28.40136],[108.63178,28.46506],[108.70731,28.50098],[108.77872,28.42492],[108.76739,28.31465],[108.72001,28.29228],[108.75091,28.2073],[108.88137,28.22182],[108.99707,28.15828],[109.02385,28.20972],[109.08531,28.18884],[109.15981,28.42597],[109.17054,28.4582],[109.20075,28.48642],[109.22753,28.48317],[109.24324,28.4969],[109.26898,28.50437],[109.26727,28.51787],[109.27645,28.52202],[109.29121,28.57268],[109.31576,28.58595],[109.29671,28.62913],[109.19466,28.60275],[109.18556,28.63184],[109.29885,28.73018],[109.23688,28.78255],[109.23208,28.87985],[109.31877,29.05827],[109.2346,29.11829],[109.22916,29.12217],[109.10745,29.21855],[109.10419,29.36691],[109.05029,29.4055],[108.97647,29.33085],[108.90918,29.32651],[108.93836,29.43601],[108.86455,29.46411],[108.90918,29.60331],[108.68499,29.70132],[108.67641,29.85255],[108.6098,29.86863],[108.4917,29.71966],[108.3657,29.84064],[108.5202,29.8722],[108.58131,30.25669],[108.40072,30.38827],[108.41789,30.49306],[108.56998,30.48211],[108.63796,30.53979],[108.6589,30.60098],[108.7413,30.4972],[108.83056,30.50311],[108.96995,30.62934],[109.04273,30.65637],[109.11861,30.6385],[109.08256,30.59418],[109.13749,30.52086],[109.30469,30.63584],[109.35138,30.48625],[109.53025,30.66154],[109.92919,30.91106],[110.06652,30.79729],[110.16952,30.9829],[110.1139,31.09527],[110.20042,31.15405],[110.12695,31.41108],[109.73522,31.58497],[109.72595,31.7083],[109.58381,31.72933],[109.27619,31.71823],[109.20066,31.85248],[108.50234,32.20641],[108.27369,31.92885],[108.54011,31.67559],[108.02032,31.24803],[108.07182,31.18049],[107.92282,30.92578],[107.99114,30.91076],[107.84866,30.79405],[107.69657,30.876],[107.63683,30.81233],[107.48508,30.84476],[107.42637,30.7318],[107.50431,30.63732],[107.19291,30.18727],[107.03155,30.04532],[106.96941,30.08484],[106.76582,30.01738],[106.54643,30.32843],[106.21067,30.18134],[106.16397,30.30413],[105.79627,30.44393],[105.68778,30.26618],[105.61431,30.26381],[105.59062,30.11216],[105.75302,30.01619],[105.70667,29.83796],[105.60127,29.83707],[105.45707,29.67612],[105.29193,29.58122]]]}},{"type":"Feature","properties":{"id":"YT"},"geometry":{"type":"Polygon","coordinates":[[[44.75722,-12.58368],[44.82644,-13.30845],[45.54824,-13.22353],[45.45962,-12.30345],[44.75722,-12.58368]]]}},{"type":"Feature","properties":{"id":"NU"},"geometry":{"type":"Polygon","coordinates":[[[-173.13438,-14.94228],[-173.11048,-23.23027],[-167.73129,-23.22266],[-167.73854,-14.92809],[-171.14262,-14.93704],[-173.13438,-14.94228]]]}},{"type":"Feature","properties":{"id":"NF"},"geometry":{"type":"Polygon","coordinates":[[[165.46901,-28.32101],[169.35326,-30.60259],[169.6687,-29.09191],[166.75333,-27.25416],[165.46901,-28.32101]]]}},{"type":"Feature","properties":{"id":"CN-JS"},"geometry":{"type":"Polygon","coordinates":[[[116.37405,34.64676],[116.95014,34.39841],[117.02567,34.15556],[117.50907,34.06176],[117.74322,33.89264],[117.71026,33.74718],[118.18405,33.74546],[117.96775,33.33052],[118.03024,33.12777],[118.20121,33.2203],[118.22456,32.9332],[118.37425,32.72144],[118.7059,32.71624],[118.85559,32.95855],[119.17762,32.83286],[119.22225,32.60756],[119.08218,32.45531],[118.89747,32.58905],[118.54659,32.58442],[118.68049,32.45647],[118.6956,32.36198],[118.64444,32.21657],[118.50299,32.19479],[118.50128,32.14393],[118.39004,32.02612],[118.35605,31.93788],[118.49716,31.84373],[118.47587,31.78246],[118.69354,31.7194],[118.64204,31.64812],[118.71894,31.62415],[118.73954,31.67851],[118.8652,31.62415],[118.86314,31.43745],[118.71482,31.29967],[118.78761,31.23394],[119.07257,31.23394],[119.17968,31.29908],[119.27238,31.25272],[119.35134,31.30143],[119.36782,31.19107],[119.50378,31.1611],[119.57656,31.11174],[119.63149,31.1329],[119.91439,31.17051],[120.36208,30.97054],[120.35007,30.88896],[120.42835,30.91459],[120.49873,30.75953],[120.5801,30.85566],[120.64739,30.85095],[120.70747,30.88572],[120.67485,30.957],[120.73459,30.96289],[120.7703,30.9988],[120.84754,30.99173],[120.89424,31.01822],[120.89836,31.09028],[120.85475,31.10938],[120.88325,31.14083],[121.03671,31.14171],[121.07276,31.16345],[121.05903,31.23658],[121.06006,31.27356],[121.11808,31.28529],[121.1495,31.27752],[121.15722,31.28515],[121.14057,31.31038],[121.12581,31.30348],[121.11173,31.37459],[121.15447,31.41108],[121.14246,31.44492],[121.23722,31.49704],[121.25335,31.47933],[121.3821,31.54723],[121.09954,31.75853],[121.30554,31.88338],[121.44286,31.76086],[121.76147,31.63818],[122.29378,31.76513],[122.80525,33.30571],[119.30259,35.07833],[118.88442,35.04349],[118.76495,34.73822],[118.51226,34.69194],[118.42849,34.44655],[118.17718,34.36837],[118.14559,34.54389],[117.93411,34.68404],[117.78991,34.51447],[117.15957,34.52692],[116.96456,34.88367],[116.43859,34.89944],[116.37405,34.64676]]]}},{"type":"Feature","properties":{"id":"MF"},"geometry":{"type":"Polygon","coordinates":[[[-63.35989,18.06012],[-63.33064,17.9615],[-63.13584,18.0541],[-63.11096,18.05368],[-63.09686,18.04608],[-63.07759,18.04943],[-63.0579,18.06614],[-63.04039,18.05619],[-63.02323,18.05757],[-62.93924,18.02904],[-62.75637,18.13489],[-62.86666,18.19278],[-63.35989,18.06012]]]}},{"type":"Feature","properties":{"id":"BL"},"geometry":{"type":"Polygon","coordinates":[[[-63.07669,17.79659],[-62.76692,17.64353],[-62.54836,17.8636],[-62.75637,18.13489],[-62.93924,18.02904],[-63.07669,17.79659]]]}},{"type":"Feature","properties":{"id":"AC"},"geometry":{"type":"Polygon","coordinates":[[[-14.91926,-6.63386],[-14.82771,-8.70814],[-13.33271,-8.07391],[-14.91926,-6.63386]]]}},{"type":"Feature","properties":{"id":"IC"},"geometry":{"type":"Polygon","coordinates":[[[-18.8556,26.96222],[-14.43883,27.02969],[-12.42686,29.61659],[-14.33337,30.94071],[-15.92339,29.50503],[-18.67893,29.62861],[-18.8556,26.96222]]]}},{"type":"Feature","properties":{"id":"ES-CE"},"geometry":{"type":"Polygon","coordinates":[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]]}},{"type":"Feature","properties":{"id":"ES-ML"},"geometry":{"type":"Polygon","coordinates":[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]]}},{"type":"Feature","properties":{"id":"IN-AP"},"geometry":{"type":"Polygon","coordinates":[[[76.76336,14.98442],[76.86996,14.97082],[76.86309,14.8401],[76.76628,14.67925],[76.80335,14.53257],[76.98205,14.48288],[76.89125,14.3966],[76.93862,14.24624],[77.04952,14.24774],[77.0866,14.191],[77.13466,14.33923],[77.27045,14.33241],[77.3767,14.21113],[77.4282,14.19998],[77.3628,14.34954],[77.48502,14.29332],[77.51472,14.15754],[77.38924,14.17419],[77.40949,14.12026],[77.33791,14.03967],[77.43215,13.98237],[77.45017,13.94772],[77.41172,13.88791],[77.35765,13.9339],[77.31954,14.03084],[77.15234,13.99187],[77.13415,14.03684],[77.0284,14.05432],[77.02651,14.16736],[76.95339,14.19233],[76.88112,14.14156],[76.98326,14.08596],[76.94463,14.05233],[76.93948,14.01719],[76.98772,14.0002],[77.05141,13.9214],[76.97862,13.82941],[77.00265,13.72837],[77.03081,13.78256],[77.05278,13.76739],[77.18393,13.76189],[77.1302,13.84891],[77.16556,13.9034],[77.23766,13.90824],[77.26221,13.85341],[77.41945,13.84541],[77.45206,13.81057],[77.47369,13.76289],[77.45326,13.70236],[77.48022,13.67751],[77.49206,13.71303],[77.52983,13.69585],[77.5245,13.75839],[77.56227,13.72721],[77.6711,13.78773],[77.71934,13.74021],[77.72209,13.7889],[77.83229,13.86158],[77.81839,13.93523],[77.9516,13.9344],[78.06146,13.88791],[78.12635,13.86208],[78.11485,13.76456],[78.11622,13.65716],[78.19965,13.64348],[78.19879,13.58592],[78.40461,13.59343],[78.37818,13.50748],[78.38058,13.40581],[78.36547,13.3634],[78.57439,13.31512],[78.57181,13.18061],[78.47637,12.98699],[78.40908,12.94517],[78.43809,12.92743],[78.46744,12.90334],[78.46813,12.86971],[78.39054,12.90652],[78.35706,12.93965],[78.315,12.85966],[78.26076,12.86469],[78.23295,12.76526],[78.2151,12.68991],[78.46401,12.61454],[78.63292,12.97143],[78.84681,13.07613],[79.14997,13.00422],[79.2152,13.13063],[79.40025,13.142],[79.36832,13.30711],[79.53414,13.30944],[79.77996,13.21254],[79.92484,13.33884],[80.02784,13.52718],[80.2153,13.48512],[80.29632,13.37626],[80.32104,13.44372],[80.67259,13.46443],[82.9708,16.27499],[82.19078,16.71874],[82.21618,16.76378],[83.0072,16.31915],[85.11932,18.86211],[84.76158,19.07055],[84.70287,19.15068],[84.59335,19.11662],[84.65961,19.06925],[84.59369,19.02317],[84.50408,19.04394],[84.31594,18.78411],[84.2284,18.78883],[84.08128,18.74478],[84.03991,18.79581],[83.88748,18.80296],[83.65058,19.12311],[83.33816,19.01084],[83.37936,18.83481],[83.11019,18.77176],[83.08822,18.54081],[82.96737,18.33692],[82.82592,18.43727],[82.63366,18.23522],[82.42835,18.49198],[82.35351,18.14193],[82.25532,17.98199],[82.04383,18.06622],[81.77398,17.88596],[81.40594,17.3585],[80.91499,17.20081],[80.83671,17.02494],[80.68771,17.06794],[80.61733,17.13226],[80.50369,17.1083],[80.4467,17.01772],[80.38867,17.08139],[80.36189,16.96683],[80.57544,16.91855],[80.58711,16.772],[80.45871,16.7881],[80.45391,16.81702],[80.40138,16.85613],[80.31864,16.87387],[80.26027,17.0082],[80.18199,17.04628],[80.04432,16.96256],[79.99248,16.8604],[80.0735,16.81242],[80.02544,16.71249],[79.94476,16.62731],[79.79507,16.72137],[79.31304,16.57302],[79.21485,16.48251],[79.21623,16.21665],[78.68408,16.04581],[78.41766,16.08012],[78.12,15.82892],[78.03108,15.90421],[77.65754,15.88869],[77.51197,15.92864],[77.44039,15.94548],[77.27113,15.96132],[77.11818,15.94036],[77.03613,15.856],[77.09278,15.71625],[76.97227,15.50992],[77.03201,15.43614],[77.04145,15.37292],[77.16281,15.29279],[77.17037,15.17619],[77.1132,15.02985],[76.98308,15.00945],[76.80044,15.09681],[76.76336,14.98442]]]}},{"type":"Feature","properties":{"id":"DG"},"geometry":{"type":"Polygon","coordinates":[[[72.0768,-6.94304],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[72.0768,-6.94304]]]}},{"type":"Feature","properties":{"id":"TA"},"geometry":{"type":"Polygon","coordinates":[[[-13.48367,-36.6746],[-13.41694,-37.88844],[-11.48092,-37.8367],[-11.55782,-36.60319],[-13.48367,-36.6746]]]}},{"type":"Feature","properties":{"id":"AU-WA"},"geometry":{"type":"Polygon","coordinates":[[[99.6403,-26.70736],[129,-43.08851],[129.00183,-25.99861],[129.00057,-25.99861],[129,-14.42902],[127.55165,-9.05052],[125.68138,-9.85176],[122.91521,-11.65621],[99.6403,-26.70736]]]}},{"type":"Feature","properties":{"id":"AU-NT"},"geometry":{"type":"Polygon","coordinates":[[[127.55165,-9.05052],[129,-14.42902],[129.00057,-25.99861],[129.00183,-25.99861],[137.99993,-25.99819],[138.00067,-16.23841],[139.41724,-8.97063],[127.55165,-9.05052]]]}},{"type":"Feature","properties":{"id":"AU-SA"},"geometry":{"type":"Polygon","coordinates":[[[129,-43.08851],[137.66184,-47.26522],[140.64335,-39.23791],[140.96369,-38.2743],[140.9638,-33.98067],[141.00268,-34.02172],[140.99934,-28.99903],[141.00013,-26.00101],[137.99993,-25.99819],[129.00183,-25.99861],[129,-43.08851]]]}},{"type":"Feature","properties":{"id":"AU-TAS"},"geometry":{"type":"Polygon","coordinates":[[[137.66184,-47.26522],[158.89388,-55.66823],[159.92772,-54.25538],[152.57175,-39.16128],[140.64335,-39.23791],[137.66184,-47.26522]]]}},{"type":"Feature","properties":{"id":"AU-VIC"},"geometry":{"type":"Polygon","coordinates":[[[140.64335,-39.23791],[152.57175,-39.16128],[150.19768,-37.59458],[148.19405,-36.79602],[148.10753,-36.79272],[148.20366,-36.59782],[148.04573,-36.39248],[147.99217,-36.0457],[147.71202,-35.94237],[147.39616,-35.94681],[147.31926,-36.05458],[147.10503,-36.00683],[147.04185,-36.09898],[146.85097,-36.08788],[146.59966,-35.97349],[146.42387,-35.96794],[146.36894,-36.03571],[145.80589,-35.98461],[145.50789,-35.80772],[145.34447,-35.86005],[145.1165,-35.81774],[144.9778,-35.86673],[144.94896,-36.05236],[144.74022,-36.11895],[144.08241,-35.57238],[143.56743,-35.33634],[143.57155,-35.20741],[143.39027,-35.18047],[143.3271,-34.99618],[143.34221,-34.79344],[142.76268,-34.56871],[142.61711,-34.77765],[142.50999,-34.74267],[142.37404,-34.34563],[142.24495,-34.3014],[142.22023,-34.18334],[142.0211,-34.12651],[141.71349,-34.0924],[141.5377,-34.18902],[141.00268,-34.02172],[140.9638,-33.98067],[140.96369,-38.2743],[140.64335,-39.23791]]]}},{"type":"Feature","properties":{"id":"AU-QLD"},"geometry":{"type":"Polygon","coordinates":[[[137.99993,-25.99819],[141.00013,-26.00101],[140.99934,-28.99903],[148.95806,-28.99906],[149.19248,-28.77479],[149.37788,-28.68628],[149.48705,-28.58202],[149.58044,-28.57056],[149.67519,-28.62723],[150.30004,-28.53558],[150.43737,-28.66098],[150.74499,-28.63446],[151.27508,-28.94017],[151.30392,-29.15627],[151.39318,-29.17186],[151.55248,-28.94858],[151.72552,-28.86683],[151.7777,-28.9606],[152.01391,-28.89449],[152.06472,-28.69351],[151.94662,-28.54282],[152.49731,-28.25168],[152.57696,-28.3327],[152.60442,-28.27466],[152.74725,-28.35929],[152.87222,-28.30852],[153.10293,-28.35445],[153.18121,-28.25289],[153.36385,-28.242],[153.47715,-28.15789],[153.53414,-28.17635],[153.55096,-28.16364],[155.3142,-27.34698],[153.5901,-24.72709],[149.03078,-19.80828],[144.30183,-9.48146],[142.81927,-9.31709],[142.5723,-9.35994],[142.31447,-9.24611],[142.23304,-9.19253],[142.1462,-9.19923],[142.0953,-9.23534],[142.0601,-9.56571],[140.88922,-9.34945],[139.41724,-8.97063],[138.00067,-16.23841],[137.99993,-25.99819]]]}},{"type":"Feature","properties":{"id":"AU-ACT"},"geometry":{"type":"Polygon","coordinates":[[[148.76247,-35.49504],[148.78891,-35.69995],[148.85723,-35.76043],[148.8768,-35.715],[148.89431,-35.75095],[148.89602,-35.82504],[148.96194,-35.8971],[149.04811,-35.91684],[149.09824,-35.81223],[149.09549,-35.6411],[149.07936,-35.58193],[149.14219,-35.59337],[149.12983,-35.55288],[149.15283,-35.50566],[149.13429,-35.45338],[149.2057,-35.34732],[149.25136,-35.33024],[149.33719,-35.33976],[149.35058,-35.3518],[149.39796,-35.32435],[149.39418,-35.30362],[149.23488,-35.24336],[149.2469,-35.2285],[149.18956,-35.20157],[149.19746,-35.18502],[149.12159,-35.1241],[148.80951,-35.30698],[148.76247,-35.49504]]]}},{"type":"Feature","properties":{"id":"IN-KA"},"geometry":{"type":"Polygon","coordinates":[[[73.87481,14.75496],[74.66307,12.69394],[74.86049,12.76057],[75.01327,12.79137],[74.98323,12.73998],[75.0531,12.71804],[75.04108,12.67484],[75.06872,12.66228],[75.08777,12.70029],[75.11438,12.67752],[75.16124,12.67969],[75.14579,12.63648],[75.19798,12.6132],[75.23471,12.56662],[75.28106,12.61856],[75.33393,12.57534],[75.27214,12.5502],[75.34698,12.46105],[75.39882,12.50161],[75.43659,12.47144],[75.37067,12.45602],[75.43109,12.31249],[75.49392,12.29103],[75.54302,12.20279],[75.78987,12.08296],[75.86059,11.95502],[76.00307,11.93185],[76.11259,11.97887],[76.11757,11.85105],[76.18949,11.87608],[76.42948,11.66568],[76.539,11.69123],[76.61281,11.60717],[76.85623,11.59506],[76.84249,11.66938],[76.91013,11.79308],[76.97193,11.77628],[76.98772,11.81291],[77.12059,11.71863],[77.25465,11.81241],[77.42906,11.76199],[77.46665,11.84887],[77.49704,11.9426],[77.67917,11.94898],[77.72724,12.05409],[77.77925,12.112],[77.725,12.17963],[77.45738,12.20681],[77.48931,12.2766],[77.61806,12.36649],[77.63523,12.49088],[77.58853,12.51803],[77.60089,12.66579],[77.67514,12.68363],[77.67943,12.65625],[77.71265,12.66395],[77.71196,12.6817],[77.74114,12.67065],[77.73994,12.69962],[77.76329,12.6956],[77.77582,12.72273],[77.76277,12.72658],[77.7662,12.73663],[77.79384,12.74768],[77.78062,12.76928],[77.81032,12.82987],[77.79247,12.84092],[77.83281,12.86151],[77.93683,12.88192],[77.91975,12.82828],[77.94782,12.8354],[77.95349,12.85966],[77.97005,12.83105],[78.00215,12.80359],[78.03322,12.85406],[78.08678,12.83146],[78.12309,12.76861],[78.23295,12.76526],[78.26076,12.86469],[78.315,12.85966],[78.35706,12.93965],[78.39054,12.90652],[78.46813,12.86971],[78.46744,12.90334],[78.43809,12.92743],[78.40908,12.94517],[78.47637,12.98699],[78.57181,13.18061],[78.57439,13.31512],[78.36547,13.3634],[78.38058,13.40581],[78.37818,13.50748],[78.40461,13.59343],[78.19879,13.58592],[78.19965,13.64348],[78.11622,13.65716],[78.11485,13.76456],[78.12635,13.86208],[78.06146,13.88791],[77.9516,13.9344],[77.81839,13.93523],[77.83229,13.86158],[77.72209,13.7889],[77.71934,13.74021],[77.6711,13.78773],[77.56227,13.72721],[77.5245,13.75839],[77.52983,13.69585],[77.49206,13.71303],[77.48022,13.67751],[77.45326,13.70236],[77.47369,13.76289],[77.45206,13.81057],[77.41945,13.84541],[77.26221,13.85341],[77.23766,13.90824],[77.16556,13.9034],[77.1302,13.84891],[77.18393,13.76189],[77.05278,13.76739],[77.03081,13.78256],[77.00265,13.72837],[76.97862,13.82941],[77.05141,13.9214],[76.98772,14.0002],[76.93948,14.01719],[76.94463,14.05233],[76.98326,14.08596],[76.88112,14.14156],[76.95339,14.19233],[77.02651,14.16736],[77.0284,14.05432],[77.13415,14.03684],[77.15234,13.99187],[77.31954,14.03084],[77.35765,13.9339],[77.41172,13.88791],[77.45017,13.94772],[77.43215,13.98237],[77.33791,14.03967],[77.40949,14.12026],[77.38924,14.17419],[77.51472,14.15754],[77.48502,14.29332],[77.3628,14.34954],[77.4282,14.19998],[77.3767,14.21113],[77.27045,14.33241],[77.13466,14.33923],[77.0866,14.191],[77.04952,14.24774],[76.93862,14.24624],[76.89125,14.3966],[76.98205,14.48288],[76.80335,14.53257],[76.76628,14.67925],[76.86309,14.8401],[76.86996,14.97082],[76.76336,14.98442],[76.80044,15.09681],[76.98308,15.00945],[77.1132,15.02985],[77.17037,15.17619],[77.16281,15.29279],[77.04145,15.37292],[77.03201,15.43614],[76.97227,15.50992],[77.09278,15.71625],[77.03613,15.856],[77.11818,15.94036],[77.27113,15.96132],[77.44039,15.94548],[77.51197,15.92864],[77.49893,16.26906],[77.60364,16.29657],[77.59523,16.34336],[77.48451,16.38223],[77.28847,16.40595],[77.2344,16.47658],[77.32366,16.48942],[77.37636,16.48481],[77.47198,16.587],[77.47095,16.71216],[77.427,16.72252],[77.44279,16.78712],[77.47489,16.77956],[77.45532,16.92068],[77.50099,17.01657],[77.46356,17.11454],[77.36623,17.15883],[77.46202,17.28607],[77.45155,17.3721],[77.52244,17.35178],[77.53583,17.44007],[77.62716,17.44335],[77.67608,17.52751],[77.52571,17.57693],[77.44005,17.57693],[77.45841,17.70568],[77.57823,17.74378],[77.50837,17.79298],[77.66166,17.9686],[77.56296,18.04925],[77.61377,18.10033],[77.56785,18.28849],[77.5082,18.31183],[77.49343,18.26847],[77.42752,18.31118],[77.35988,18.30857],[77.41653,18.39427],[77.37138,18.40095],[77.36108,18.45225],[77.324,18.41838],[77.30718,18.44053],[77.24899,18.40714],[77.20822,18.2785],[77.17234,18.28453],[77.11741,18.19869],[77.11758,18.15775],[77.05355,18.15955],[77.03733,18.18189],[77.00351,18.16053],[76.98205,18.19355],[76.91871,18.12431],[76.9666,18.09592],[76.94532,18.08042],[76.94978,18.03848],[76.91596,18.03489],[76.91305,17.96779],[76.92043,17.91291],[76.88129,17.89413],[76.76868,17.90001],[76.7383,17.88269],[76.79134,17.82289],[76.69898,17.72792],[76.6892,17.68082],[76.61178,17.77631],[76.57402,17.70061],[76.56423,17.76928],[76.52166,17.75833],[76.5172,17.71925],[76.48424,17.71206],[76.40201,17.60426],[76.32974,17.57939],[76.34674,17.49468],[76.33043,17.4558],[76.37128,17.42943],[76.37592,17.37259],[76.40596,17.3526],[76.36545,17.30885],[76.31429,17.33998],[76.27326,17.32917],[76.24031,17.37652],[76.17439,17.34654],[76.18606,17.30459],[76.11928,17.37242],[76.07276,17.33081],[76.05525,17.36178],[75.92428,17.32835],[75.892,17.36816],[75.82609,17.42517],[75.69425,17.40994],[75.63846,17.49346],[75.58439,17.34883],[75.68206,16.95303],[75.27969,16.95697],[75.20553,16.83214],[75.07713,16.95566],[74.92744,16.93793],[74.90341,16.77101],[74.69398,16.7138],[74.6284,16.57762],[74.4691,16.65888],[74.37057,16.53748],[74.26483,16.51904],[74.35958,16.37746],[74.32662,16.31816],[74.50241,16.22489],[74.48249,16.09694],[74.41486,16.10255],[74.36576,16.03822],[74.46292,16.04746],[74.33967,15.85171],[74.34345,15.76417],[74.20165,15.77903],[74.11617,15.65411],[74.25659,15.64551],[74.24903,15.49206],[74.33486,15.28352],[74.25075,15.25371],[74.31838,15.1805],[74.2741,15.09996],[74.29195,15.02869],[74.20543,14.92819],[74.16217,14.94976],[73.87481,14.75496]]]}},{"type":"Feature","properties":{"id":"NL-BQ3"},"geometry":{"type":"Polygon","coordinates":[[[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-63.22932,17.32592]]]}},{"type":"Feature","properties":{"id":"CA-BC"},"geometry":{"type":"Polygon","coordinates":[[[-139.05365,59.99655],[-138.71149,59.90728],[-138.62145,59.76431],[-137.60623,59.24465],[-137.4925,58.89415],[-136.82619,59.16198],[-136.52365,59.16752],[-136.47323,59.46617],[-136.33727,59.44466],[-136.22381,59.55526],[-136.31566,59.59083],[-135.48007,59.79937],[-135.03069,59.56208],[-135.00267,59.28745],[-134.7047,59.2458],[-134.55699,59.1297],[-134.48059,59.13231],[-134.27175,58.8634],[-133.84645,58.73543],[-133.38523,58.42773],[-131.8271,56.62247],[-130.77769,56.36185],[-130.33965,56.10849],[-130.10173,56.12178],[-130.00093,56.00325],[-130.00857,55.91344],[-130.15373,55.74895],[-129.97513,55.28029],[-130.08035,55.21556],[-130.18765,55.07744],[-130.27203,54.97174],[-130.44184,54.85377],[-130.64499,54.76912],[-130.61931,54.70835],[-133.92876,54.62289],[-133.36909,48.51151],[-125.03842,48.53282],[-123.50039,48.21223],[-123.15614,48.35395],[-123.26565,48.6959],[-123.0093,48.76586],[-123.0093,48.83186],[-123.32163,49.00419],[-117.03266,49.00056],[-116.04938,48.99999],[-114.0683,48.99885],[-114.74352,49.57064],[-114.70812,50.29534],[-120.00143,53.79861],[-120.00135,60.00043],[-123.86203,59.99964],[-139.05365,59.99655]]]}},{"type":"Feature","properties":{"id":"CA-AB"},"geometry":{"type":"Polygon","coordinates":[[[-120.00143,53.79861],[-114.70812,50.29534],[-114.74352,49.57064],[-114.0683,48.99885],[-110.0051,48.99901],[-110.00637,59.99944],[-120.00135,60.00043],[-120.00143,53.79861]]]}},{"type":"Feature","properties":{"id":"CA-SK"},"geometry":{"type":"Polygon","coordinates":[[[-110.00637,59.99944],[-110.0051,48.99901],[-104.05004,48.99925],[-101.36198,48.99935],[-101.98961,55.81762],[-102.00759,59.99922],[-110.00637,59.99944]]]}},{"type":"Feature","properties":{"id":"CA-MB"},"geometry":{"type":"Polygon","coordinates":[[[-102.00759,59.99922],[-101.98961,55.81762],[-101.36198,48.99935],[-97.24024,48.99952],[-95.15355,48.9996],[-95.15357,49.384],[-95.15374,52.84191],[-88.99084,56.84662],[-94.78348,59.99917],[-102.00759,59.99922]]]}},{"type":"Feature","properties":{"id":"IN-WB"},"geometry":{"type":"Polygon","coordinates":[[[85.82279,23.26784],[85.92269,23.12236],[86.04423,23.14572],[86.21761,22.98747],[86.5472,22.97641],[86.42188,22.77586],[86.78031,22.56487],[86.88812,22.24715],[86.7247,22.21569],[86.71371,22.1448],[86.94992,22.08627],[87.05635,21.85066],[87.22457,21.96024],[87.27264,21.81114],[87.43709,21.76045],[87.74779,20.78694],[89.13606,21.42955],[89.13927,21.60785],[89.03553,21.77397],[89.07114,22.15335],[88.9367,22.58527],[88.94614,22.66941],[88.9151,22.75228],[88.96713,22.83346],[88.87063,22.95235],[88.88327,23.03885],[88.86377,23.08759],[88.99148,23.21134],[88.71133,23.2492],[88.79254,23.46028],[88.79351,23.50535],[88.74841,23.47361],[88.56507,23.64044],[88.58087,23.87105],[88.66189,23.87607],[88.73743,23.91751],[88.6976,24.14703],[88.74841,24.1959],[88.68801,24.31464],[88.50934,24.32474],[88.12296,24.51301],[88.08786,24.63232],[88.00683,24.66477],[88.15515,24.85806],[88.14004,24.93529],[88.21832,24.96642],[88.27325,24.88796],[88.33917,24.86803],[88.46277,25.07468],[88.44766,25.20149],[88.94067,25.18534],[89.00463,25.26583],[89.01105,25.30303],[88.85278,25.34679],[88.81296,25.51546],[88.677,25.46959],[88.4559,25.59227],[88.45103,25.66245],[88.242,25.80811],[88.13138,25.78773],[88.08804,25.91334],[88.16581,26.0238],[88.1844,26.14417],[88.34757,26.22216],[88.35153,26.29123],[88.51649,26.35923],[88.48749,26.45855],[88.36938,26.48683],[88.35153,26.45241],[88.33093,26.48929],[88.41196,26.63837],[88.4298,26.54489],[88.62144,26.46783],[88.69485,26.38353],[88.67837,26.26291],[88.78961,26.31093],[88.85004,26.23211],[89.05328,26.2469],[88.91321,26.37984],[88.92357,26.40711],[88.95612,26.4564],[89.08899,26.38845],[89.15869,26.13708],[89.35953,26.0077],[89.53515,26.00382],[89.57101,25.9682],[89.63968,26.22595],[89.70201,26.15138],[89.73581,26.15818],[89.71298,26.26755],[89.84653,26.40078],[89.86885,26.46258],[89.86124,26.73307],[89.63369,26.74402],[89.42349,26.83727],[89.3901,26.84225],[89.38319,26.85963],[89.37913,26.86224],[89.1926,26.81329],[89.12825,26.81661],[89.09554,26.89089],[88.95807,26.92668],[88.92301,26.99286],[88.8714,26.97488],[88.86984,27.10937],[88.74219,27.144],[88.62842,27.1731],[88.52045,27.17753],[88.45161,27.07655],[88.33505,27.10176],[88.30226,27.12682],[88.22656,27.11842],[88.08563,27.14072],[88.08331,27.16501],[88.04932,27.21631],[88.01587,27.21388],[87.9887,27.11045],[88.11719,26.98758],[88.13422,26.98705],[88.12302,26.95324],[88.19107,26.75516],[88.1659,26.68177],[88.16452,26.64111],[88.09963,26.54195],[88.14313,26.51097],[88.23394,26.55413],[88.19137,26.47549],[88.26158,26.4263],[88.23034,26.37679],[88.29093,26.3568],[88.0046,26.14341],[87.95036,26.06511],[87.84393,26.04537],[87.78625,25.87559],[87.90058,25.85304],[87.89989,25.76866],[88.04923,25.68794],[88.03859,25.54182],[88.0688,25.48047],[87.92667,25.53717],[87.76514,25.42529],[87.78865,25.34495],[87.86521,25.27139],[87.78831,25.15212],[87.95173,24.97174],[87.83431,24.7381],[87.89543,24.57616],[87.77904,24.57147],[87.7811,24.34928],[87.63587,24.21628],[87.71038,24.14048],[87.4443,23.97527],[87.23281,24.04238],[87.27933,23.87516],[87.08312,23.80639],[86.89704,23.89054],[86.79748,23.82806],[86.8198,23.76115],[86.79508,23.68351],[86.7374,23.68194],[86.59286,23.67156],[86.5242,23.62628],[86.44214,23.62816],[86.31374,23.42544],[86.14208,23.47647],[86.14311,23.56619],[86.0123,23.56619],[86.04389,23.48245],[85.87223,23.47489],[85.8633,23.41316],[85.89969,23.37062],[85.82279,23.26784]]]}},{"type":"Feature","properties":{"id":"FM-KSA"},"geometry":{"type":"Polygon","coordinates":[[[161.58988,8.892633],[161.87397,3.186785],[165.35175,6.367],[161.58988,8.892633]]]}},{"type":"Feature","properties":{"id":"CA-ON"},"geometry":{"type":"Polygon","coordinates":[[[-95.15374,52.84191],[-95.15357,49.384],[-95.12903,49.37056],[-95.05825,49.35311],[-95.01419,49.35647],[-94.99532,49.36579],[-94.95681,49.37035],[-94.85381,49.32492],[-94.8159,49.32299],[-94.82487,49.29483],[-94.77355,49.11998],[-94.75017,49.09931],[-94.687,48.84077],[-94.70087,48.8339],[-94.70486,48.82365],[-94.69669,48.80918],[-94.69335,48.77883],[-94.58903,48.71803],[-94.54885,48.71543],[-94.53826,48.70216],[-94.44258,48.69223],[-94.4174,48.71049],[-94.27153,48.70232],[-94.25172,48.68404],[-94.25104,48.65729],[-94.23215,48.65202],[-93.85769,48.63284],[-93.83288,48.62745],[-93.80676,48.58232],[-93.80939,48.52439],[-93.79267,48.51631],[-93.66382,48.51845],[-93.47022,48.54357],[-93.44472,48.59147],[-93.40693,48.60948],[-93.39758,48.60364],[-93.3712,48.60599],[-93.33946,48.62787],[-93.25391,48.64266],[-92.94973,48.60866],[-92.7287,48.54005],[-92.6342,48.54133],[-92.62747,48.50278],[-92.69927,48.49573],[-92.71323,48.46081],[-92.65606,48.43471],[-92.50712,48.44921],[-92.45588,48.40624],[-92.48147,48.36609],[-92.37185,48.22259],[-92.27167,48.25046],[-92.30939,48.31251],[-92.26662,48.35651],[-92.202,48.35252],[-92.14732,48.36578],[-92.05339,48.35958],[-91.98929,48.25409],[-91.86125,48.21278],[-91.71231,48.19875],[-91.70451,48.11805],[-91.55649,48.10611],[-91.58025,48.04339],[-91.45829,48.07454],[-91.43248,48.04912],[-91.25025,48.08522],[-91.08016,48.18096],[-90.87588,48.2484],[-90.75045,48.09143],[-90.56444,48.12184],[-90.56312,48.09488],[-90.07418,48.11043],[-89.89974,47.98109],[-89.77248,48.02607],[-89.57972,48.00023],[-89.48837,48.01412],[-88.37033,48.30586],[-84.85871,46.88881],[-84.55635,46.45974],[-84.47607,46.45225],[-84.4481,46.48972],[-84.42101,46.49853],[-84.34174,46.50683],[-84.29893,46.49127],[-84.26351,46.49508],[-84.2264,46.53337],[-84.1945,46.54061],[-84.17723,46.52753],[-84.12885,46.53068],[-84.11196,46.50248],[-84.13451,46.39218],[-84.11254,46.32329],[-84.11615,46.2681],[-84.09756,46.25512],[-84.1096,46.23987],[-83.95399,46.05634],[-83.90453,46.05922],[-83.83329,46.12169],[-83.57017,46.105],[-83.43746,45.99749],[-83.59589,45.82131],[-82.48419,45.30225],[-82.42469,42.992],[-82.4146,42.97626],[-82.4253,42.95423],[-82.45331,42.93139],[-82.4826,42.8068],[-82.46613,42.76615],[-82.51063,42.66025],[-82.51858,42.611],[-82.57583,42.5718],[-82.58873,42.54984],[-82.64242,42.55594],[-82.82964,42.37355],[-83.02253,42.33045],[-83.07837,42.30978],[-83.09837,42.28877],[-83.12724,42.2376],[-83.14962,42.04089],[-83.11184,41.95671],[-82.67862,41.67615],[-80.53581,42.29896],[-79.77073,42.55308],[-78.93684,42.82887],[-78.90712,42.89733],[-78.90905,42.93022],[-78.93224,42.95229],[-78.96312,42.95509],[-78.98126,42.97],[-79.02074,42.98444],[-79.02424,43.01983],[-78.99941,43.05612],[-79.01055,43.06659],[-79.07486,43.07845],[-79.05671,43.10937],[-79.06881,43.12029],[-79.0427,43.13934],[-79.04652,43.16396],[-79.05384,43.17418],[-79.05002,43.20133],[-79.05544,43.21224],[-79.05512,43.25375],[-79.06921,43.26183],[-79.25796,43.54052],[-76.79706,43.63099],[-76.43859,44.09393],[-76.35324,44.13493],[-76.31222,44.19894],[-76.244,44.19643],[-76.1664,44.23051],[-76.16285,44.28262],[-76.00018,44.34896],[-75.95947,44.34463],[-75.8217,44.43176],[-75.76813,44.51537],[-75.41441,44.76614],[-75.2193,44.87821],[-75.01363,44.95608],[-74.99101,44.98051],[-74.8447,45.00606],[-74.66689,45.00646],[-74.50073,45.06927],[-74.32151,45.18844],[-74.47257,45.30253],[-74.38125,45.56562],[-74.48081,45.59638],[-74.52819,45.59157],[-74.61059,45.62183],[-74.64286,45.64008],[-74.71427,45.62856],[-74.94087,45.64536],[-75.02601,45.59589],[-75.13038,45.57715],[-75.24162,45.58677],[-75.33981,45.53581],[-75.4804,45.51368],[-75.58151,45.47361],[-75.61876,45.47192],[-75.68562,45.45826],[-75.69334,45.45187],[-75.70287,45.43712],[-75.70424,45.42706],[-75.70957,45.42345],[-75.71772,45.42158],[-75.7239,45.422],[-75.72381,45.41766],[-75.72982,45.41736],[-75.75935,45.4114],[-75.80535,45.3762],[-75.8493,45.37716],[-75.91522,45.41477],[-75.97152,45.47643],[-76.091,45.51735],[-76.19468,45.52023],[-76.23245,45.51109],[-76.24343,45.46873],[-76.36772,45.45814],[-76.5023,45.51638],[-76.60873,45.53226],[-76.65885,45.56015],[-76.67602,45.58466],[-76.6719,45.62357],[-76.71653,45.66438],[-76.68975,45.69172],[-76.77558,45.75308],[-76.7646,45.84978],[-76.77696,45.87273],[-76.893,45.90141],[-76.93077,45.89137],[-76.91085,45.80624],[-76.94107,45.789],[-76.98982,45.78613],[-77.01866,45.80863],[-77.04681,45.8072],[-77.07565,45.83543],[-77.12097,45.84165],[-77.19238,45.86556],[-77.2734,45.93962],[-77.28782,45.98258],[-77.27683,46.0174],[-77.31803,46.02741],[-77.35717,46.05696],[-77.67852,46.19925],[-77.69363,46.18452],[-78.24981,46.27714],[-78.31642,46.25388],[-78.38508,46.29138],[-78.703,46.32173],[-78.72703,46.3407],[-78.73115,46.38619],[-78.88496,46.45624],[-78.98796,46.54604],[-79.01817,46.63664],[-79.04563,46.64607],[-79.08958,46.68518],[-79.17335,46.82678],[-79.21317,46.83335],[-79.44869,47.10512],[-79.42535,47.25307],[-79.48921,47.30711],[-79.58877,47.42941],[-79.55581,47.51342],[-79.51787,47.53313],[-79.51659,51.46031],[-82.09357,52.92137],[-82.10455,55.13419],[-88.99084,56.84662],[-95.15374,52.84191]]]}},{"type":"Feature","properties":{"id":"CA-NS"},"geometry":{"type":"Polygon","coordinates":[[[-67.16117,44.20069],[-65.81187,42.91911],[-59.437,43.71683],[-57.60106,47.38123],[-60.49316,47.38172],[-61.75622,46.42845],[-62.49954,45.85952],[-63.58306,46.10096],[-64.03625,46.01901],[-64.04861,45.97703],[-64.1571,45.9799],[-64.28619,45.83274],[-64.33563,45.8557],[-64.54574,45.68615],[-67.16117,44.20069]]]}},{"type":"Feature","properties":{"id":"CA-PE"},"geometry":{"type":"Polygon","coordinates":[[[-64.66796,46.6005],[-63.75335,46.22936],[-63.58306,46.10096],[-62.49954,45.85952],[-61.75622,46.42845],[-64.01702,47.11511],[-64.66796,46.6005]]]}},{"type":"Feature","properties":{"id":"FM-PNI"},"geometry":{"type":"Polygon","coordinates":[[[153.83475,11.01511],[154.49668,-0.44964],[156.88247,-1.39237],[161.87397,3.186785],[161.58988,8.892633],[159.04653,10.59067],[153.83475,11.01511]]]}},{"type":"Feature","properties":{"id":"CA-NB"},"geometry":{"type":"Polygon","coordinates":[[[-69.05073,47.30076],[-69.05039,47.2456],[-68.89222,47.1807],[-68.70125,47.24399],[-68.60575,47.24659],[-68.57914,47.28431],[-68.38332,47.28723],[-68.37458,47.35851],[-68.23244,47.35712],[-67.94843,47.1925],[-67.87993,47.10377],[-67.78578,47.06473],[-67.78111,45.9392],[-67.75196,45.91814],[-67.80961,45.87531],[-67.75654,45.82324],[-67.80653,45.80022],[-67.80705,45.69528],[-67.6049,45.60725],[-67.43815,45.59162],[-67.42144,45.50584],[-67.50578,45.48971],[-67.42394,45.37969],[-67.48201,45.27351],[-67.34927,45.122],[-67.29754,45.14865],[-67.29748,45.18173],[-67.27039,45.1934],[-67.22751,45.16344],[-67.20349,45.1722],[-67.19603,45.16771],[-67.15965,45.16179],[-67.11316,45.11176],[-67.0216,44.95333],[-66.96824,44.90965],[-66.98249,44.87071],[-66.96824,44.83078],[-66.93432,44.82597],[-67.16117,44.20069],[-64.54574,45.68615],[-64.33563,45.8557],[-64.28619,45.83274],[-64.1571,45.9799],[-64.04861,45.97703],[-64.03625,46.01901],[-63.58306,46.10096],[-63.75335,46.22936],[-64.66796,46.6005],[-64.01702,47.11511],[-64.29855,48.12037],[-65.19393,47.92013],[-66.28295,48.03872],[-66.37908,48.08919],[-66.50405,48.0791],[-66.52465,48.04698],[-66.69012,48.00944],[-66.93664,47.97716],[-66.96548,47.89159],[-67.06161,47.93117],[-67.37884,47.85751],[-67.60406,47.93209],[-67.60543,48.00014],[-68.12316,48.00014],[-68.12454,47.91461],[-68.38409,47.91553],[-68.38546,47.55439],[-68.57223,47.42727],[-68.80844,47.34824],[-69.05073,47.30076]]]}},{"type":"Feature","properties":{"id":"CA-NL"},"geometry":{"type":"Polygon","coordinates":[[[-67.83623,54.45267],[-67.35283,52.90413],[-64.55407,51.57984],[-63.8082,51.99787],[-57.10774,51.99856],[-57.10928,51.37809],[-60.49316,47.38172],[-57.60106,47.38123],[-56.67989,47.3339],[-56.25228,47.31192],[-55.8643,46.64935],[-51.16966,45.93987],[-45.64471,55.43944],[-53.68108,62.9266],[-64.79302,60.27404],[-63.7603,54.63112],[-66.84746,55.09899],[-67.83623,54.45267]]]}},{"type":"Feature","properties":{"id":"CA-QC"},"geometry":{"type":"Polygon","coordinates":[[[-79.92119,54.66449],[-78.74291,52.08899],[-79.56139,51.58666],[-79.51659,51.46031],[-79.51787,47.53313],[-79.55581,47.51342],[-79.58877,47.42941],[-79.48921,47.30711],[-79.42535,47.25307],[-79.44869,47.10512],[-79.21317,46.83335],[-79.17335,46.82678],[-79.08958,46.68518],[-79.04563,46.64607],[-79.01817,46.63664],[-78.98796,46.54604],[-78.88496,46.45624],[-78.73115,46.38619],[-78.72703,46.3407],[-78.703,46.32173],[-78.38508,46.29138],[-78.31642,46.25388],[-78.24981,46.27714],[-77.69363,46.18452],[-77.67852,46.19925],[-77.35717,46.05696],[-77.31803,46.02741],[-77.27683,46.0174],[-77.28782,45.98258],[-77.2734,45.93962],[-77.19238,45.86556],[-77.12097,45.84165],[-77.07565,45.83543],[-77.04681,45.8072],[-77.01866,45.80863],[-76.98982,45.78613],[-76.94107,45.789],[-76.91085,45.80624],[-76.93077,45.89137],[-76.893,45.90141],[-76.77696,45.87273],[-76.7646,45.84978],[-76.77558,45.75308],[-76.68975,45.69172],[-76.71653,45.66438],[-76.6719,45.62357],[-76.67602,45.58466],[-76.65885,45.56015],[-76.60873,45.53226],[-76.5023,45.51638],[-76.36772,45.45814],[-76.24343,45.46873],[-76.23245,45.51109],[-76.19468,45.52023],[-76.091,45.51735],[-75.97152,45.47643],[-75.91522,45.41477],[-75.8493,45.37716],[-75.80535,45.3762],[-75.75935,45.4114],[-75.72982,45.41736],[-75.72381,45.41766],[-75.7239,45.422],[-75.71772,45.42158],[-75.70957,45.42345],[-75.70424,45.42706],[-75.70287,45.43712],[-75.69334,45.45187],[-75.68562,45.45826],[-75.61876,45.47192],[-75.58151,45.47361],[-75.4804,45.51368],[-75.33981,45.53581],[-75.24162,45.58677],[-75.13038,45.57715],[-75.02601,45.59589],[-74.94087,45.64536],[-74.71427,45.62856],[-74.64286,45.64008],[-74.61059,45.62183],[-74.52819,45.59157],[-74.48081,45.59638],[-74.38125,45.56562],[-74.47257,45.30253],[-74.32151,45.18844],[-74.50073,45.06927],[-74.66689,45.00646],[-74.32699,44.99029],[-73.35025,45.00942],[-71.50067,45.01357],[-71.48735,45.07784],[-71.42778,45.12624],[-71.40364,45.21382],[-71.44252,45.2361],[-71.37133,45.24624],[-71.29371,45.29996],[-71.22338,45.25184],[-71.19723,45.25438],[-71.14568,45.24128],[-71.08364,45.30623],[-71.01866,45.31573],[-71.0107,45.34819],[-70.95193,45.33895],[-70.91169,45.29849],[-70.89864,45.2398],[-70.84816,45.22698],[-70.80236,45.37444],[-70.82638,45.39828],[-70.78372,45.43269],[-70.65383,45.37592],[-70.62518,45.42286],[-70.72651,45.49771],[-70.68516,45.56964],[-70.54019,45.67291],[-70.38934,45.73215],[-70.41523,45.79497],[-70.25976,45.89675],[-70.24694,45.95138],[-70.31025,45.96424],[-70.23855,46.1453],[-70.29078,46.18832],[-70.18547,46.35357],[-70.05812,46.41768],[-69.99966,46.69543],[-69.22119,47.46461],[-69.05148,47.42012],[-69.05073,47.30076],[-68.80844,47.34824],[-68.57223,47.42727],[-68.38546,47.55439],[-68.38409,47.91553],[-68.12454,47.91461],[-68.12316,48.00014],[-67.60543,48.00014],[-67.60406,47.93209],[-67.37884,47.85751],[-67.06161,47.93117],[-66.96548,47.89159],[-66.93664,47.97716],[-66.69012,48.00944],[-66.52465,48.04698],[-66.50405,48.0791],[-66.37908,48.08919],[-66.28295,48.03872],[-65.19393,47.92013],[-64.29855,48.12037],[-64.01702,47.11511],[-61.75622,46.42845],[-60.49316,47.38172],[-57.10928,51.37809],[-57.10774,51.99856],[-63.8082,51.99787],[-64.55407,51.57984],[-67.35283,52.90413],[-67.83623,54.45267],[-66.84746,55.09899],[-63.7603,54.63112],[-64.79302,60.27404],[-68.84423,60.00396],[-69.3496,61.12682],[-73.67821,62.57769],[-78.62206,62.65853],[-79.23729,58.64783],[-77.32567,57.55654],[-77.17186,56.02851],[-78.98049,54.90049],[-79.92119,54.66449]]]}},{"type":"Feature","properties":{"id":"CA-YT"},"geometry":{"type":"Polygon","coordinates":[[[-141.00555,72.20369],[-141.00116,60.30648],[-140.5227,60.22077],[-140.45648,60.30919],[-139.98024,60.18027],[-139.68991,60.33693],[-139.05831,60.35205],[-139.20603,60.08896],[-139.05365,59.99655],[-123.86203,59.99964],[-124.71898,60.9114],[-126.80638,60.77223],[-134.09581,67.00286],[-136.15918,67.00031],[-136.20381,67.0511],[-136.21788,67.59731],[-136.4486,67.65377],[-136.4486,68.88195],[-136.61065,69.40701],[-141.00555,72.20369]]]}},{"type":"Feature","properties":{"id":"CA-NT"},"geometry":{"type":"Polygon","coordinates":[[[-141.00555,72.20369],[-136.61065,69.40701],[-136.4486,68.88195],[-136.4486,67.65377],[-136.21788,67.59731],[-136.20381,67.0511],[-136.15918,67.00031],[-134.09581,67.00286],[-126.80638,60.77223],[-124.71898,60.9114],[-123.86203,59.99964],[-120.00135,60.00043],[-110.00637,59.99944],[-102.00759,59.99922],[-102.08165,64.2419],[-111.26622,65.10654],[-120.71446,68.0217],[-120.71446,69.63965],[-110.07969,70.00345],[-110.08928,79.74266],[-141.00555,72.20369]]]}},{"type":"Feature","properties":{"id":"CA-NU"},"geometry":{"type":"Polygon","coordinates":[[[-120.71446,68.0217],[-111.26622,65.10654],[-102.08165,64.2419],[-102.00759,59.99922],[-94.78348,59.99917],[-88.99084,56.84662],[-82.10455,55.13419],[-82.09357,52.92137],[-79.51659,51.46031],[-79.56139,51.58666],[-78.74291,52.08899],[-79.92119,54.66449],[-78.98049,54.90049],[-77.17186,56.02851],[-77.32567,57.55654],[-79.23729,58.64783],[-78.62206,62.65853],[-73.67821,62.57769],[-69.3496,61.12682],[-68.84423,60.00396],[-64.79302,60.27404],[-53.68108,62.9266],[-74.12379,75.70014],[-73.91222,78.42484],[-67.48417,80.75493],[-63.1988,81.66522],[-59.93819,82.31398],[-62.36036,83.40597],[-85.36473,83.41631],[-110.08928,79.74266],[-110.07969,70.00345],[-120.71446,69.63965],[-120.71446,68.0217]]]}},{"type":"Feature","properties":{"id":"US-DC"},"geometry":{"type":"Polygon","coordinates":[[[-77.11962,38.93441],[-77.11536,38.92787],[-77.10592,38.91912],[-77.10201,38.91264],[-77.08897,38.90436],[-77.07047,38.90106],[-77.06759,38.89895],[-77.05957,38.88152],[-77.05493,38.87964],[-77.0491,38.87343],[-77.04592,38.87557],[-77.03021,38.86133],[-77.03906,38.79153],[-76.90939,38.89301],[-77.04117,38.99552],[-77.11962,38.93441]]]}},{"type":"Feature","properties":{"id":"US-AL"},"geometry":{"type":"Polygon","coordinates":[[[-88.47496,31.89065],[-88.37952,30.00457],[-87.51915,30.07055],[-87.51915,30.28187],[-87.44774,30.30677],[-87.50679,30.31863],[-87.3777,30.44658],[-87.42989,30.47144],[-87.44774,30.52113],[-87.39418,30.62518],[-87.40654,30.67362],[-87.52739,30.74093],[-87.63588,30.86596],[-87.59056,30.96375],[-87.5988,30.99671],[-85.00191,31.0026],[-85.00191,31.02143],[-85.09804,31.16255],[-85.10902,31.27295],[-85.04311,31.52965],[-85.07332,31.62676],[-85.14336,31.844],[-85.05547,32.01766],[-85.06233,32.13519],[-84.88518,32.26185],[-85.0074,32.33034],[-84.9662,32.42661],[-84.99916,32.51697],[-85.07057,32.5818],[-85.18318,32.85909],[-85.60478,34.98639],[-88.20305,35.00664],[-88.20029,34.99532],[-88.14949,34.9211],[-88.09868,34.89407],[-88.47496,31.89065]]]}},{"type":"Feature","properties":{"id":"FM-TRK"},"geometry":{"type":"Polygon","coordinates":[[[148.42679,11.45488],[148.49862,1.920402],[154.49668,-0.44964],[153.83475,11.01511],[148.42679,11.45488]]]}},{"type":"Feature","properties":{"id":"US-AZ"},"geometry":{"type":"Polygon","coordinates":[[[-114.82011,32.49609],[-112.34553,31.7357],[-111.07523,31.33232],[-109.05235,31.3333],[-109.04686,37.00061],[-114.05113,37.00171],[-114.04564,36.1991],[-114.12254,36.11372],[-114.14864,36.02936],[-114.2379,36.01715],[-114.30931,36.06379],[-114.37523,36.147],[-114.57298,36.15254],[-114.61281,36.13036],[-114.63066,36.14367],[-114.75014,36.09486],[-114.72404,36.03159],[-114.74052,36.0127],[-114.74602,35.98271],[-114.66774,35.87039],[-114.70482,35.85592],[-114.68971,35.65197],[-114.65263,35.60956],[-114.68147,35.49783],[-114.59633,35.33331],[-114.57024,35.15498],[-114.5826,35.12803],[-114.62791,35.12129],[-114.64577,35.10444],[-114.60732,35.07635],[-114.64165,35.01451],[-114.63203,34.86704],[-114.58672,34.83773],[-114.5565,34.77233],[-114.47411,34.71478],[-114.42879,34.58939],[-114.37798,34.52153],[-114.38484,34.45361],[-114.3409,34.44682],[-114.1404,34.3074],[-114.13628,34.26315],[-114.3821,34.11434],[-114.53728,33.93223],[-114.50432,33.87182],[-114.52766,33.85015],[-114.49745,33.69718],[-114.52904,33.68461],[-114.53316,33.55196],[-114.65263,33.41336],[-114.72954,33.40763],[-114.69795,33.35947],[-114.72816,33.3044],[-114.67186,33.22517],[-114.70894,33.0918],[-114.61693,33.0262],[-114.5208,33.03196],[-114.46724,32.91098],[-114.47136,32.84294],[-114.5359,32.79216],[-114.53178,32.75405],[-114.59633,32.73442],[-114.70482,32.7425],[-114.71871,32.71894],[-114.76736,32.64094],[-114.80584,32.62028],[-114.81141,32.55543],[-114.79524,32.55731],[-114.82011,32.49609]]]}},{"type":"Feature","properties":{"id":"US-AR"},"geometry":{"type":"Polygon","coordinates":[[[-94.61906,36.49995],[-94.43092,35.38371],[-94.48448,33.64004],[-94.38423,33.56455],[-94.07661,33.57828],[-94.0464,33.55539],[-94.04366,33.01929],[-91.16798,33.00432],[-91.14052,33.29866],[-91.08696,33.96299],[-90.58846,34.49776],[-90.56236,34.72946],[-90.24925,34.93349],[-90.31242,34.99989],[-90.16136,35.13252],[-90.07072,35.1314],[-90.11604,35.38483],[-89.89357,35.64528],[-89.94301,35.66871],[-89.95399,35.73228],[-89.72878,35.8047],[-89.70131,35.83366],[-89.77409,35.87261],[-89.73564,35.91044],[-89.67384,35.8804],[-89.64638,35.91489],[-89.73701,36.00048],[-90.37834,35.99826],[-90.11467,36.26446],[-90.07759,36.27442],[-90.07347,36.39833],[-90.13939,36.41711],[-90.1545,36.49885],[-94.61906,36.49995]]]}},{"type":"Feature","properties":{"id":"US-CA"},"geometry":{"type":"Polygon","coordinates":[[[-125.69978,42.00605],[-122.18305,33.57011],[-118.48109,32.5991],[-117.1243,32.53427],[-115.88053,32.63624],[-114.71871,32.71894],[-114.70482,32.7425],[-114.59633,32.73442],[-114.53178,32.75405],[-114.5359,32.79216],[-114.47136,32.84294],[-114.46724,32.91098],[-114.5208,33.03196],[-114.61693,33.0262],[-114.70894,33.0918],[-114.67186,33.22517],[-114.72816,33.3044],[-114.69795,33.35947],[-114.72954,33.40763],[-114.65263,33.41336],[-114.53316,33.55196],[-114.52904,33.68461],[-114.49745,33.69718],[-114.52766,33.85015],[-114.50432,33.87182],[-114.53728,33.93223],[-114.3821,34.11434],[-114.13628,34.26315],[-114.1404,34.3074],[-114.3409,34.44682],[-114.38484,34.45361],[-114.37798,34.52153],[-114.42879,34.58939],[-114.47411,34.71478],[-114.5565,34.77233],[-114.58672,34.83773],[-114.63203,34.86704],[-114.64165,35.01451],[-120.00023,38.99862],[-120.0016,41.99495],[-125.69978,42.00605]]]}},{"type":"Feature","properties":{"id":"US-CO"},"geometry":{"type":"Polygon","coordinates":[[[-109.05003,41.00069],[-109.04686,37.00061],[-103.00462,36.99417],[-102.04214,36.99314],[-102.04923,40.00324],[-102.05165,41.00235],[-104.0521,41.00188],[-109.05003,41.00069]]]}},{"type":"Feature","properties":{"id":"US-CT"},"geometry":{"type":"Polygon","coordinates":[[[-73.72761,41.10079],[-73.65535,41.01225],[-73.66067,41.00098],[-73.65947,40.99373],[-73.65723,40.99062],[-73.65981,40.98854],[-73.62136,40.9494],[-71.85736,41.32188],[-71.82955,41.34199],[-71.83917,41.3626],[-71.83333,41.37033],[-71.83367,41.38837],[-71.84191,41.39455],[-71.84363,41.40948],[-71.81926,41.41952],[-71.79866,41.41592],[-71.78733,41.65621],[-71.8001,42.00779],[-71.80067,42.0236],[-72.75464,42.03756],[-72.76837,42.00491],[-72.81712,41.9993],[-72.81369,42.03654],[-73.48746,42.0497],[-73.5508,41.2957],[-73.48283,41.21298],[-73.72761,41.10079]]]}},{"type":"Feature","properties":{"id":"FM-YAP"},"geometry":{"type":"Polygon","coordinates":[[[136.04605,12.45908],[136.27107,6.73747],[148.49862,1.920402],[148.42679,11.45488],[136.04605,12.45908]]]}},{"type":"Feature","properties":{"id":"US-DE"},"geometry":{"type":"Polygon","coordinates":[[[-75.78987,39.72204],[-75.78918,39.65388],[-75.69382,38.46017],[-74.98718,38.4507],[-75.01808,38.79724],[-75.38063,39.30382],[-75.56053,39.45773],[-75.55641,39.63352],[-75.46131,39.77457],[-75.40672,39.7962],[-75.41621,39.80165],[-75.43796,39.81414],[-75.46783,39.82548],[-75.49942,39.83365],[-75.53959,39.83945],[-75.57632,39.83945],[-75.59898,39.83734],[-75.62713,39.83259],[-75.65975,39.82337],[-75.68172,39.81387],[-75.71434,39.79515],[-75.72532,39.78644],[-75.74043,39.77378],[-75.76,39.75002],[-75.77476,39.7223],[-75.78987,39.72204]]]}},{"type":"Feature","properties":{"id":"US-FL"},"geometry":{"type":"Polygon","coordinates":[[[-87.63588,30.86596],[-87.52739,30.74093],[-87.40654,30.67362],[-87.39418,30.62518],[-87.44774,30.52113],[-87.42989,30.47144],[-87.3777,30.44658],[-87.50679,30.31863],[-87.44774,30.30677],[-87.51915,30.28187],[-87.51915,30.07055],[-83.18732,24.36791],[-82.02215,24.23074],[-80.16442,23.44484],[-79.36558,27.02964],[-81.34929,30.71298],[-81.56078,30.71535],[-81.95354,30.83098],[-81.97826,30.77554],[-82.01946,30.7897],[-82.05105,30.66575],[-82.00847,30.56765],[-82.04555,30.36287],[-82.17052,30.3605],[-82.20897,30.40908],[-82.2282,30.56765],[-84.86355,30.7118],[-84.92122,30.76256],[-84.9377,30.88285],[-85.00637,30.97591],[-85.00191,31.0026],[-87.5988,30.99671],[-87.59056,30.96375],[-87.63588,30.86596]]]}},{"type":"Feature","properties":{"id":"US-GA"},"geometry":{"type":"Polygon","coordinates":[[[-85.60478,34.98639],[-85.18318,32.85909],[-85.07057,32.5818],[-84.99916,32.51697],[-84.9662,32.42661],[-85.0074,32.33034],[-84.88518,32.26185],[-85.06233,32.13519],[-85.05547,32.01766],[-85.14336,31.844],[-85.07332,31.62676],[-85.04311,31.52965],[-85.10902,31.27295],[-85.09804,31.16255],[-85.00191,31.0026],[-85.00637,30.97591],[-84.9377,30.88285],[-84.92122,30.76256],[-84.86355,30.7118],[-82.2282,30.56765],[-82.20897,30.40908],[-82.17052,30.3605],[-82.04555,30.36287],[-82.00847,30.56765],[-82.05105,30.66575],[-82.01946,30.7897],[-81.97826,30.77554],[-81.95354,30.83098],[-81.56078,30.71535],[-81.34929,30.71298],[-80.49837,32.0326],[-80.92066,32.03667],[-81.00511,32.1001],[-81.05386,32.08497],[-81.11635,32.11638],[-81.11978,32.1937],[-81.15823,32.24017],[-81.11772,32.29127],[-81.20767,32.42409],[-81.19257,32.46292],[-81.27771,32.53531],[-81.28595,32.55904],[-81.3244,32.55788],[-81.38277,32.59086],[-81.41847,32.63193],[-81.39581,32.65101],[-81.42809,32.84101],[-81.45555,32.84851],[-81.50087,32.93557],[-81.49538,33.00988],[-81.62103,33.09564],[-81.64163,33.09276],[-81.70824,33.11807],[-81.76248,33.15947],[-81.7721,33.18303],[-81.75974,33.19912],[-81.77553,33.2198],[-81.78858,33.20716],[-81.85037,33.24622],[-81.85587,33.30248],[-81.93964,33.34609],[-81.91354,33.43953],[-81.92968,33.46674],[-81.9877,33.48794],[-81.99766,33.51342],[-82.05019,33.56464],[-82.10752,33.59782],[-82.1343,33.59095],[-82.19747,33.63013],[-82.20022,33.66214],[-82.3015,33.80062],[-82.39008,33.85651],[-82.56414,33.95567],[-82.59538,34.02855],[-82.64105,34.06809],[-82.64311,34.0951],[-82.71658,34.14882],[-82.74095,34.20789],[-82.75057,34.2709],[-82.78043,34.29672],[-82.79657,34.34039],[-82.83365,34.36419],[-82.87519,34.47408],[-82.90231,34.48625],[-83.00085,34.47238],[-83.03655,34.48625],[-83.16873,34.59259],[-83.17148,34.60728],[-83.22916,34.61096],[-83.34829,34.69455],[-83.35447,34.72814],[-83.32048,34.75861],[-83.32357,34.78878],[-83.2374,34.87417],[-83.11689,34.94033],[-83.12616,34.9544],[-83.09869,34.99098],[-83.10796,35.0011],[-84.32551,34.99393],[-85.60478,34.98639]]]}},{"type":"Feature","properties":{"id":"US-ID"},"geometry":{"type":"Polygon","coordinates":[[[-117.24003,44.37408],[-117.18921,44.34266],[-117.22149,44.30238],[-117.18304,44.26453],[-116.97841,44.24387],[-116.97155,44.19565],[-116.90151,44.17792],[-116.89602,44.15526],[-116.94271,44.09513],[-116.97429,44.08921],[-116.97704,44.05172],[-116.93722,44.03198],[-116.93859,43.98654],[-116.97704,43.96282],[-116.96194,43.91734],[-116.99215,43.8629],[-117.02923,43.83121],[-117.02619,42.00013],[-114.04073,42.00031],[-111.04628,42.00049],[-111.04897,44.47412],[-111.38328,44.75395],[-111.51237,44.64267],[-111.48765,44.539],[-112.31163,44.55662],[-112.39403,44.44888],[-112.71812,44.50571],[-112.83897,44.43712],[-112.82249,44.36255],[-113.00926,44.45476],[-113.13286,44.7754],[-113.25645,44.82802],[-113.3361,44.7871],[-113.5009,44.93506],[-113.44047,44.97005],[-113.45421,45.05742],[-113.75084,45.34385],[-113.82225,45.59809],[-113.98155,45.70752],[-114.33861,45.46148],[-114.54735,45.5731],[-114.55559,45.77653],[-114.39628,45.88752],[-114.49516,46.04407],[-114.31938,46.64887],[-114.61052,46.64322],[-115.33562,47.25631],[-115.75859,47.43311],[-115.63225,47.47953],[-115.75859,47.55002],[-115.68992,47.60746],[-116.04835,47.97742],[-116.04938,48.99999],[-117.03266,49.00056],[-117.04021,46.4257],[-117.03644,46.42309],[-117.03575,46.40794],[-117.04914,46.37787],[-117.06287,46.3665],[-117.06184,46.34873],[-117.04811,46.34281],[-117.03472,46.34138],[-116.92348,46.16048],[-116.98116,46.08242],[-116.94958,46.07004],[-116.91799,45.99567],[-116.77585,45.82129],[-116.54789,45.7533],[-116.46274,45.60746],[-116.67972,45.3185],[-116.75388,45.11342],[-116.84864,45.02321],[-116.85825,44.97563],[-116.83353,44.92995],[-116.9379,44.78588],[-117.05051,44.74298],[-117.15626,44.53093],[-117.22492,44.48392],[-117.24003,44.37408]]]}},{"type":"Feature","properties":{"id":"US-IL"},"geometry":{"type":"Polygon","coordinates":[[[-91.52996,40.18533],[-91.44756,39.8551],[-91.36242,39.80026],[-91.37615,39.73481],[-91.04656,39.45756],[-90.72933,39.25847],[-90.67989,39.09612],[-90.71491,39.05828],[-90.66822,38.94035],[-90.59475,38.87302],[-90.55767,38.87088],[-90.50342,38.90616],[-90.46429,38.96705],[-90.41141,38.96438],[-90.25554,38.91951],[-90.1086,38.84789],[-90.12234,38.79761],[-90.16079,38.7778],[-90.2116,38.73015],[-90.21366,38.70604],[-90.18894,38.68138],[-90.17795,38.64385],[-90.19031,38.60094],[-90.26104,38.54027],[-90.29674,38.43278],[-90.37914,38.32728],[-90.35168,38.20436],[-90.18139,38.07258],[-89.83806,37.90374],[-89.52221,37.69322],[-89.52221,37.52786],[-89.43706,37.4385],[-89.42882,37.35559],[-89.49199,37.3403],[-89.52495,37.28788],[-89.46728,37.20698],[-89.38488,37.04274],[-89.28325,36.99011],[-89.286,37.09314],[-89.19536,37.01862],[-89.09649,37.1676],[-88.95916,37.23323],[-88.62682,37.11505],[-88.55266,37.06247],[-88.46477,37.06247],[-88.42357,37.15884],[-88.51696,37.27476],[-88.47301,37.39487],[-88.40984,37.4276],[-88.35491,37.40142],[-88.30822,37.44286],[-88.06652,37.47121],[-88.15716,37.66279],[-88.03356,37.80181],[-88.09124,37.90807],[-88.02257,37.8864],[-88.03356,38.04231],[-87.93468,38.14606],[-87.97863,38.24535],[-87.74792,38.4005],[-87.75341,38.47364],[-87.66552,38.51234],[-87.61059,38.6712],[-87.52819,38.68406],[-87.49523,38.76121],[-87.55565,38.86821],[-87.5172,38.9537],[-87.57213,38.98786],[-87.57213,39.05188],[-87.65453,39.15845],[-87.59411,39.20103],[-87.59411,39.33286],[-87.52819,39.35835],[-87.51995,41.76174],[-87.20959,41.75764],[-87.02282,42.49706],[-90.64245,42.50785],[-90.65258,42.48406],[-90.43079,42.34617],[-90.40333,42.22019],[-90.19184,42.14692],[-90.14515,42.0287],[-90.18566,41.809],[-90.30651,41.75012],[-90.34325,41.65143],[-90.34187,41.58803],[-90.3956,41.57481],[-90.46581,41.52162],[-90.50667,41.51828],[-90.54581,41.5274],[-90.59267,41.51468],[-90.60572,41.49565],[-90.67303,41.46141],[-91.04107,41.43259],[-91.10149,41.28415],[-90.96416,41.01115],[-91.15093,40.66621],[-91.3377,40.60368],[-91.44307,40.37471],[-91.52996,40.18533]]]}},{"type":"Feature","properties":{"id":"US-IN"},"geometry":{"type":"Polygon","coordinates":[[[-88.09124,37.90807],[-88.03356,37.80181],[-87.95578,37.76983],[-87.90222,37.82273],[-87.94102,37.88427],[-87.89089,37.92842],[-87.83527,37.87478],[-87.67666,37.90079],[-87.67735,37.82273],[-87.60662,37.82761],[-87.58328,37.87912],[-87.62447,37.92463],[-87.59014,37.97336],[-87.5771,37.94629],[-87.51049,37.90567],[-87.41093,37.94683],[-87.23309,37.84768],[-87.16442,37.84334],[-87.13078,37.7853],[-87.09301,37.78313],[-87.05387,37.82002],[-87.04357,37.89971],[-86.81217,37.99717],[-86.74282,37.90133],[-86.65645,37.90794],[-86.63556,37.83675],[-86.58981,37.91303],[-86.52309,37.91813],[-86.52378,38.03288],[-86.32465,38.17552],[-86.27384,38.13989],[-86.27728,38.05937],[-86.20106,38.01611],[-86.10973,38.01557],[-86.04244,37.9582],[-86.01772,37.99771],[-85.94219,38.01016],[-85.91267,38.0664],[-85.90511,38.17552],[-85.83576,38.23379],[-85.84057,38.27316],[-85.79182,38.28717],[-85.74925,38.26345],[-85.65723,38.3125],[-85.6387,38.3825],[-85.41828,38.53574],[-85.443,38.72402],[-85.2734,38.73848],[-85.16628,38.68597],[-84.99188,38.77543],[-84.80991,38.78881],[-84.82845,38.83054],[-84.7852,38.88294],[-84.88064,38.90486],[-84.82433,38.97696],[-84.89643,39.05911],[-84.81884,39.10708],[-84.80645,41.69747],[-84.80614,41.761],[-87.20959,41.75764],[-87.51995,41.76174],[-87.52819,39.35835],[-87.59411,39.33286],[-87.59411,39.20103],[-87.65453,39.15845],[-87.57213,39.05188],[-87.57213,38.98786],[-87.5172,38.9537],[-87.55565,38.86821],[-87.49523,38.76121],[-87.52819,38.68406],[-87.61059,38.6712],[-87.66552,38.51234],[-87.75341,38.47364],[-87.74792,38.4005],[-87.97863,38.24535],[-87.93468,38.14606],[-88.03356,38.04231],[-88.02257,37.8864],[-88.09124,37.90807]]]}},{"type":"Feature","properties":{"id":"US-IA"},"geometry":{"type":"Polygon","coordinates":[[[-96.63614,42.74513],[-96.49057,42.57547],[-96.47134,42.49249],[-96.40268,42.49249],[-96.38071,42.46616],[-96.3862,42.4317],[-96.41641,42.40737],[-96.25162,42.02699],[-96.14999,41.9678],[-96.08682,41.53956],[-95.91928,41.45728],[-95.95224,41.34603],[-95.93507,41.32489],[-95.88701,41.31922],[-95.8719,41.30426],[-95.87602,41.28569],[-95.92203,41.26917],[-95.91173,41.23046],[-95.93095,41.20566],[-95.92203,41.18655],[-95.86092,41.18861],[-95.84512,41.18035],[-95.84581,41.16484],[-95.87534,41.16587],[-95.88426,41.14985],[-95.86298,41.08829],[-95.88495,41.05568],[-95.84238,40.67557],[-95.77211,40.58647],[-91.73074,40.61201],[-91.44307,40.37471],[-91.3377,40.60368],[-91.15093,40.66621],[-90.96416,41.01115],[-91.10149,41.28415],[-91.04107,41.43259],[-90.67303,41.46141],[-90.60572,41.49565],[-90.59267,41.51468],[-90.54581,41.5274],[-90.50667,41.51828],[-90.46581,41.52162],[-90.3956,41.57481],[-90.34187,41.58803],[-90.34325,41.65143],[-90.30651,41.75012],[-90.18566,41.809],[-90.14515,42.0287],[-90.19184,42.14692],[-90.40333,42.22019],[-90.43079,42.34617],[-90.65258,42.48406],[-90.64245,42.50785],[-90.70901,42.65026],[-91.02487,42.7068],[-91.08804,42.7774],[-91.09354,42.8761],[-91.1622,42.93646],[-91.15121,43.02085],[-91.16769,43.17528],[-91.07706,43.27334],[-91.09628,43.31932],[-91.21439,43.38123],[-91.22537,43.4989],[-96.45334,43.50083],[-96.60043,43.50089],[-96.52902,43.31333],[-96.58121,43.28334],[-96.54275,43.22733],[-96.48233,43.22133],[-96.43838,43.11516],[-96.63614,42.74513]]]}},{"type":"Feature","properties":{"id":"US-KS"},"geometry":{"type":"Polygon","coordinates":[[[-102.04923,40.00324],[-102.04214,36.99314],[-94.61795,36.9986],[-94.60772,39.11895],[-94.5909,39.13759],[-94.58953,39.15384],[-94.60601,39.16129],[-94.64686,39.1533],[-94.66128,39.15863],[-94.66094,39.17673],[-94.67845,39.18498],[-94.72239,39.16874],[-94.75157,39.17194],[-94.77011,39.18684],[-94.77664,39.20174],[-94.90298,39.30383],[-94.91122,39.35216],[-94.87895,39.37446],[-94.89405,39.39622],[-94.92839,39.38561],[-95.10348,39.53404],[-95.10279,39.57851],[-95.02177,39.67371],[-94.86247,39.74133],[-94.85903,39.75769],[-94.86865,39.773],[-94.90779,39.75875],[-94.93491,39.77221],[-94.93251,39.78804],[-94.88272,39.79542],[-94.87654,39.82391],[-95.31497,40.00166],[-102.04923,40.00324]]]}},{"type":"Feature","properties":{"id":"US-KY"},"geometry":{"type":"Polygon","coordinates":[[[-89.41668,36.50778],[-88.05987,36.49674],[-88.06536,36.67979],[-87.80993,36.63792],[-86.5932,36.65555],[-86.56848,36.63572],[-86.51355,36.65555],[-83.69109,36.58281],[-83.67455,36.60056],[-83.5239,36.66716],[-83.42125,36.66798],[-83.14074,36.75244],[-83.08032,36.84701],[-82.89904,36.88437],[-82.86059,36.98316],[-82.73425,37.04018],[-82.72326,37.12564],[-82.34698,37.2744],[-81.9707,37.53839],[-82.13961,37.56235],[-82.17669,37.63416],[-82.29754,37.67656],[-82.33462,37.77758],[-82.41702,37.84593],[-82.49392,37.93372],[-82.46646,37.98569],[-82.63263,38.13922],[-82.64361,38.1673],[-82.60791,38.17378],[-82.59692,38.21156],[-82.61203,38.24176],[-82.58319,38.25039],[-82.57495,38.32261],[-82.59692,38.342],[-82.59692,38.42382],[-82.61203,38.47329],[-82.72601,38.55603],[-82.79879,38.56355],[-82.84549,38.58502],[-82.87844,38.69121],[-82.8702,38.73943],[-82.89492,38.7555],[-82.97869,38.72765],[-83.02951,38.72872],[-83.0556,38.69336],[-83.10778,38.67621],[-83.14898,38.62258],[-83.20117,38.61614],[-83.24511,38.63116],[-83.27533,38.59897],[-83.31652,38.60112],[-83.33575,38.64618],[-83.52801,38.70408],[-83.62414,38.67621],[-83.66534,38.62902],[-83.7752,38.65047],[-83.79443,38.69765],[-83.95648,38.78762],[-84.07183,38.77263],[-84.23388,38.81331],[-84.23388,38.88176],[-84.29293,38.95334],[-84.30117,38.99445],[-84.34924,39.0382],[-84.41035,39.0446],[-84.43026,39.05739],[-84.4337,39.09577],[-84.4488,39.11922],[-84.47695,39.12188],[-84.49893,39.0995],[-84.51747,39.09151],[-84.54631,39.10163],[-84.56622,39.08671],[-84.62252,39.07392],[-84.68432,39.09844],[-84.74887,39.14851],[-84.81884,39.10708],[-84.89643,39.05911],[-84.82433,38.97696],[-84.88064,38.90486],[-84.7852,38.88294],[-84.82845,38.83054],[-84.80991,38.78881],[-84.99188,38.77543],[-85.16628,38.68597],[-85.2734,38.73848],[-85.443,38.72402],[-85.41828,38.53574],[-85.6387,38.3825],[-85.65723,38.3125],[-85.74925,38.26345],[-85.79182,38.28717],[-85.84057,38.27316],[-85.83576,38.23379],[-85.90511,38.17552],[-85.91267,38.0664],[-85.94219,38.01016],[-86.01772,37.99771],[-86.04244,37.9582],[-86.10973,38.01557],[-86.20106,38.01611],[-86.27728,38.05937],[-86.27384,38.13989],[-86.32465,38.17552],[-86.52378,38.03288],[-86.52309,37.91813],[-86.58981,37.91303],[-86.63556,37.83675],[-86.65645,37.90794],[-86.74282,37.90133],[-86.81217,37.99717],[-87.04357,37.89971],[-87.05387,37.82002],[-87.09301,37.78313],[-87.13078,37.7853],[-87.16442,37.84334],[-87.23309,37.84768],[-87.41093,37.94683],[-87.51049,37.90567],[-87.5771,37.94629],[-87.59014,37.97336],[-87.62447,37.92463],[-87.58328,37.87912],[-87.60662,37.82761],[-87.67735,37.82273],[-87.67666,37.90079],[-87.83527,37.87478],[-87.89089,37.92842],[-87.94102,37.88427],[-87.90222,37.82273],[-87.95578,37.76983],[-88.03356,37.80181],[-88.15716,37.66279],[-88.06652,37.47121],[-88.30822,37.44286],[-88.35491,37.40142],[-88.40984,37.4276],[-88.47301,37.39487],[-88.51696,37.27476],[-88.42357,37.15884],[-88.46477,37.06247],[-88.55266,37.06247],[-88.62682,37.11505],[-88.95916,37.23323],[-89.09649,37.1676],[-89.19536,37.01862],[-89.19146,36.9678],[-89.10907,36.98097],[-89.22168,36.56737],[-89.36175,36.63352],[-89.41668,36.50778]]]}},{"type":"Feature","properties":{"id":"US-LA"},"geometry":{"type":"Polygon","coordinates":[[[-94.04467,32.00379],[-93.92932,31.91524],[-93.52008,31.03684],[-93.76452,30.33771],[-93.70959,30.28791],[-93.71508,30.05521],[-93.92382,29.81005],[-93.77551,29.43998],[-88.93054,28.25639],[-88.37952,30.00457],[-89.58972,30.1835],[-89.69134,30.46089],[-89.77374,30.5177],[-89.85339,30.67373],[-89.73529,31.00389],[-91.63317,31.00153],[-91.44641,31.54147],[-90.90533,32.31769],[-91.16798,33.00432],[-94.04366,33.01929],[-94.04467,32.00379]]]}},{"type":"Feature","properties":{"id":"US-ME"},"geometry":{"type":"Polygon","coordinates":[[[-71.08364,45.30623],[-70.97613,43.56977],[-70.95278,43.55783],[-70.98849,43.3884],[-70.96377,43.33749],[-70.93493,43.33549],[-70.89648,43.29052],[-70.81271,43.23052],[-70.83331,43.13238],[-70.73786,43.07272],[-70.70422,43.07623],[-70.70216,43.04463],[-70.04999,42.81005],[-67.16117,44.20069],[-66.93432,44.82597],[-66.96824,44.83078],[-66.98249,44.87071],[-66.96824,44.90965],[-67.0216,44.95333],[-67.11316,45.11176],[-67.15965,45.16179],[-67.19603,45.16771],[-67.20349,45.1722],[-67.22751,45.16344],[-67.27039,45.1934],[-67.29748,45.18173],[-67.29754,45.14865],[-67.34927,45.122],[-67.48201,45.27351],[-67.42394,45.37969],[-67.50578,45.48971],[-67.42144,45.50584],[-67.43815,45.59162],[-67.6049,45.60725],[-67.80705,45.69528],[-67.80653,45.80022],[-67.75654,45.82324],[-67.80961,45.87531],[-67.75196,45.91814],[-67.78111,45.9392],[-67.78578,47.06473],[-67.87993,47.10377],[-67.94843,47.1925],[-68.23244,47.35712],[-68.37458,47.35851],[-68.38332,47.28723],[-68.57914,47.28431],[-68.60575,47.24659],[-68.70125,47.24399],[-68.89222,47.1807],[-69.05039,47.2456],[-69.05073,47.30076],[-69.05148,47.42012],[-69.22119,47.46461],[-69.99966,46.69543],[-70.05812,46.41768],[-70.18547,46.35357],[-70.29078,46.18832],[-70.23855,46.1453],[-70.31025,45.96424],[-70.24694,45.95138],[-70.25976,45.89675],[-70.41523,45.79497],[-70.38934,45.73215],[-70.54019,45.67291],[-70.68516,45.56964],[-70.72651,45.49771],[-70.62518,45.42286],[-70.65383,45.37592],[-70.78372,45.43269],[-70.82638,45.39828],[-70.80236,45.37444],[-70.84816,45.22698],[-70.89864,45.2398],[-70.91169,45.29849],[-70.95193,45.33895],[-71.0107,45.34819],[-71.01866,45.31573],[-71.08364,45.30623]]]}},{"type":"Feature","properties":{"id":"US-MD"},"geometry":{"type":"Polygon","coordinates":[[[-79.48702,39.20187],[-79.42831,39.22448],[-79.37853,39.27261],[-79.35999,39.27526],[-79.33801,39.29652],[-79.31158,39.30502],[-79.29235,39.29865],[-79.25321,39.35575],[-79.21579,39.36424],[-79.19657,39.38733],[-79.06885,39.47643],[-79.0455,39.4804],[-78.96688,39.43958],[-78.83951,39.5678],[-78.81925,39.56065],[-78.79659,39.63472],[-78.76501,39.64715],[-78.77702,39.62177],[-78.73789,39.62388],[-78.73892,39.60775],[-78.77771,39.60457],[-78.76364,39.58262],[-78.73789,39.58632],[-78.72621,39.56356],[-78.66167,39.53576],[-78.46872,39.5167],[-78.40658,39.617],[-78.31732,39.59479],[-78.18411,39.69524],[-78.10789,39.68203],[-78.05021,39.64768],[-78.01107,39.60113],[-77.87512,39.617],[-77.83392,39.60431],[-77.83392,39.56621],[-77.88885,39.55774],[-77.84216,39.49842],[-77.78036,39.49312],[-77.79821,39.43587],[-77.75289,39.42632],[-77.73135,39.32349],[-77.68054,39.32667],[-77.61462,39.3033],[-77.56655,39.30861],[-77.54596,39.27247],[-77.49102,39.25227],[-77.45532,39.22462],[-77.47729,39.18844],[-77.51162,39.18311],[-77.5281,39.14691],[-77.51849,39.12135],[-77.48416,39.11282],[-77.46081,39.07872],[-77.30975,39.05846],[-77.23971,39.02006],[-77.25619,39.00192],[-77.22186,38.97417],[-77.1477,38.9699],[-77.11962,38.93441],[-77.04117,38.99552],[-76.90939,38.89301],[-77.03906,38.79153],[-77.04196,38.70568],[-77.08041,38.70568],[-77.12023,38.68103],[-77.12161,38.63385],[-77.21225,38.6038],[-77.28915,38.50178],[-77.28778,38.38454],[-77.20813,38.35223],[-77.03784,38.42973],[-76.92248,38.23475],[-76.61074,38.15704],[-76.23034,37.8985],[-75.88153,37.90934],[-75.65768,37.94509],[-75.62472,37.99597],[-75.16879,38.02735],[-74.98718,38.4507],[-75.69382,38.46017],[-75.78918,39.65388],[-75.78987,39.72204],[-79.47663,39.72086],[-79.48702,39.20187]]]}},{"type":"Feature","properties":{"id":"IN-DD"},"geometry":{"type":"Polygon","coordinates":[[[70.8467,20.44438],[72.47526,20.38318],[72.89291,20.36748],[72.90801,20.43087],[72.83901,20.48555],[71.00154,20.74648],[70.87331,20.73203],[70.8467,20.44438]]]}},{"type":"Feature","properties":{"id":"US-MA"},"geometry":{"type":"Polygon","coordinates":[[[-73.50942,42.0867],[-73.48746,42.0497],[-72.81369,42.03654],[-72.81712,41.9993],[-72.76837,42.00491],[-72.75464,42.03756],[-71.80067,42.0236],[-71.8001,42.00779],[-71.38117,42.0194],[-71.3822,41.89277],[-71.33894,41.89916],[-71.341,41.79814],[-71.32898,41.7815],[-71.26135,41.75231],[-71.19577,41.67465],[-71.13432,41.65952],[-71.14153,41.60717],[-71.13123,41.591],[-71.10101,41.43444],[-69.97282,40.56828],[-69.42513,41.52748],[-70.04999,42.81005],[-70.81606,42.8739],[-70.84627,42.86182],[-70.88541,42.88446],[-70.92592,42.88698],[-70.96781,42.86887],[-71.04196,42.85981],[-71.06531,42.80744],[-71.13878,42.82305],[-71.18616,42.79484],[-71.18273,42.7399],[-71.25139,42.74343],[-71.29465,42.69651],[-72.45969,42.72515],[-73.26498,42.74494],[-73.50942,42.0867]]]}},{"type":"Feature","properties":{"id":"CN-QH"},"geometry":{"type":"Polygon","coordinates":[[[89.40811,36.01522],[89.42802,35.91602],[89.80224,35.859],[89.68482,35.42207],[89.45274,35.22991],[89.57908,34.90113],[89.81941,34.90395],[89.73358,34.65862],[89.87708,34.2277],[89.63882,34.04583],[89.93751,33.80083],[89.99656,33.55913],[90.24993,33.42857],[90.38177,33.2605],[90.51567,33.2651],[90.70175,33.13985],[91.51405,33.11339],[91.97891,32.86113],[92.20275,32.88766],[92.22335,32.72144],[93.02398,32.73646],[93.48953,32.4947],[93.72161,32.57343],[94.14733,32.43561],[94.61254,32.67001],[94.91981,32.413],[95.22571,32.3872],[95.51032,31.74685],[95.61881,31.77896],[95.7891,31.75327],[96.14822,31.69078],[96.20109,31.53816],[96.25053,31.55396],[96.21963,31.76145],[96.13929,31.82623],[96.24435,31.9341],[96.33636,31.95682],[96.37069,31.85539],[96.5657,31.7194],[96.8431,31.7083],[96.725,32.02612],[96.9564,31.99351],[97.00309,32.06628],[97.16583,32.03049],[97.22557,32.10962],[97.30865,32.07501],[97.37663,32.5306],[97.66399,32.47704],[97.72544,32.52886],[97.66296,32.55781],[97.54417,32.62332],[97.5325,32.64241],[97.48168,32.65556],[97.4271,32.7122],[97.37766,32.80718],[97.37834,32.86978],[97.33989,32.9038],[97.4319,32.9813],[97.53078,32.99167],[97.49061,33.11512],[97.4858,33.166],[97.59841,33.25964],[97.62348,33.33769],[97.7584,33.40536],[97.40409,33.63062],[97.39517,33.89492],[97.65266,33.93538],[97.66158,34.12431],[98.41689,34.09816],[98.42651,33.85217],[98.73962,33.43717],[98.85497,33.14675],[99.36035,32.90092],[99.73388,32.72375],[99.8822,33.04781],[100.49554,32.65671],[100.54687,32.5714],[100.66463,32.52481],[100.71819,32.67492],[100.94032,32.60756],[101.1185,32.63619],[101.22665,32.76071],[101.1621,33.22835],[101.64001,33.09844],[101.76223,33.46925],[101.61254,33.51506],[101.58782,33.67349],[101.17034,33.65463],[101.19232,33.79455],[100.7611,34.17545],[100.94581,34.37404],[101.76841,34.06233],[102.25318,34.36441],[102.15499,34.51221],[101.72996,34.70436],[102.40218,35.18503],[102.3191,35.34369],[102.50381,35.58808],[102.75169,35.49533],[102.80456,35.5758],[102.70568,35.86011],[102.9467,35.83507],[102.97004,36.03299],[103.02394,36.25562],[102.92026,36.30073],[102.88936,36.33338],[102.83168,36.33531],[102.82859,36.37098],[102.71598,36.60009],[102.59651,36.71081],[102.72319,36.76886],[102.47325,36.97238],[102.64594,37.10447],[101.9878,37.73108],[101.3578,37.7916],[100.97019,38.01293],[100.93105,38.16749],[100.09815,38.45735],[100.17677,38.2112],[98.74923,39.08743],[98.28643,39.03358],[98.24867,38.88515],[98.08937,38.78513],[97.3368,39.16733],[96.97769,39.20884],[96.9358,38.9108],[97.05459,38.6284],[96.66595,38.48665],[96.65702,38.22901],[96.29859,38.15669],[95.65658,38.36857],[95.24459,38.30502],[94.99053,38.43638],[94.5346,38.35781],[94.35607,38.76265],[93.42086,38.9092],[93.11599,39.17372],[92.40874,39.03625],[90.44975,38.49928],[90.09406,38.49014],[90.18127,38.39764],[90.14076,38.33734],[90.31585,38.22955],[90.52322,38.31903],[90.5136,37.74465],[91.06567,37.48575],[91.31149,37.02887],[90.84869,36.93342],[90.7196,36.59347],[91.02035,36.54053],[91.10961,36.10792],[90.86379,36.02577],[90.01922,36.2631],[89.95056,36.08018],[89.691,36.0935],[89.40811,36.01522]]]}},{"type":"Feature","properties":{"id":"US-MI"},"geometry":{"type":"Polygon","coordinates":[[[-90.42047,46.56636],[-90.39026,46.53331],[-90.33396,46.5522],[-90.2186,46.50212],[-90.11148,46.3355],[-89.09387,46.14078],[-88.8096,46.02457],[-88.64618,45.98832],[-88.51846,46.02362],[-88.30835,45.95587],[-88.10098,45.9234],[-88.07901,45.87371],[-88.13669,45.82205],[-88.0845,45.78088],[-88.01172,45.79333],[-87.86752,45.75023],[-87.77826,45.67639],[-87.83182,45.65912],[-87.77414,45.6063],[-87.79474,45.56209],[-87.84143,45.57074],[-87.80435,45.53805],[-87.8071,45.47067],[-87.85791,45.43888],[-87.87027,45.35499],[-87.75903,45.35209],[-87.69724,45.38972],[-87.64368,45.34534],[-87.74118,45.19554],[-87.65741,45.10643],[-87.44318,45.07735],[-87.41022,45.20134],[-87.09574,45.44563],[-86.7428,45.44467],[-86.25116,45.23713],[-87.02282,42.49706],[-87.20959,41.75764],[-84.80614,41.761],[-84.80645,41.69747],[-83.42355,41.73233],[-83.11184,41.95671],[-83.14962,42.04089],[-83.12724,42.2376],[-83.09837,42.28877],[-83.07837,42.30978],[-83.02253,42.33045],[-82.82964,42.37355],[-82.64242,42.55594],[-82.58873,42.54984],[-82.57583,42.5718],[-82.51858,42.611],[-82.51063,42.66025],[-82.46613,42.76615],[-82.4826,42.8068],[-82.45331,42.93139],[-82.4253,42.95423],[-82.4146,42.97626],[-82.42469,42.992],[-82.48419,45.30225],[-83.59589,45.82131],[-83.43746,45.99749],[-83.57017,46.105],[-83.83329,46.12169],[-83.90453,46.05922],[-83.95399,46.05634],[-84.1096,46.23987],[-84.09756,46.25512],[-84.11615,46.2681],[-84.11254,46.32329],[-84.13451,46.39218],[-84.11196,46.50248],[-84.12885,46.53068],[-84.17723,46.52753],[-84.1945,46.54061],[-84.2264,46.53337],[-84.26351,46.49508],[-84.29893,46.49127],[-84.34174,46.50683],[-84.42101,46.49853],[-84.4481,46.48972],[-84.47607,46.45225],[-84.55635,46.45974],[-84.85871,46.88881],[-88.37033,48.30586],[-89.48837,48.01412],[-90.42047,46.56636]]]}},{"type":"Feature","properties":{"id":"US-MN"},"geometry":{"type":"Polygon","coordinates":[[[-97.24024,48.99952],[-97.09604,48.68275],[-97.15372,48.59745],[-97.14685,48.17245],[-97.05484,47.9485],[-97.01639,47.91905],[-97.02738,47.89144],[-96.85983,47.60148],[-96.83511,47.0083],[-96.76233,46.92114],[-96.79666,46.81035],[-96.7898,46.63902],[-96.72525,46.45011],[-96.60303,46.32981],[-96.56732,45.93486],[-96.58106,45.82588],[-96.65109,45.74735],[-96.86258,45.62263],[-96.68405,45.41382],[-96.49591,45.36367],[-96.45334,45.29706],[-96.45334,43.50083],[-91.22537,43.4989],[-91.24582,43.77649],[-91.42572,43.99227],[-91.65094,44.06533],[-91.9874,44.3832],[-92.82511,44.74132],[-92.77017,44.82953],[-92.76056,45.28643],[-92.65207,45.40804],[-92.76056,45.56786],[-92.88416,45.56978],[-92.8718,45.72051],[-92.78803,45.76364],[-92.72348,45.889],[-92.29639,46.07794],[-92.29227,46.66258],[-92.2085,46.65221],[-92.203,46.69744],[-92.1055,46.75016],[-92.02448,46.70498],[-89.48837,48.01412],[-89.57972,48.00023],[-89.77248,48.02607],[-89.89974,47.98109],[-90.07418,48.11043],[-90.56312,48.09488],[-90.56444,48.12184],[-90.75045,48.09143],[-90.87588,48.2484],[-91.08016,48.18096],[-91.25025,48.08522],[-91.43248,48.04912],[-91.45829,48.07454],[-91.58025,48.04339],[-91.55649,48.10611],[-91.70451,48.11805],[-91.71231,48.19875],[-91.86125,48.21278],[-91.98929,48.25409],[-92.05339,48.35958],[-92.14732,48.36578],[-92.202,48.35252],[-92.26662,48.35651],[-92.30939,48.31251],[-92.27167,48.25046],[-92.37185,48.22259],[-92.48147,48.36609],[-92.45588,48.40624],[-92.50712,48.44921],[-92.65606,48.43471],[-92.71323,48.46081],[-92.69927,48.49573],[-92.62747,48.50278],[-92.6342,48.54133],[-92.7287,48.54005],[-92.94973,48.60866],[-93.25391,48.64266],[-93.33946,48.62787],[-93.3712,48.60599],[-93.39758,48.60364],[-93.40693,48.60948],[-93.44472,48.59147],[-93.47022,48.54357],[-93.66382,48.51845],[-93.79267,48.51631],[-93.80939,48.52439],[-93.80676,48.58232],[-93.83288,48.62745],[-93.85769,48.63284],[-94.23215,48.65202],[-94.25104,48.65729],[-94.25172,48.68404],[-94.27153,48.70232],[-94.4174,48.71049],[-94.44258,48.69223],[-94.53826,48.70216],[-94.54885,48.71543],[-94.58903,48.71803],[-94.69335,48.77883],[-94.69669,48.80918],[-94.70486,48.82365],[-94.70087,48.8339],[-94.687,48.84077],[-94.75017,49.09931],[-94.77355,49.11998],[-94.82487,49.29483],[-94.8159,49.32299],[-94.85381,49.32492],[-94.95681,49.37035],[-94.99532,49.36579],[-95.01419,49.35647],[-95.05825,49.35311],[-95.12903,49.37056],[-95.15357,49.384],[-95.15355,48.9996],[-97.24024,48.99952]]]}},{"type":"Feature","properties":{"id":"IN-AR"},"geometry":{"type":"Polygon","coordinates":[[[91.55819,27.6144],[91.65007,27.48287],[92.01132,27.47352],[92.12019,27.27829],[92.04702,27.26861],[92.03457,27.07334],[92.11863,26.893],[92.6441,26.98495],[92.64289,27.03894],[92.90313,27.00591],[93.03291,26.919],[93.38035,26.96308],[93.50257,26.93859],[93.68385,26.97838],[93.83354,27.07746],[93.80882,27.15142],[94.15489,27.4839],[94.26269,27.52471],[94.216,27.62331],[94.46937,27.5728],[94.86968,27.74036],[95.316,27.87125],[95.51994,27.88157],[95.63117,27.96105],[95.97518,27.9665],[95.76335,27.73519],[95.88523,27.52836],[95.86189,27.43333],[95.99647,27.37481],[95.89279,27.27294],[95.49041,27.2473],[95.46295,27.12209],[95.19515,27.02915],[95.23513,26.68499],[95.30339,26.65372],[95.437,26.7083],[95.81603,27.01335],[95.93002,27.04149],[96.04949,27.19428],[96.15591,27.24572],[96.40779,27.29818],[96.55761,27.29928],[96.73888,27.36638],[96.88445,27.25046],[96.85287,27.2065],[96.89132,27.17474],[97.14675,27.09041],[97.17422,27.14052],[96.91431,27.45752],[96.90112,27.62149],[97.29919,27.92233],[97.35824,27.87256],[97.38845,28.01329],[97.35412,28.06663],[97.31292,28.06784],[97.34547,28.21385],[97.1289,28.3619],[96.98882,28.32564],[96.88445,28.39452],[96.85561,28.4875],[96.6455,28.61657],[96.48895,28.42955],[96.40929,28.51526],[96.61391,28.72742],[96.3626,29.10607],[96.20467,29.02325],[96.18682,29.11087],[96.31316,29.18643],[96.05361,29.38167],[95.84899,29.31464],[95.75149,29.32063],[95.72086,29.20797],[95.50842,29.13487],[95.41091,29.13007],[95.3038,29.13847],[95.26122,29.07727],[95.2214,29.10727],[95.11291,29.09527],[95.0978,29.14446],[94.81353,29.17804],[94.69318,29.31739],[94.2752,29.11687],[94.35897,29.01965],[93.72797,28.68821],[93.44621,28.67189],[93.18069,28.50319],[93.14635,28.37035],[92.93075,28.25671],[92.67486,28.15018],[92.65472,28.07632],[92.73025,28.05814],[92.7275,27.98662],[92.42538,27.80092],[92.32101,27.79363],[92.27432,27.89077],[91.87057,27.7195],[91.84722,27.76325],[91.6469,27.76358],[91.55819,27.6144]]]}},{"type":"Feature","properties":{"id":"US-WI"},"geometry":{"type":"Polygon","coordinates":[[[-92.88416,45.56978],[-92.76056,45.56786],[-92.65207,45.40804],[-92.76056,45.28643],[-92.77017,44.82953],[-92.82511,44.74132],[-91.9874,44.3832],[-91.65094,44.06533],[-91.42572,43.99227],[-91.24582,43.77649],[-91.22537,43.4989],[-91.21439,43.38123],[-91.09628,43.31932],[-91.07706,43.27334],[-91.16769,43.17528],[-91.15121,43.02085],[-91.1622,42.93646],[-91.09354,42.8761],[-91.08804,42.7774],[-91.02487,42.7068],[-90.70901,42.65026],[-90.64245,42.50785],[-87.02282,42.49706],[-86.25116,45.23713],[-86.7428,45.44467],[-87.09574,45.44563],[-87.41022,45.20134],[-87.44318,45.07735],[-87.65741,45.10643],[-87.74118,45.19554],[-87.64368,45.34534],[-87.69724,45.38972],[-87.75903,45.35209],[-87.87027,45.35499],[-87.85791,45.43888],[-87.8071,45.47067],[-87.80435,45.53805],[-87.84143,45.57074],[-87.79474,45.56209],[-87.77414,45.6063],[-87.83182,45.65912],[-87.77826,45.67639],[-87.86752,45.75023],[-88.01172,45.79333],[-88.0845,45.78088],[-88.13669,45.82205],[-88.07901,45.87371],[-88.10098,45.9234],[-88.30835,45.95587],[-88.51846,46.02362],[-88.64618,45.98832],[-88.8096,46.02457],[-89.09387,46.14078],[-90.11148,46.3355],[-90.2186,46.50212],[-90.33396,46.5522],[-90.39026,46.53331],[-90.42047,46.56636],[-89.48837,48.01412],[-92.02448,46.70498],[-92.1055,46.75016],[-92.203,46.69744],[-92.2085,46.65221],[-92.29227,46.66258],[-92.29639,46.07794],[-92.72348,45.889],[-92.78803,45.76364],[-92.8718,45.72051],[-92.88416,45.56978]]]}},{"type":"Feature","properties":{"id":"US-WY"},"geometry":{"type":"Polygon","coordinates":[[[-111.05503,44.99947],[-111.04897,44.47412],[-111.04628,42.00049],[-111.04675,40.99766],[-109.05003,41.00069],[-104.0521,41.00188],[-104.05548,43.00258],[-104.05897,44.99972],[-111.05503,44.99947]]]}},{"type":"Feature","properties":{"id":"US-MS"},"geometry":{"type":"Polygon","coordinates":[[[-91.63317,31.00153],[-89.73529,31.00389],[-89.85339,30.67373],[-89.77374,30.5177],[-89.69134,30.46089],[-89.58972,30.1835],[-88.37952,30.00457],[-88.47496,31.89065],[-88.09868,34.89407],[-88.14949,34.9211],[-88.20029,34.99532],[-90.31242,34.99989],[-90.24925,34.93349],[-90.56236,34.72946],[-90.58846,34.49776],[-91.08696,33.96299],[-91.14052,33.29866],[-91.16798,33.00432],[-90.90533,32.31769],[-91.44641,31.54147],[-91.63317,31.00153]]]}},{"type":"Feature","properties":{"id":"US-MO"},"geometry":{"type":"Polygon","coordinates":[[[-95.77211,40.58647],[-95.65168,40.39775],[-95.31497,40.00166],[-94.87654,39.82391],[-94.88272,39.79542],[-94.93251,39.78804],[-94.93491,39.77221],[-94.90779,39.75875],[-94.86865,39.773],[-94.85903,39.75769],[-94.86247,39.74133],[-95.02177,39.67371],[-95.10279,39.57851],[-95.10348,39.53404],[-94.92839,39.38561],[-94.89405,39.39622],[-94.87895,39.37446],[-94.91122,39.35216],[-94.90298,39.30383],[-94.77664,39.20174],[-94.77011,39.18684],[-94.75157,39.17194],[-94.72239,39.16874],[-94.67845,39.18498],[-94.66094,39.17673],[-94.66128,39.15863],[-94.64686,39.1533],[-94.60601,39.16129],[-94.58953,39.15384],[-94.5909,39.13759],[-94.60772,39.11895],[-94.61795,36.9986],[-94.61906,36.49995],[-90.1545,36.49885],[-90.13939,36.41711],[-90.07347,36.39833],[-90.07759,36.27442],[-90.11467,36.26446],[-90.37834,35.99826],[-89.73701,36.00048],[-89.61332,36.10897],[-89.58997,36.1478],[-89.6916,36.24089],[-89.57212,36.24754],[-89.5474,36.43117],[-89.52268,36.46762],[-89.57212,36.56144],[-89.52268,36.5846],[-89.47187,36.55813],[-89.48972,36.46541],[-89.45127,36.46431],[-89.41668,36.50778],[-89.36175,36.63352],[-89.22168,36.56737],[-89.10907,36.98097],[-89.19146,36.9678],[-89.19536,37.01862],[-89.286,37.09314],[-89.28325,36.99011],[-89.38488,37.04274],[-89.46728,37.20698],[-89.52495,37.28788],[-89.49199,37.3403],[-89.42882,37.35559],[-89.43706,37.4385],[-89.52221,37.52786],[-89.52221,37.69322],[-89.83806,37.90374],[-90.18139,38.07258],[-90.35168,38.20436],[-90.37914,38.32728],[-90.29674,38.43278],[-90.26104,38.54027],[-90.19031,38.60094],[-90.17795,38.64385],[-90.18894,38.68138],[-90.21366,38.70604],[-90.2116,38.73015],[-90.16079,38.7778],[-90.12234,38.79761],[-90.1086,38.84789],[-90.25554,38.91951],[-90.41141,38.96438],[-90.46429,38.96705],[-90.50342,38.90616],[-90.55767,38.87088],[-90.59475,38.87302],[-90.66822,38.94035],[-90.71491,39.05828],[-90.67989,39.09612],[-90.72933,39.25847],[-91.04656,39.45756],[-91.37615,39.73481],[-91.36242,39.80026],[-91.44756,39.8551],[-91.52996,40.18533],[-91.44307,40.37471],[-91.73074,40.61201],[-95.77211,40.58647]]]}},{"type":"Feature","properties":{"id":"US-MT"},"geometry":{"type":"Polygon","coordinates":[[[-116.04938,48.99999],[-116.04835,47.97742],[-115.68992,47.60746],[-115.75859,47.55002],[-115.63225,47.47953],[-115.75859,47.43311],[-115.33562,47.25631],[-114.61052,46.64322],[-114.31938,46.64887],[-114.49516,46.04407],[-114.39628,45.88752],[-114.55559,45.77653],[-114.54735,45.5731],[-114.33861,45.46148],[-113.98155,45.70752],[-113.82225,45.59809],[-113.75084,45.34385],[-113.45421,45.05742],[-113.44047,44.97005],[-113.5009,44.93506],[-113.3361,44.7871],[-113.25645,44.82802],[-113.13286,44.7754],[-113.00926,44.45476],[-112.82249,44.36255],[-112.83897,44.43712],[-112.71812,44.50571],[-112.39403,44.44888],[-112.31163,44.55662],[-111.48765,44.539],[-111.51237,44.64267],[-111.38328,44.75395],[-111.04897,44.47412],[-111.05503,44.99947],[-104.05897,44.99972],[-104.05691,45.94728],[-104.05004,48.99925],[-110.0051,48.99901],[-114.0683,48.99885],[-116.04938,48.99999]]]}},{"type":"Feature","properties":{"id":"US-SD"},"geometry":{"type":"Polygon","coordinates":[[[-104.05897,44.99972],[-104.05548,43.00258],[-98.50401,43.00058],[-98.44908,42.93275],[-97.99383,42.76612],[-97.95057,42.77065],[-97.84826,42.86685],[-97.44314,42.84773],[-97.40125,42.86736],[-97.25019,42.85729],[-97.11836,42.76813],[-97.00575,42.76561],[-96.94875,42.71922],[-96.88627,42.73536],[-96.69264,42.64805],[-96.71461,42.60561],[-96.60475,42.50344],[-96.55187,42.51963],[-96.47134,42.49249],[-96.49057,42.57547],[-96.63614,42.74513],[-96.43838,43.11516],[-96.48233,43.22133],[-96.54275,43.22733],[-96.58121,43.28334],[-96.52902,43.31333],[-96.60043,43.50089],[-96.45334,43.50083],[-96.45334,45.29706],[-96.49591,45.36367],[-96.68405,45.41382],[-96.86258,45.62263],[-96.65109,45.74735],[-96.58106,45.82588],[-96.56732,45.93486],[-104.05691,45.94728],[-104.05897,44.99972]]]}},{"type":"Feature","properties":{"id":"US-NE"},"geometry":{"type":"Polygon","coordinates":[[[-104.05548,43.00258],[-104.0521,41.00188],[-102.05165,41.00235],[-102.04923,40.00324],[-95.31497,40.00166],[-95.65168,40.39775],[-95.77211,40.58647],[-95.84238,40.67557],[-95.88495,41.05568],[-95.86298,41.08829],[-95.88426,41.14985],[-95.87534,41.16587],[-95.84581,41.16484],[-95.84512,41.18035],[-95.86092,41.18861],[-95.92203,41.18655],[-95.93095,41.20566],[-95.91173,41.23046],[-95.92203,41.26917],[-95.87602,41.28569],[-95.8719,41.30426],[-95.88701,41.31922],[-95.93507,41.32489],[-95.95224,41.34603],[-95.91928,41.45728],[-96.08682,41.53956],[-96.14999,41.9678],[-96.25162,42.02699],[-96.41641,42.40737],[-96.3862,42.4317],[-96.38071,42.46616],[-96.40268,42.49249],[-96.47134,42.49249],[-96.55187,42.51963],[-96.60475,42.50344],[-96.71461,42.60561],[-96.69264,42.64805],[-96.88627,42.73536],[-96.94875,42.71922],[-97.00575,42.76561],[-97.11836,42.76813],[-97.25019,42.85729],[-97.40125,42.86736],[-97.44314,42.84773],[-97.84826,42.86685],[-97.95057,42.77065],[-97.99383,42.76612],[-98.44908,42.93275],[-98.50401,43.00058],[-104.05548,43.00258]]]}},{"type":"Feature","properties":{"id":"US-NV"},"geometry":{"type":"Polygon","coordinates":[[[-120.0016,41.99495],[-120.00023,38.99862],[-114.64165,35.01451],[-114.60732,35.07635],[-114.64577,35.10444],[-114.62791,35.12129],[-114.5826,35.12803],[-114.57024,35.15498],[-114.59633,35.33331],[-114.68147,35.49783],[-114.65263,35.60956],[-114.68971,35.65197],[-114.70482,35.85592],[-114.66774,35.87039],[-114.74602,35.98271],[-114.74052,36.0127],[-114.72404,36.03159],[-114.75014,36.09486],[-114.63066,36.14367],[-114.61281,36.13036],[-114.57298,36.15254],[-114.37523,36.147],[-114.30931,36.06379],[-114.2379,36.01715],[-114.14864,36.02936],[-114.12254,36.11372],[-114.04564,36.1991],[-114.05113,37.00171],[-114.04073,42.00031],[-117.02619,42.00013],[-120.0016,41.99495]]]}},{"type":"Feature","properties":{"id":"US-UT"},"geometry":{"type":"Polygon","coordinates":[[[-114.05113,37.00171],[-109.04686,37.00061],[-109.05003,41.00069],[-111.04675,40.99766],[-111.04628,42.00049],[-114.04073,42.00031],[-114.05113,37.00171]]]}},{"type":"Feature","properties":{"id":"US-ND"},"geometry":{"type":"Polygon","coordinates":[[[-104.05691,45.94728],[-96.56732,45.93486],[-96.60303,46.32981],[-96.72525,46.45011],[-96.7898,46.63902],[-96.79666,46.81035],[-96.76233,46.92114],[-96.83511,47.0083],[-96.85983,47.60148],[-97.02738,47.89144],[-97.01639,47.91905],[-97.05484,47.9485],[-97.14685,48.17245],[-97.15372,48.59745],[-97.09604,48.68275],[-97.24024,48.99952],[-101.36198,48.99935],[-104.05004,48.99925],[-104.05691,45.94728]]]}},{"type":"Feature","properties":{"id":"US-NH"},"geometry":{"type":"Polygon","coordinates":[[[-72.55705,42.85427],[-72.54331,42.81147],[-72.51173,42.78174],[-72.51722,42.7636],[-72.49456,42.77368],[-72.45969,42.72515],[-71.29465,42.69651],[-71.25139,42.74343],[-71.18273,42.7399],[-71.18616,42.79484],[-71.13878,42.82305],[-71.06531,42.80744],[-71.04196,42.85981],[-70.96781,42.86887],[-70.92592,42.88698],[-70.88541,42.88446],[-70.84627,42.86182],[-70.81606,42.8739],[-70.04999,42.81005],[-70.70216,43.04463],[-70.70422,43.07623],[-70.73786,43.07272],[-70.83331,43.13238],[-70.81271,43.23052],[-70.89648,43.29052],[-70.93493,43.33549],[-70.96377,43.33749],[-70.98849,43.3884],[-70.95278,43.55783],[-70.97613,43.56977],[-71.08364,45.30623],[-71.14568,45.24128],[-71.19723,45.25438],[-71.22338,45.25184],[-71.29371,45.29996],[-71.37133,45.24624],[-71.44252,45.2361],[-71.40364,45.21382],[-71.42778,45.12624],[-71.48735,45.07784],[-71.50067,45.01357],[-71.54287,44.98758],[-71.49618,44.90788],[-71.55386,44.86214],[-71.57583,44.79592],[-71.63351,44.74912],[-71.54287,44.58601],[-71.56347,44.56351],[-71.59231,44.56351],[-71.59368,44.49302],[-71.639,44.47343],[-71.7008,44.41558],[-71.79693,44.39891],[-71.81753,44.35866],[-71.86834,44.33706],[-71.90679,44.34688],[-71.9782,44.33411],[-72.0249,44.31741],[-72.06335,44.27514],[-72.04,44.08408],[-72.11141,43.9977],[-72.12309,43.9201],[-72.16291,43.8919],[-72.20617,43.77302],[-72.30093,43.70754],[-72.32702,43.63553],[-72.40049,43.51265],[-72.38127,43.49472],[-72.41491,43.36557],[-72.39362,43.35659],[-72.45611,43.14654],[-72.441,43.13702],[-72.43688,43.08388],[-72.46092,43.05479],[-72.46504,42.98099],[-72.53233,42.95134],[-72.52546,42.93626],[-72.53301,42.89553],[-72.55361,42.88497],[-72.55705,42.85427]]]}},{"type":"Feature","properties":{"id":"US-NJ"},"geometry":{"type":"Polygon","coordinates":[[[-75.56053,39.45773],[-75.38063,39.30382],[-75.01808,38.79724],[-74.98718,38.4507],[-73.81773,39.66512],[-73.9166,40.514],[-74.23109,40.47954],[-74.2613,40.49625],[-74.25615,40.51427],[-74.24722,40.52236],[-74.25134,40.54532],[-74.23418,40.55914],[-74.21839,40.55836],[-74.19985,40.59956],[-74.20397,40.63162],[-74.18577,40.64647],[-74.12775,40.6436],[-74.05668,40.65402],[-74.02612,40.69959],[-74.01445,40.7589],[-73.95197,40.8509],[-73.92072,40.91268],[-73.89463,40.99461],[-74.32825,41.18583],[-74.6956,41.35872],[-74.76152,41.33913],[-74.83431,41.28136],[-74.88237,41.1848],[-75.02931,41.03995],[-75.13849,40.98347],[-75.05266,40.8657],[-75.06777,40.84804],[-75.09935,40.84804],[-75.08219,40.82726],[-75.133,40.77373],[-75.17076,40.77893],[-75.19617,40.75084],[-75.18175,40.73107],[-75.20372,40.691],[-75.18003,40.66939],[-75.20029,40.64881],[-75.19068,40.63865],[-75.19102,40.62093],[-75.19994,40.61259],[-75.19068,40.59435],[-75.1948,40.57844],[-75.18106,40.56619],[-75.16458,40.56332],[-75.14776,40.57453],[-75.12682,40.57453],[-75.10347,40.56984],[-75.06708,40.5388],[-75.06365,40.48581],[-75.07017,40.45525],[-75.06124,40.4218],[-75.03137,40.40534],[-74.98743,40.40664],[-74.96477,40.39592],[-74.94966,40.36611],[-74.94451,40.34152],[-74.91396,40.3177],[-74.89198,40.31246],[-74.86315,40.28837],[-74.84426,40.25013],[-74.82504,40.24069],[-74.78349,40.2234],[-74.76701,40.2095],[-74.75843,40.18774],[-74.73852,40.17855],[-74.72273,40.16045],[-74.72341,40.14864],[-74.74127,40.13473],[-74.75843,40.13447],[-74.7907,40.12187],[-74.81542,40.1287],[-74.82641,40.12633],[-74.83774,40.10244],[-74.8628,40.08432],[-74.91224,40.06987],[-74.9246,40.07224],[-74.97164,40.05279],[-75.01352,40.0202],[-75.04682,40.01099],[-75.0712,39.97969],[-75.09901,39.97443],[-75.13334,39.95786],[-75.13815,39.93627],[-75.12785,39.91073],[-75.13986,39.88716],[-75.16905,39.88294],[-75.26809,39.85014],[-75.33745,39.84842],[-75.41621,39.80165],[-75.40672,39.7962],[-75.46131,39.77457],[-75.55641,39.63352],[-75.56053,39.45773]]]}},{"type":"Feature","properties":{"id":"US-NM"},"geometry":{"type":"Polygon","coordinates":[[[-109.05235,31.3333],[-108.20979,31.33316],[-108.20899,31.78534],[-106.529,31.784],[-106.5436,31.80546],[-106.60333,31.82938],[-106.64522,31.89178],[-106.62187,31.92151],[-106.61981,32.00074],[-103.06402,31.99986],[-103.04239,36.49992],[-103.00325,36.50047],[-103.00462,36.99417],[-109.04686,37.00061],[-109.05235,31.3333]]]}},{"type":"Feature","properties":{"id":"CN-HA"},"geometry":{"type":"Polygon","coordinates":[[[110.35388,34.519],[110.6385,34.18056],[110.587,33.89093],[110.84106,33.67978],[111.0189,33.56771],[110.9801,33.2605],[111.18163,33.10534],[111.29665,32.85622],[111.46488,32.73732],[111.57474,32.59455],[111.67293,32.62636],[112.0008,32.45864],[112.31323,32.32862],[112.55149,32.39851],[113.2505,32.39213],[113.41804,32.27784],[113.71467,32.43329],[113.75038,32.26855],[113.7284,32.08432],[113.84582,31.84314],[113.9859,31.75736],[114.18708,31.85452],[114.58465,31.71648],[114.83184,31.45092],[115.21018,31.58204],[115.36165,31.40228],[115.4718,31.65045],[115.64002,31.76145],[115.87005,31.77195],[115.93872,32.06861],[115.86181,32.45705],[115.91812,32.57922],[115.56312,32.38634],[115.44982,32.53813],[115.1889,32.59946],[115.20675,32.84844],[114.88128,32.97295],[114.91287,33.14387],[115.13465,33.08348],[115.30769,33.20709],[115.34957,33.5185],[115.62286,33.57115],[115.54801,33.8821],[115.59059,34.02563],[115.76431,34.07598],[115.99845,33.97439],[115.96034,33.90376],[116.05545,33.85787],[116.03176,33.83563],[116.14986,33.71148],[116.22951,33.72148],[116.63806,33.8858],[116.64974,33.96585],[116.53266,34.09588],[116.57867,34.27537],[116.2429,34.37177],[116.19792,34.51759],[116.09596,34.60665],[115.67916,34.55577],[115.45394,34.63659],[115.42545,34.8014],[115.19439,34.90817],[115.21465,34.94814],[115.13122,35.00187],[114.82429,34.994],[114.91218,35.19962],[115.07903,35.40416],[115.32966,35.47744],[115.41206,35.64111],[115.67985,35.76211],[115.87005,35.91185],[116.03073,35.963],[116.0939,36.11069],[115.64414,35.91936],[115.49171,35.92297],[115.35644,35.77771],[115.35026,35.95021],[115.43197,36.01078],[115.48072,36.15506],[115.31936,36.07518],[115.19714,36.22544],[114.91493,36.04354],[114.91287,36.13066],[114.61418,36.12345],[114.02435,36.33172],[113.72909,36.36103],[113.65287,35.83507],[113.57597,35.81614],[113.60961,35.67626],[113.48945,35.52943],[113.02802,35.35937],[112.76916,35.20635],[112.05505,35.27981],[112.03994,35.04517],[111.82228,35.07159],[111.57508,34.84649],[111.22695,34.79294],[111.1576,34.81831],[110.8905,34.65354],[110.41122,34.58432],[110.35388,34.519]]]}},{"type":"Feature","properties":{"id":"US-NY"},"geometry":{"type":"Polygon","coordinates":[[[-79.77073,42.55308],[-79.76349,42.00107],[-75.3772,42.00515],[-75.08057,41.80278],[-75.0531,41.59155],[-74.98718,41.48258],[-74.74274,41.42288],[-74.6956,41.35872],[-73.89463,40.99461],[-73.92072,40.91268],[-73.95197,40.8509],[-74.01445,40.7589],[-74.02612,40.69959],[-74.05668,40.65402],[-74.12775,40.6436],[-74.18577,40.64647],[-74.20397,40.63162],[-74.19985,40.59956],[-74.21839,40.55836],[-74.23418,40.55914],[-74.25134,40.54532],[-74.24722,40.52236],[-74.25615,40.51427],[-74.2613,40.49625],[-74.23109,40.47954],[-73.9166,40.514],[-73.81773,39.66512],[-71.6391,40.94332],[-71.85736,41.32188],[-73.62136,40.9494],[-73.65981,40.98854],[-73.65723,40.99062],[-73.65947,40.99373],[-73.66067,41.00098],[-73.65535,41.01225],[-73.72761,41.10079],[-73.48283,41.21298],[-73.5508,41.2957],[-73.48746,42.0497],[-73.50942,42.0867],[-73.26498,42.74494],[-73.25409,43.57117],[-73.33649,43.62686],[-73.43811,43.57117],[-73.35571,43.77182],[-73.4436,44.07055],[-73.3255,44.25969],[-73.35025,45.00942],[-74.32699,44.99029],[-74.66689,45.00646],[-74.8447,45.00606],[-74.99101,44.98051],[-75.01363,44.95608],[-75.2193,44.87821],[-75.41441,44.76614],[-75.76813,44.51537],[-75.8217,44.43176],[-75.95947,44.34463],[-76.00018,44.34896],[-76.16285,44.28262],[-76.1664,44.23051],[-76.244,44.19643],[-76.31222,44.19894],[-76.35324,44.13493],[-76.43859,44.09393],[-76.79706,43.63099],[-79.25796,43.54052],[-79.06921,43.26183],[-79.05512,43.25375],[-79.05544,43.21224],[-79.05002,43.20133],[-79.05384,43.17418],[-79.04652,43.16396],[-79.0427,43.13934],[-79.06881,43.12029],[-79.05671,43.10937],[-79.07486,43.07845],[-79.01055,43.06659],[-78.99941,43.05612],[-79.02424,43.01983],[-79.02074,42.98444],[-78.98126,42.97],[-78.96312,42.95509],[-78.93224,42.95229],[-78.90905,42.93022],[-78.90712,42.89733],[-78.93684,42.82887],[-79.77073,42.55308]]]}},{"type":"Feature","properties":{"id":"US-NC"},"geometry":{"type":"Polygon","coordinates":[[[-84.32551,34.99393],[-83.10796,35.0011],[-82.39746,35.20044],[-81.04889,35.15105],[-81.04889,35.04993],[-80.9198,35.08815],[-80.79346,34.94193],[-80.78796,34.82252],[-79.68109,34.81124],[-77.99552,33.38485],[-74.86753,35.41538],[-75.79776,36.55091],[-80.30148,36.54837],[-81.66962,36.58987],[-81.72455,36.33806],[-81.82617,36.36682],[-82.06238,36.11872],[-82.22992,36.16086],[-82.57324,35.96103],[-82.64191,36.06989],[-82.97424,35.78744],[-83.13904,35.76961],[-83.50159,35.56433],[-83.74603,35.56209],[-84.0097,35.4324],[-84.03717,35.29129],[-84.1333,35.24419],[-84.22943,35.27335],[-84.28711,35.224],[-84.32551,34.99393]]]}},{"type":"Feature","properties":{"id":"US-PA"},"geometry":{"type":"Polygon","coordinates":[[[-80.53581,42.29896],[-80.51902,40.63803],[-80.52292,39.72222],[-79.47663,39.72086],[-75.78987,39.72204],[-75.77476,39.7223],[-75.76,39.75002],[-75.74043,39.77378],[-75.72532,39.78644],[-75.71434,39.79515],[-75.68172,39.81387],[-75.65975,39.82337],[-75.62713,39.83259],[-75.59898,39.83734],[-75.57632,39.83945],[-75.53959,39.83945],[-75.49942,39.83365],[-75.46783,39.82548],[-75.43796,39.81414],[-75.41621,39.80165],[-75.33745,39.84842],[-75.26809,39.85014],[-75.16905,39.88294],[-75.13986,39.88716],[-75.12785,39.91073],[-75.13815,39.93627],[-75.13334,39.95786],[-75.09901,39.97443],[-75.0712,39.97969],[-75.04682,40.01099],[-75.01352,40.0202],[-74.97164,40.05279],[-74.9246,40.07224],[-74.91224,40.06987],[-74.8628,40.08432],[-74.83774,40.10244],[-74.82641,40.12633],[-74.81542,40.1287],[-74.7907,40.12187],[-74.75843,40.13447],[-74.74127,40.13473],[-74.72341,40.14864],[-74.72273,40.16045],[-74.73852,40.17855],[-74.75843,40.18774],[-74.76701,40.2095],[-74.78349,40.2234],[-74.82504,40.24069],[-74.84426,40.25013],[-74.86315,40.28837],[-74.89198,40.31246],[-74.91396,40.3177],[-74.94451,40.34152],[-74.94966,40.36611],[-74.96477,40.39592],[-74.98743,40.40664],[-75.03137,40.40534],[-75.06124,40.4218],[-75.07017,40.45525],[-75.06365,40.48581],[-75.06708,40.5388],[-75.10347,40.56984],[-75.12682,40.57453],[-75.14776,40.57453],[-75.16458,40.56332],[-75.18106,40.56619],[-75.1948,40.57844],[-75.19068,40.59435],[-75.19994,40.61259],[-75.19102,40.62093],[-75.19068,40.63865],[-75.20029,40.64881],[-75.18003,40.66939],[-75.20372,40.691],[-75.18175,40.73107],[-75.19617,40.75084],[-75.17076,40.77893],[-75.133,40.77373],[-75.08219,40.82726],[-75.09935,40.84804],[-75.06777,40.84804],[-75.05266,40.8657],[-75.13849,40.98347],[-75.02931,41.03995],[-74.88237,41.1848],[-74.83431,41.28136],[-74.76152,41.33913],[-74.6956,41.35872],[-74.74274,41.42288],[-74.98718,41.48258],[-75.0531,41.59155],[-75.08057,41.80278],[-75.3772,42.00515],[-79.76349,42.00107],[-79.77073,42.55308],[-80.53581,42.29896]]]}},{"type":"Feature","properties":{"id":"US-OR"},"geometry":{"type":"Polygon","coordinates":[[[-125.69978,42.00605],[-120.0016,41.99495],[-117.02619,42.00013],[-117.02923,43.83121],[-116.99215,43.8629],[-116.96194,43.91734],[-116.97704,43.96282],[-116.93859,43.98654],[-116.93722,44.03198],[-116.97704,44.05172],[-116.97429,44.08921],[-116.94271,44.09513],[-116.89602,44.15526],[-116.90151,44.17792],[-116.97155,44.19565],[-116.97841,44.24387],[-117.18304,44.26453],[-117.22149,44.30238],[-117.18921,44.34266],[-117.24003,44.37408],[-117.22492,44.48392],[-117.15626,44.53093],[-117.05051,44.74298],[-116.9379,44.78588],[-116.83353,44.92995],[-116.85825,44.97563],[-116.84864,45.02321],[-116.75388,45.11342],[-116.67972,45.3185],[-116.46274,45.60746],[-116.54789,45.7533],[-116.77585,45.82129],[-116.91799,45.99567],[-118.98743,46.00052],[-119.1378,45.92797],[-119.26826,45.94086],[-119.4928,45.90647],[-119.6109,45.92654],[-119.67064,45.85772],[-119.96658,45.82375],[-120.16502,45.7663],[-120.21103,45.72701],[-120.50079,45.69537],[-120.56053,45.74043],[-120.63538,45.74714],[-120.89493,45.65219],[-121.08307,45.65075],[-121.14899,45.60609],[-121.19156,45.61282],[-121.21284,45.66802],[-121.34674,45.70592],[-121.41266,45.69489],[-121.53008,45.7227],[-121.71478,45.69489],[-121.81366,45.71215],[-121.90097,45.67502],[-121.9891,45.62074],[-122.14899,45.58514],[-122.23595,45.55275],[-122.32384,45.54697],[-122.38014,45.5739],[-122.43919,45.5638],[-122.47456,45.57942],[-122.67609,45.61786],[-122.76501,45.65747],[-122.77668,45.68745],[-122.76157,45.7378],[-122.76501,45.76654],[-122.79591,45.81203],[-122.78492,45.86704],[-122.81445,45.91627],[-122.81033,45.96068],[-122.87762,46.03414],[-122.8941,46.07797],[-122.96688,46.10654],[-123.00945,46.13605],[-123.13442,46.18647],[-123.17837,46.18694],[-123.28754,46.14367],[-123.38093,46.14842],[-123.43243,46.18219],[-123.42968,46.23351],[-123.47431,46.26864],[-123.58349,46.25583],[-123.87806,46.23588],[-123.92818,46.23968],[-124.02981,46.30281],[-124.03873,46.26295],[-124.14997,46.26295],[-125.2772,46.2631],[-125.69978,42.00605]]]}},{"type":"Feature","properties":{"id":"CN-SH"},"geometry":{"type":"Polygon","coordinates":[[[120.85475,31.10938],[120.89836,31.09028],[120.89424,31.01822],[120.98659,31.01939],[120.98865,30.89603],[121.03122,30.82265],[121.12529,30.86509],[121.12152,30.78018],[121.21456,30.78726],[121.26434,30.68516],[123.5458,31.01942],[122.29378,31.76513],[121.76147,31.63818],[121.44286,31.76086],[121.30554,31.88338],[121.09954,31.75853],[121.3821,31.54723],[121.25335,31.47933],[121.23722,31.49704],[121.14246,31.44492],[121.15447,31.41108],[121.11173,31.37459],[121.12581,31.30348],[121.14057,31.31038],[121.15722,31.28515],[121.1495,31.27752],[121.11808,31.28529],[121.06006,31.27356],[121.05903,31.23658],[121.07276,31.16345],[121.03671,31.14171],[120.88325,31.14083],[120.85475,31.10938]]]}},{"type":"Feature","properties":{"id":"US-WA"},"geometry":{"type":"Polygon","coordinates":[[[-125.2772,46.2631],[-124.14997,46.26295],[-124.03873,46.26295],[-124.02981,46.30281],[-123.92818,46.23968],[-123.87806,46.23588],[-123.58349,46.25583],[-123.47431,46.26864],[-123.42968,46.23351],[-123.43243,46.18219],[-123.38093,46.14842],[-123.28754,46.14367],[-123.17837,46.18694],[-123.13442,46.18647],[-123.00945,46.13605],[-122.96688,46.10654],[-122.8941,46.07797],[-122.87762,46.03414],[-122.81033,45.96068],[-122.81445,45.91627],[-122.78492,45.86704],[-122.79591,45.81203],[-122.76501,45.76654],[-122.76157,45.7378],[-122.77668,45.68745],[-122.76501,45.65747],[-122.67609,45.61786],[-122.47456,45.57942],[-122.43919,45.5638],[-122.38014,45.5739],[-122.32384,45.54697],[-122.23595,45.55275],[-122.14899,45.58514],[-121.9891,45.62074],[-121.90097,45.67502],[-121.81366,45.71215],[-121.71478,45.69489],[-121.53008,45.7227],[-121.41266,45.69489],[-121.34674,45.70592],[-121.21284,45.66802],[-121.19156,45.61282],[-121.14899,45.60609],[-121.08307,45.65075],[-120.89493,45.65219],[-120.63538,45.74714],[-120.56053,45.74043],[-120.50079,45.69537],[-120.21103,45.72701],[-120.16502,45.7663],[-119.96658,45.82375],[-119.67064,45.85772],[-119.6109,45.92654],[-119.4928,45.90647],[-119.26826,45.94086],[-119.1378,45.92797],[-118.98743,46.00052],[-116.91799,45.99567],[-116.94958,46.07004],[-116.98116,46.08242],[-116.92348,46.16048],[-117.03472,46.34138],[-117.04811,46.34281],[-117.06184,46.34873],[-117.06287,46.3665],[-117.04914,46.37787],[-117.03575,46.40794],[-117.03644,46.42309],[-117.04021,46.4257],[-117.03266,49.00056],[-123.32163,49.00419],[-123.0093,48.83186],[-123.0093,48.76586],[-123.26565,48.6959],[-123.15614,48.35395],[-123.50039,48.21223],[-125.03842,48.53282],[-125.2772,46.2631]]]}},{"type":"Feature","properties":{"id":"US-OK"},"geometry":{"type":"Polygon","coordinates":[[[-103.00462,36.99417],[-103.00325,36.50047],[-100.00134,36.50078],[-99.99928,34.56197],[-99.9725,34.56197],[-99.95464,34.5778],[-99.92443,34.57836],[-99.76444,34.42841],[-99.71638,34.40462],[-99.71226,34.38932],[-99.66557,34.37515],[-99.59965,34.37629],[-99.58523,34.38989],[-99.58111,34.41821],[-99.50489,34.41198],[-99.43554,34.37515],[-99.39915,34.37799],[-99.39434,34.44257],[-99.37717,34.45899],[-99.23916,34.36439],[-99.18354,34.22029],[-99.04415,34.20042],[-98.98579,34.22313],[-98.75164,34.1277],[-98.64452,34.1635],[-98.58616,34.15384],[-98.49277,34.06572],[-98.409,34.09074],[-98.37879,34.15384],[-98.16662,34.11462],[-98.12061,34.15725],[-98.08834,34.13111],[-98.12061,34.07653],[-98.08697,34.00483],[-97.95033,33.99459],[-97.98191,33.89547],[-97.87067,33.85043],[-97.81368,33.87552],[-97.68116,33.99117],[-97.59258,33.95701],[-97.59327,33.90687],[-97.55138,33.89832],[-97.49783,33.92169],[-97.45251,33.89718],[-97.45731,33.83218],[-97.40513,33.82135],[-97.3605,33.82648],[-97.30762,33.88521],[-97.26849,33.85899],[-97.23553,33.91713],[-97.1854,33.90402],[-97.16686,33.84473],[-97.20394,33.81735],[-97.18334,33.75115],[-97.15038,33.72317],[-97.09888,33.72603],[-97.08515,33.75857],[-97.09614,33.80252],[-97.05221,33.82252],[-97.08515,33.85614],[-97.02691,33.84657],[-96.98215,33.89376],[-96.99245,33.93365],[-96.93958,33.95871],[-96.90113,33.94163],[-96.86954,33.85386],[-96.81118,33.87267],[-96.75762,33.82648],[-96.70887,33.8396],[-96.6663,33.91599],[-96.58733,33.89262],[-96.62441,33.85157],[-96.57497,33.82021],[-96.52622,33.82591],[-96.507,33.77342],[-96.42735,33.78026],[-96.35044,33.68661],[-96.32023,33.7049],[-96.29002,33.7757],[-96.22959,33.75743],[-96.18565,33.75743],[-96.16367,33.82135],[-95.93296,33.88749],[-95.84232,33.84416],[-95.57041,33.93992],[-95.54295,33.89661],[-95.43034,33.87381],[-95.28477,33.88521],[-95.2216,33.96726],[-95.10349,33.92397],[-94.88651,33.76657],[-94.48448,33.64004],[-94.43092,35.38371],[-94.61906,36.49995],[-94.61795,36.9986],[-102.04214,36.99314],[-103.00462,36.99417]]]}},{"type":"Feature","properties":{"id":"US-TX"},"geometry":{"type":"Polygon","coordinates":[[[-106.64522,31.89178],[-106.60333,31.82938],[-106.5436,31.80546],[-106.529,31.784],[-106.52266,31.77509],[-106.51251,31.76922],[-106.50962,31.76155],[-106.50111,31.75714],[-106.48815,31.74769],[-106.47298,31.75054],[-106.46726,31.75998],[-106.45244,31.76523],[-106.43419,31.75478],[-106.41773,31.75196],[-106.38003,31.73151],[-106.3718,31.71165],[-106.34864,31.69663],[-106.33419,31.66303],[-106.30305,31.62154],[-106.28084,31.56173],[-106.24612,31.54193],[-106.23711,31.51262],[-106.20346,31.46305],[-106.09025,31.40569],[-106.00363,31.39181],[-104.77674,30.4236],[-104.5171,29.64671],[-104.3969,29.57105],[-104.39363,29.55396],[-104.37752,29.54255],[-103.15787,28.93865],[-102.60596,29.8192],[-101.47277,29.7744],[-101.05686,29.44738],[-101.01128,29.36947],[-100.96725,29.3477],[-100.94579,29.34523],[-100.94056,29.33371],[-100.87982,29.296],[-100.79696,29.24688],[-100.67294,29.09744],[-100.63689,28.90812],[-100.59809,28.88197],[-100.52313,28.75598],[-100.5075,28.74066],[-100.51222,28.70679],[-100.50029,28.66117],[-99.55409,27.61314],[-99.51478,27.55836],[-99.52955,27.49747],[-99.50208,27.50021],[-99.48045,27.49016],[-99.482,27.47128],[-99.49744,27.43746],[-99.53573,27.30926],[-99.08477,26.39849],[-99.03053,26.41249],[-99.00546,26.3925],[-98.35126,26.15129],[-98.30491,26.10475],[-98.27075,26.09457],[-98.24603,26.07191],[-97.97017,26.05232],[-97.95155,26.0625],[-97.66511,26.01708],[-97.52025,25.88518],[-97.49828,25.89877],[-97.45669,25.86874],[-97.42511,25.83969],[-97.37332,25.83854],[-97.35946,25.92189],[-97.13927,25.96583],[-96.92418,25.97377],[-93.77551,29.43998],[-93.92382,29.81005],[-93.71508,30.05521],[-93.70959,30.28791],[-93.76452,30.33771],[-93.52008,31.03684],[-93.92932,31.91524],[-94.04467,32.00379],[-94.04366,33.01929],[-94.0464,33.55539],[-94.07661,33.57828],[-94.38423,33.56455],[-94.48448,33.64004],[-94.88651,33.76657],[-95.10349,33.92397],[-95.2216,33.96726],[-95.28477,33.88521],[-95.43034,33.87381],[-95.54295,33.89661],[-95.57041,33.93992],[-95.84232,33.84416],[-95.93296,33.88749],[-96.16367,33.82135],[-96.18565,33.75743],[-96.22959,33.75743],[-96.29002,33.7757],[-96.32023,33.7049],[-96.35044,33.68661],[-96.42735,33.78026],[-96.507,33.77342],[-96.52622,33.82591],[-96.57497,33.82021],[-96.62441,33.85157],[-96.58733,33.89262],[-96.6663,33.91599],[-96.70887,33.8396],[-96.75762,33.82648],[-96.81118,33.87267],[-96.86954,33.85386],[-96.90113,33.94163],[-96.93958,33.95871],[-96.99245,33.93365],[-96.98215,33.89376],[-97.02691,33.84657],[-97.08515,33.85614],[-97.05221,33.82252],[-97.09614,33.80252],[-97.08515,33.75857],[-97.09888,33.72603],[-97.15038,33.72317],[-97.18334,33.75115],[-97.20394,33.81735],[-97.16686,33.84473],[-97.1854,33.90402],[-97.23553,33.91713],[-97.26849,33.85899],[-97.30762,33.88521],[-97.3605,33.82648],[-97.40513,33.82135],[-97.45731,33.83218],[-97.45251,33.89718],[-97.49783,33.92169],[-97.55138,33.89832],[-97.59327,33.90687],[-97.59258,33.95701],[-97.68116,33.99117],[-97.81368,33.87552],[-97.87067,33.85043],[-97.98191,33.89547],[-97.95033,33.99459],[-98.08697,34.00483],[-98.12061,34.07653],[-98.08834,34.13111],[-98.12061,34.15725],[-98.16662,34.11462],[-98.37879,34.15384],[-98.409,34.09074],[-98.49277,34.06572],[-98.58616,34.15384],[-98.64452,34.1635],[-98.75164,34.1277],[-98.98579,34.22313],[-99.04415,34.20042],[-99.18354,34.22029],[-99.23916,34.36439],[-99.37717,34.45899],[-99.39434,34.44257],[-99.39915,34.37799],[-99.43554,34.37515],[-99.50489,34.41198],[-99.58111,34.41821],[-99.58523,34.38989],[-99.59965,34.37629],[-99.66557,34.37515],[-99.71226,34.38932],[-99.71638,34.40462],[-99.76444,34.42841],[-99.92443,34.57836],[-99.95464,34.5778],[-99.9725,34.56197],[-99.99928,34.56197],[-100.00134,36.50078],[-103.00325,36.50047],[-103.04239,36.49992],[-103.06402,31.99986],[-106.61981,32.00074],[-106.62187,31.92151],[-106.64522,31.89178]]]}},{"type":"Feature","properties":{"id":"US-OH"},"geometry":{"type":"Polygon","coordinates":[[[-84.81884,39.10708],[-84.74887,39.14851],[-84.68432,39.09844],[-84.62252,39.07392],[-84.56622,39.08671],[-84.54631,39.10163],[-84.51747,39.09151],[-84.49893,39.0995],[-84.47695,39.12188],[-84.4488,39.11922],[-84.4337,39.09577],[-84.43026,39.05739],[-84.41035,39.0446],[-84.34924,39.0382],[-84.30117,38.99445],[-84.29293,38.95334],[-84.23388,38.88176],[-84.23388,38.81331],[-84.07183,38.77263],[-83.95648,38.78762],[-83.79443,38.69765],[-83.7752,38.65047],[-83.66534,38.62902],[-83.62414,38.67621],[-83.52801,38.70408],[-83.33575,38.64618],[-83.31652,38.60112],[-83.27533,38.59897],[-83.24511,38.63116],[-83.20117,38.61614],[-83.14898,38.62258],[-83.10778,38.67621],[-83.0556,38.69336],[-83.02951,38.72872],[-82.97869,38.72765],[-82.89492,38.7555],[-82.8702,38.73943],[-82.87844,38.69121],[-82.84549,38.58502],[-82.79879,38.56355],[-82.72601,38.55603],[-82.61203,38.47329],[-82.59692,38.42382],[-82.54406,38.39937],[-82.31884,38.45101],[-82.28588,38.59283],[-82.17876,38.59713],[-82.21722,38.80933],[-82.14306,38.83714],[-82.1513,38.89488],[-82.08813,38.9782],[-82.02496,39.02943],[-81.93707,38.98888],[-81.90136,38.92694],[-81.93157,38.89702],[-81.89587,38.8735],[-81.84368,38.9013],[-81.83544,38.94831],[-81.76403,38.91839],[-81.74755,38.93762],[-81.786,38.96966],[-81.76128,39.01876],[-81.81622,39.05716],[-81.81622,39.08915],[-81.76952,39.07635],[-81.74206,39.1062],[-81.7503,39.1914],[-81.68987,39.2297],[-81.69537,39.26161],[-81.65417,39.28287],[-81.56902,39.27649],[-81.55529,39.35724],[-81.5086,39.36573],[-81.4674,39.41243],[-81.41796,39.39758],[-81.39874,39.35087],[-81.36303,39.34025],[-81.26141,39.39121],[-81.22021,39.38909],[-81.1845,39.43153],[-81.13232,39.4485],[-81.05541,39.53116],[-80.91534,39.61796],[-80.87139,39.62854],[-80.86384,39.68801],[-80.82813,39.71337],[-80.86555,39.77013],[-80.82092,39.80891],[-80.82436,39.84477],[-80.78899,39.87323],[-80.8065,39.90985],[-80.79655,39.91933],[-80.76702,39.90853],[-80.75329,39.91222],[-80.7605,39.95539],[-80.73818,39.97749],[-80.74265,40.00564],[-80.73097,40.03824],[-80.73681,40.07792],[-80.70591,40.10445],[-80.70763,40.14645],[-80.66917,40.19918],[-80.65475,40.24374],[-80.61871,40.26627],[-80.61733,40.28828],[-80.60017,40.31708],[-80.61253,40.3409],[-80.60944,40.37491],[-80.63313,40.39243],[-80.61356,40.40655],[-80.61424,40.43216],[-80.59776,40.46195],[-80.59639,40.47971],[-80.61287,40.49485],[-80.62935,40.53452],[-80.66608,40.58043],[-80.6345,40.61458],[-80.59948,40.6237],[-80.57837,40.6125],[-80.51902,40.63803],[-80.53581,42.29896],[-82.67862,41.67615],[-83.11184,41.95671],[-83.42355,41.73233],[-84.80645,41.69747],[-84.81884,39.10708]]]}},{"type":"Feature","properties":{"id":"US-RI"},"geometry":{"type":"Polygon","coordinates":[[[-71.85736,41.32188],[-71.6391,40.94332],[-71.10101,41.43444],[-71.13123,41.591],[-71.14153,41.60717],[-71.13432,41.65952],[-71.19577,41.67465],[-71.26135,41.75231],[-71.32898,41.7815],[-71.341,41.79814],[-71.33894,41.89916],[-71.3822,41.89277],[-71.38117,42.0194],[-71.8001,42.00779],[-71.79866,41.41592],[-71.81926,41.41952],[-71.84363,41.40948],[-71.84191,41.39455],[-71.83367,41.38837],[-71.83333,41.37033],[-71.83917,41.3626],[-71.82955,41.34199],[-71.85736,41.32188]]]}},{"type":"Feature","properties":{"id":"US-SC"},"geometry":{"type":"Polygon","coordinates":[[[-83.35447,34.72814],[-83.34829,34.69455],[-83.22916,34.61096],[-83.17148,34.60728],[-83.16873,34.59259],[-83.03655,34.48625],[-83.00085,34.47238],[-82.90231,34.48625],[-82.87519,34.47408],[-82.83365,34.36419],[-82.79657,34.34039],[-82.78043,34.29672],[-82.75057,34.2709],[-82.74095,34.20789],[-82.71658,34.14882],[-82.64311,34.0951],[-82.64105,34.06809],[-82.59538,34.02855],[-82.56414,33.95567],[-82.39008,33.85651],[-82.3015,33.80062],[-82.20022,33.66214],[-82.19747,33.63013],[-82.1343,33.59095],[-82.10752,33.59782],[-82.05019,33.56464],[-81.99766,33.51342],[-81.9877,33.48794],[-81.92968,33.46674],[-81.91354,33.43953],[-81.93964,33.34609],[-81.85587,33.30248],[-81.85037,33.24622],[-81.78858,33.20716],[-81.77553,33.2198],[-81.75974,33.19912],[-81.7721,33.18303],[-81.76248,33.15947],[-81.70824,33.11807],[-81.64163,33.09276],[-81.62103,33.09564],[-81.49538,33.00988],[-81.50087,32.93557],[-81.45555,32.84851],[-81.42809,32.84101],[-81.39581,32.65101],[-81.41847,32.63193],[-81.38277,32.59086],[-81.3244,32.55788],[-81.28595,32.55904],[-81.27771,32.53531],[-81.19257,32.46292],[-81.20767,32.42409],[-81.11772,32.29127],[-81.15823,32.24017],[-81.11978,32.1937],[-81.11635,32.11638],[-81.05386,32.08497],[-81.00511,32.1001],[-80.92066,32.03667],[-80.49837,32.0326],[-77.99552,33.38485],[-79.68109,34.81124],[-80.78796,34.82252],[-80.79346,34.94193],[-80.9198,35.08815],[-81.04889,35.04993],[-81.04889,35.15105],[-82.39746,35.20044],[-83.10796,35.0011],[-83.09869,34.99098],[-83.12616,34.9544],[-83.11689,34.94033],[-83.2374,34.87417],[-83.32357,34.78878],[-83.32048,34.75861],[-83.35447,34.72814]]]}},{"type":"Feature","properties":{"id":"US-TN"},"geometry":{"type":"Polygon","coordinates":[[[-90.31242,34.99989],[-88.20029,34.99532],[-88.20305,35.00664],[-85.60478,34.98639],[-84.32551,34.99393],[-84.28711,35.224],[-84.22943,35.27335],[-84.1333,35.24419],[-84.03717,35.29129],[-84.0097,35.4324],[-83.74603,35.56209],[-83.50159,35.56433],[-83.13904,35.76961],[-82.97424,35.78744],[-82.64191,36.06989],[-82.57324,35.96103],[-82.22992,36.16086],[-82.06238,36.11872],[-81.82617,36.36682],[-81.72455,36.33806],[-81.66962,36.58987],[-81.64833,36.61206],[-81.92299,36.61647],[-81.93466,36.5947],[-83.67455,36.60056],[-83.69109,36.58281],[-86.51355,36.65555],[-86.56848,36.63572],[-86.5932,36.65555],[-87.80993,36.63792],[-88.06536,36.67979],[-88.05987,36.49674],[-89.41668,36.50778],[-89.45127,36.46431],[-89.48972,36.46541],[-89.47187,36.55813],[-89.52268,36.5846],[-89.57212,36.56144],[-89.52268,36.46762],[-89.5474,36.43117],[-89.57212,36.24754],[-89.6916,36.24089],[-89.58997,36.1478],[-89.61332,36.10897],[-89.73701,36.00048],[-89.64638,35.91489],[-89.67384,35.8804],[-89.73564,35.91044],[-89.77409,35.87261],[-89.70131,35.83366],[-89.72878,35.8047],[-89.95399,35.73228],[-89.94301,35.66871],[-89.89357,35.64528],[-90.11604,35.38483],[-90.07072,35.1314],[-90.16136,35.13252],[-90.31242,34.99989]]]}},{"type":"Feature","properties":{"id":"US-VT"},"geometry":{"type":"Polygon","coordinates":[[[-73.4436,44.07055],[-73.35571,43.77182],[-73.43811,43.57117],[-73.33649,43.62686],[-73.25409,43.57117],[-73.26498,42.74494],[-72.45969,42.72515],[-72.49456,42.77368],[-72.51722,42.7636],[-72.51173,42.78174],[-72.54331,42.81147],[-72.55705,42.85427],[-72.55361,42.88497],[-72.53301,42.89553],[-72.52546,42.93626],[-72.53233,42.95134],[-72.46504,42.98099],[-72.46092,43.05479],[-72.43688,43.08388],[-72.441,43.13702],[-72.45611,43.14654],[-72.39362,43.35659],[-72.41491,43.36557],[-72.38127,43.49472],[-72.40049,43.51265],[-72.32702,43.63553],[-72.30093,43.70754],[-72.20617,43.77302],[-72.16291,43.8919],[-72.12309,43.9201],[-72.11141,43.9977],[-72.04,44.08408],[-72.06335,44.27514],[-72.0249,44.31741],[-71.9782,44.33411],[-71.90679,44.34688],[-71.86834,44.33706],[-71.81753,44.35866],[-71.79693,44.39891],[-71.7008,44.41558],[-71.639,44.47343],[-71.59368,44.49302],[-71.59231,44.56351],[-71.56347,44.56351],[-71.54287,44.58601],[-71.63351,44.74912],[-71.57583,44.79592],[-71.55386,44.86214],[-71.49618,44.90788],[-71.54287,44.98758],[-71.50067,45.01357],[-73.35025,45.00942],[-73.3255,44.25969],[-73.4436,44.07055]]]}},{"type":"Feature","properties":{"id":"US-VA"},"geometry":{"type":"Polygon","coordinates":[[[-83.67455,36.60056],[-81.93466,36.5947],[-81.92299,36.61647],[-81.64833,36.61206],[-81.66962,36.58987],[-80.30148,36.54837],[-75.79776,36.55091],[-75.16879,38.02735],[-75.62472,37.99597],[-75.65768,37.94509],[-75.88153,37.90934],[-76.23034,37.8985],[-76.61074,38.15704],[-76.92248,38.23475],[-77.03784,38.42973],[-77.20813,38.35223],[-77.28778,38.38454],[-77.28915,38.50178],[-77.21225,38.6038],[-77.12161,38.63385],[-77.12023,38.68103],[-77.08041,38.70568],[-77.04196,38.70568],[-77.03906,38.79153],[-77.03021,38.86133],[-77.04592,38.87557],[-77.0491,38.87343],[-77.05493,38.87964],[-77.05957,38.88152],[-77.06759,38.89895],[-77.07047,38.90106],[-77.08897,38.90436],[-77.10201,38.91264],[-77.10592,38.91912],[-77.11536,38.92787],[-77.11962,38.93441],[-77.1477,38.9699],[-77.22186,38.97417],[-77.25619,39.00192],[-77.23971,39.02006],[-77.30975,39.05846],[-77.46081,39.07872],[-77.48416,39.11282],[-77.51849,39.12135],[-77.5281,39.14691],[-77.51162,39.18311],[-77.47729,39.18844],[-77.45532,39.22462],[-77.49102,39.25227],[-77.54596,39.27247],[-77.56655,39.30861],[-77.61462,39.3033],[-77.68054,39.32667],[-77.73135,39.32349],[-77.82818,39.13337],[-78.34728,39.46705],[-78.34934,39.42676],[-78.3617,39.40872],[-78.34385,39.38909],[-78.3672,39.35883],[-78.33973,39.35352],[-78.42075,39.25736],[-78.40084,39.24566],[-78.43792,39.19725],[-78.40496,39.16851],[-78.5725,39.03156],[-78.55396,39.01716],[-78.6034,38.96539],[-78.62743,38.98408],[-78.69198,38.91519],[-78.71944,38.90557],[-78.71807,38.93602],[-78.7421,38.92748],[-78.75858,38.90183],[-78.78742,38.88794],[-78.86982,38.7633],[-78.99479,38.84998],[-79.09504,38.7092],[-79.08543,38.68133],[-79.10191,38.65345],[-79.12663,38.66418],[-79.21864,38.48918],[-79.30515,38.41282],[-79.47681,38.458],[-79.53587,38.55257],[-79.6471,38.59015],[-79.67319,38.53646],[-79.66221,38.51175],[-79.70066,38.49133],[-79.68967,38.45693],[-79.69517,38.42358],[-79.73362,38.37838],[-79.72538,38.36115],[-79.73911,38.35146],[-79.76383,38.35792],[-79.80915,38.30837],[-79.7858,38.26849],[-79.91901,38.18326],[-79.93412,38.10334],[-80.00278,37.99519],[-80.17994,37.85762],[-80.29667,37.68719],[-80.22251,37.62522],[-80.32413,37.56646],[-80.2953,37.51746],[-80.47245,37.42373],[-80.51228,37.48042],[-80.76633,37.37573],[-80.79243,37.39973],[-80.85835,37.43136],[-80.88306,37.38337],[-80.84873,37.34735],[-80.91602,37.30913],[-80.97782,37.29056],[-80.98606,37.30148],[-81.12339,37.27526],[-81.17695,37.26214],[-81.22776,37.23591],[-81.36372,37.33643],[-81.42002,37.27089],[-81.50242,37.2534],[-81.55735,37.20966],[-81.6782,37.20201],[-81.76334,37.27744],[-81.8581,37.28509],[-81.87595,37.32988],[-81.92814,37.36154],[-81.93775,37.43791],[-81.99268,37.4608],[-81.99543,37.4826],[-81.9405,37.50766],[-81.9707,37.53839],[-82.34698,37.2744],[-82.72326,37.12564],[-82.73425,37.04018],[-82.86059,36.98316],[-82.89904,36.88437],[-83.08032,36.84701],[-83.14074,36.75244],[-83.42125,36.66798],[-83.5239,36.66716],[-83.67455,36.60056]]]}},{"type":"Feature","properties":{"id":"US-WV"},"geometry":{"type":"Polygon","coordinates":[[[-82.64361,38.1673],[-82.63263,38.13922],[-82.46646,37.98569],[-82.49392,37.93372],[-82.41702,37.84593],[-82.33462,37.77758],[-82.29754,37.67656],[-82.17669,37.63416],[-82.13961,37.56235],[-81.9707,37.53839],[-81.9405,37.50766],[-81.99543,37.4826],[-81.99268,37.4608],[-81.93775,37.43791],[-81.92814,37.36154],[-81.87595,37.32988],[-81.8581,37.28509],[-81.76334,37.27744],[-81.6782,37.20201],[-81.55735,37.20966],[-81.50242,37.2534],[-81.42002,37.27089],[-81.36372,37.33643],[-81.22776,37.23591],[-81.17695,37.26214],[-81.12339,37.27526],[-80.98606,37.30148],[-80.97782,37.29056],[-80.91602,37.30913],[-80.84873,37.34735],[-80.88306,37.38337],[-80.85835,37.43136],[-80.79243,37.39973],[-80.76633,37.37573],[-80.51228,37.48042],[-80.47245,37.42373],[-80.2953,37.51746],[-80.32413,37.56646],[-80.22251,37.62522],[-80.29667,37.68719],[-80.17994,37.85762],[-80.00278,37.99519],[-79.93412,38.10334],[-79.91901,38.18326],[-79.7858,38.26849],[-79.80915,38.30837],[-79.76383,38.35792],[-79.73911,38.35146],[-79.72538,38.36115],[-79.73362,38.37838],[-79.69517,38.42358],[-79.68967,38.45693],[-79.70066,38.49133],[-79.66221,38.51175],[-79.67319,38.53646],[-79.6471,38.59015],[-79.53587,38.55257],[-79.47681,38.458],[-79.30515,38.41282],[-79.21864,38.48918],[-79.12663,38.66418],[-79.10191,38.65345],[-79.08543,38.68133],[-79.09504,38.7092],[-78.99479,38.84998],[-78.86982,38.7633],[-78.78742,38.88794],[-78.75858,38.90183],[-78.7421,38.92748],[-78.71807,38.93602],[-78.71944,38.90557],[-78.69198,38.91519],[-78.62743,38.98408],[-78.6034,38.96539],[-78.55396,39.01716],[-78.5725,39.03156],[-78.40496,39.16851],[-78.43792,39.19725],[-78.40084,39.24566],[-78.42075,39.25736],[-78.33973,39.35352],[-78.3672,39.35883],[-78.34385,39.38909],[-78.3617,39.40872],[-78.34934,39.42676],[-78.34728,39.46705],[-77.82818,39.13337],[-77.73135,39.32349],[-77.75289,39.42632],[-77.79821,39.43587],[-77.78036,39.49312],[-77.84216,39.49842],[-77.88885,39.55774],[-77.83392,39.56621],[-77.83392,39.60431],[-77.87512,39.617],[-78.01107,39.60113],[-78.05021,39.64768],[-78.10789,39.68203],[-78.18411,39.69524],[-78.31732,39.59479],[-78.40658,39.617],[-78.46872,39.5167],[-78.66167,39.53576],[-78.72621,39.56356],[-78.73789,39.58632],[-78.76364,39.58262],[-78.77771,39.60457],[-78.73892,39.60775],[-78.73789,39.62388],[-78.77702,39.62177],[-78.76501,39.64715],[-78.79659,39.63472],[-78.81925,39.56065],[-78.83951,39.5678],[-78.96688,39.43958],[-79.0455,39.4804],[-79.06885,39.47643],[-79.19657,39.38733],[-79.21579,39.36424],[-79.25321,39.35575],[-79.29235,39.29865],[-79.31158,39.30502],[-79.33801,39.29652],[-79.35999,39.27526],[-79.37853,39.27261],[-79.42831,39.22448],[-79.48702,39.20187],[-79.47663,39.72086],[-80.52292,39.72222],[-80.51902,40.63803],[-80.57837,40.6125],[-80.59948,40.6237],[-80.6345,40.61458],[-80.66608,40.58043],[-80.62935,40.53452],[-80.61287,40.49485],[-80.59639,40.47971],[-80.59776,40.46195],[-80.61424,40.43216],[-80.61356,40.40655],[-80.63313,40.39243],[-80.60944,40.37491],[-80.61253,40.3409],[-80.60017,40.31708],[-80.61733,40.28828],[-80.61871,40.26627],[-80.65475,40.24374],[-80.66917,40.19918],[-80.70763,40.14645],[-80.70591,40.10445],[-80.73681,40.07792],[-80.73097,40.03824],[-80.74265,40.00564],[-80.73818,39.97749],[-80.7605,39.95539],[-80.75329,39.91222],[-80.76702,39.90853],[-80.79655,39.91933],[-80.8065,39.90985],[-80.78899,39.87323],[-80.82436,39.84477],[-80.82092,39.80891],[-80.86555,39.77013],[-80.82813,39.71337],[-80.86384,39.68801],[-80.87139,39.62854],[-80.91534,39.61796],[-81.05541,39.53116],[-81.13232,39.4485],[-81.1845,39.43153],[-81.22021,39.38909],[-81.26141,39.39121],[-81.36303,39.34025],[-81.39874,39.35087],[-81.41796,39.39758],[-81.4674,39.41243],[-81.5086,39.36573],[-81.55529,39.35724],[-81.56902,39.27649],[-81.65417,39.28287],[-81.69537,39.26161],[-81.68987,39.2297],[-81.7503,39.1914],[-81.74206,39.1062],[-81.76952,39.07635],[-81.81622,39.08915],[-81.81622,39.05716],[-81.76128,39.01876],[-81.786,38.96966],[-81.74755,38.93762],[-81.76403,38.91839],[-81.83544,38.94831],[-81.84368,38.9013],[-81.89587,38.8735],[-81.93157,38.89702],[-81.90136,38.92694],[-81.93707,38.98888],[-82.02496,39.02943],[-82.08813,38.9782],[-82.1513,38.89488],[-82.14306,38.83714],[-82.21722,38.80933],[-82.17876,38.59713],[-82.28588,38.59283],[-82.31884,38.45101],[-82.54406,38.39937],[-82.59692,38.42382],[-82.59692,38.342],[-82.57495,38.32261],[-82.58319,38.25039],[-82.61203,38.24176],[-82.59692,38.21156],[-82.60791,38.17378],[-82.64361,38.1673]]]}},{"type":"Feature","properties":{"id":"UA-43"},"geometry":{"type":"Polygon","coordinates":[[[31.62627,45.50633],[32.99857,44.48323],[33.28621,44.94345],[33.5643,44.84057],[33.5849,44.80794],[33.60275,44.81086],[33.6776,44.78577],[33.68172,44.77017],[33.61305,44.75018],[33.61649,44.71237],[33.72772,44.71579],[33.7751,44.68968],[33.71502,44.62081],[33.73528,44.60199],[33.77853,44.6125],[33.92616,44.42082],[33.85784,44.41886],[33.76171,44.38918],[33.66142,43.9825],[36.61884,44.89556],[36.52546,45.20215],[36.68476,45.40306],[36.66828,45.63016],[35.23066,45.79231],[34.96015,45.75634],[34.79905,45.81009],[34.80153,45.90047],[34.75479,45.90705],[34.66679,45.97136],[34.60861,45.99347],[34.55889,45.99347],[34.52011,45.95097],[34.48729,45.94267],[34.44155,45.95995],[34.41221,46.00245],[34.33912,46.06114],[34.25111,46.0532],[34.181,46.06804],[34.12929,46.10494],[34.07311,46.11769],[34.05272,46.10838],[33.91549,46.15938],[33.85234,46.19863],[33.79715,46.20482],[33.74047,46.18555],[33.646,46.23028],[33.61517,46.22615],[33.63854,46.14147],[33.61467,46.13561],[33.57318,46.10317],[33.59087,46.06013],[33.54017,46.0123],[31.62627,45.50633]]]}},{"type":"Feature","properties":{"id":"UA-40"},"geometry":{"type":"Polygon","coordinates":[[[32.99857,44.48323],[33.66142,43.9825],[33.76171,44.38918],[33.85784,44.41886],[33.92616,44.42082],[33.77853,44.6125],[33.73528,44.60199],[33.71502,44.62081],[33.7751,44.68968],[33.72772,44.71579],[33.61649,44.71237],[33.61305,44.75018],[33.68172,44.77017],[33.6776,44.78577],[33.60275,44.81086],[33.5849,44.80794],[33.5643,44.84057],[33.28621,44.94345],[32.99857,44.48323]]]}},{"type":"Feature","properties":{"id":"NL-BQ1"},"geometry":{"type":"Polygon","coordinates":[[[-68.90012,12.62309],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309]]]}},{"type":"Feature","properties":{"id":"CN-GS"},"geometry":{"type":"Polygon","coordinates":[[[92.40874,39.03625],[93.11599,39.17372],[93.42086,38.9092],[94.35607,38.76265],[94.5346,38.35781],[94.99053,38.43638],[95.24459,38.30502],[95.65658,38.36857],[96.29859,38.15669],[96.65702,38.22901],[96.66595,38.48665],[97.05459,38.6284],[96.9358,38.9108],[96.97769,39.20884],[97.3368,39.16733],[98.08937,38.78513],[98.24867,38.88515],[98.28643,39.03358],[98.74923,39.08743],[100.17677,38.2112],[100.09815,38.45735],[100.93105,38.16749],[100.97019,38.01293],[101.3578,37.7916],[101.9878,37.73108],[102.64594,37.10447],[102.47325,36.97238],[102.72319,36.76886],[102.59651,36.71081],[102.71598,36.60009],[102.82859,36.37098],[102.83168,36.33531],[102.88936,36.33338],[102.92026,36.30073],[103.02394,36.25562],[102.97004,36.03299],[102.9467,35.83507],[102.70568,35.86011],[102.80456,35.5758],[102.75169,35.49533],[102.50381,35.58808],[102.3191,35.34369],[102.40218,35.18503],[101.72996,34.70436],[102.15499,34.51221],[102.25318,34.36441],[101.76841,34.06233],[100.94581,34.37404],[100.7611,34.17545],[101.19232,33.79455],[101.17034,33.65463],[101.58782,33.67349],[101.61254,33.51506],[101.76223,33.46925],[101.94625,33.58773],[101.81579,33.10937],[102.46879,33.47211],[102.14332,33.98379],[102.3912,33.97753],[102.45849,34.09929],[102.66792,34.07541],[102.89039,34.33266],[103.17672,34.07484],[103.16162,33.7974],[103.77891,33.66606],[104.10026,33.68435],[104.28634,33.35978],[104.45526,33.32938],[104.33921,33.1979],[104.4326,33.00636],[104.40238,32.79189],[105.11032,32.60004],[105.39321,32.72433],[105.43647,32.94875],[105.47878,32.89406],[105.85945,32.93896],[105.96244,33.15652],[105.72624,33.36322],[105.95764,33.61061],[106.36688,33.61347],[106.51519,33.50246],[106.58523,33.57],[106.41357,33.89777],[106.71295,34.37092],[106.64188,34.38651],[106.6175,34.44797],[106.47399,34.52183],[106.3322,34.51532],[106.33186,34.55972],[106.5612,34.74443],[106.48635,35.05754],[106.76788,35.09238],[107.05833,35.02887],[107.18604,34.9062],[107.8466,34.97487],[107.7079,35.30896],[107.84248,35.26524],[108.58886,35.31176],[108.64654,35.95021],[108.6589,36.41023],[107.34878,36.90378],[107.30346,37.0979],[106.66076,37.19751],[106.49047,36.30848],[106.9313,36.12456],[106.921,35.76824],[106.44241,35.69466],[106.4994,35.35993],[106.36344,35.23889],[106.05857,35.48639],[105.48454,35.72756],[105.32592,35.99911],[105.51544,36.0996],[105.18997,36.95208],[104.29321,37.4367],[103.83865,37.65773],[103.39507,37.88352],[103.36074,38.08809],[103.54476,38.15615],[104.18884,39.12047],[103.9389,39.46482],[102.96798,39.10981],[102.45712,39.23757],[101.8872,39.06931],[101.7279,38.64154],[100.84075,39.18224],[99.70229,39.98659],[100.28045,40.67439],[99.76958,40.97575],[98.3139,40.56806],[97.1777,42.7964],[96.37926,42.72055],[96.04934,42.38796],[96.18324,41.97225],[95.35171,41.54559],[95.19103,41.7498],[94.80789,41.53428],[93.76556,40.66605],[92.92785,40.58058],[92.40874,39.03625]]]}},{"type":"Feature","properties":{"id":"NL-BQ2"},"geometry":{"type":"Polygon","coordinates":[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.07669,17.79659],[-63.29212,17.90532],[-63.58819,17.61311]]]}},{"type":"Feature","properties":{"id":"IN-DL"},"geometry":{"type":"Polygon","coordinates":[[[76.83915,28.58301],[76.84584,28.55037],[76.86249,28.54487],[76.87391,28.5282],[76.88738,28.51998],[76.88043,28.50543],[76.89116,28.50037],[76.89657,28.50686],[76.90206,28.50671],[76.90635,28.51395],[76.92034,28.50678],[76.95334,28.50509],[76.97845,28.5213],[76.99111,28.51365],[76.9969,28.51949],[77.00982,28.51466],[77.01703,28.52104],[77.01476,28.52398],[77.00098,28.53114],[77.00544,28.53947],[77.0139,28.54068],[77.02424,28.5328],[77.03209,28.53118],[77.04344,28.52513],[77.0434,28.52409],[77.04862,28.52107],[77.0463,28.5167],[77.05226,28.51512],[77.05746,28.51297],[77.06093,28.51225],[77.06428,28.51285],[77.06703,28.51199],[77.07153,28.51787],[77.07235,28.52025],[77.07505,28.51877],[77.08003,28.51815],[77.09265,28.51434],[77.09863,28.51146],[77.09575,28.50713],[77.09775,28.50493],[77.11973,28.4952],[77.11277,28.4731],[77.13076,28.43984],[77.14043,28.43848],[77.14651,28.43692],[77.16161,28.42922],[77.17208,28.40559],[77.17389,28.40446],[77.17818,28.40921],[77.22058,28.41344],[77.24169,28.42763],[77.24628,28.4496],[77.24101,28.45616],[77.23156,28.45609],[77.23483,28.46982],[77.24298,28.47917],[77.26238,28.48762],[77.26991,28.48791],[77.27527,28.49358],[77.28832,28.49673],[77.30053,28.49419],[77.30053,28.49007],[77.30855,28.48823],[77.31413,28.4837],[77.32563,28.49004],[77.34615,28.51651],[77.29886,28.55723],[77.29293,28.57634],[77.29916,28.58772],[77.30422,28.58587],[77.31049,28.59085],[77.31332,28.59661],[77.3246,28.59789],[77.33066,28.60098],[77.33645,28.60174],[77.3419,28.60524],[77.34087,28.62299],[77.31594,28.64152],[77.31988,28.65188],[77.32027,28.66234],[77.32551,28.67827],[77.32997,28.67861],[77.33345,28.68147],[77.32409,28.69864],[77.33207,28.71317],[77.3134,28.71366],[77.29971,28.71008],[77.29628,28.70526],[77.29036,28.70602],[77.2865,28.7143],[77.29083,28.72273],[77.28688,28.7248],[77.27667,28.73564],[77.26057,28.73564],[77.25547,28.73861],[77.25658,28.7444],[77.26006,28.75039],[77.25512,28.7558],[77.24886,28.75524],[77.23839,28.75908],[77.20659,28.78451],[77.20418,28.80527],[77.20985,28.81429],[77.2229,28.82091],[77.21157,28.8573],[77.17457,28.85865],[77.15681,28.8367],[77.14282,28.83858],[77.14616,28.85572],[77.13406,28.86301],[77.12265,28.8573],[77.11063,28.86918],[77.09166,28.87098],[77.08316,28.88315],[77.07939,28.87135],[77.06171,28.86963],[77.04282,28.8355],[76.99398,28.84068],[76.98077,28.82113],[76.97064,28.82783],[76.96146,28.81474],[76.95116,28.81798],[76.94292,28.79955],[76.95433,28.79045],[76.94807,28.78097],[76.95579,28.76841],[76.94403,28.75419],[76.95811,28.7432],[76.96068,28.73161],[76.94832,28.71264],[76.96781,28.69917],[76.95776,28.68448],[76.95502,28.66988],[76.93751,28.66942],[76.92378,28.64999],[76.94601,28.63289],[76.93528,28.61865],[76.91725,28.63395],[76.90738,28.62393],[76.8903,28.63274],[76.86429,28.58602],[76.83915,28.58301]]]}},{"type":"Feature","properties":{"id":"NO-21"},"geometry":{"type":"Polygon","coordinates":[[[-3.52068,82.6752],[16.4353,73.61229],[33.12005,75.46568],[35.22046,80.57056],[-3.52068,82.6752]]]}},{"type":"Feature","properties":{"id":"NO-22"},"geometry":{"type":"Polygon","coordinates":[[[-10.71459,70.09565],[-5.93364,70.76368],[-9.68082,72.73731],[-10.71459,70.09565]]]}},{"type":"Feature","properties":{"id":"IN-GJ"},"geometry":{"type":"Polygon","coordinates":[[[68.11329,23.53945],[68.83233,21.42207],[70.8467,20.44438],[70.87331,20.73203],[71.00154,20.74648],[72.83901,20.48555],[72.90801,20.43087],[72.89291,20.36748],[72.47526,20.38318],[72.4768,20.16425],[72.80021,20.12622],[72.8754,20.22869],[72.9808,20.21323],[72.92346,20.26348],[72.94578,20.35331],[73.12156,20.36909],[73.18508,20.29407],[73.06766,20.21806],[73.06354,20.18487],[73.171,20.19744],[73.23383,20.14266],[73.29048,20.1549],[73.29769,20.20453],[73.4333,20.2055],[73.44978,20.71726],[73.6695,20.56208],[73.7495,20.5624],[73.88854,20.72946],[73.93901,20.73588],[73.91635,20.92232],[73.7325,21.10163],[73.58573,21.15591],[73.74538,21.14279],[73.74315,21.16568],[73.83447,21.19337],[73.83619,21.26953],[73.94897,21.29737],[74.01712,21.42047],[74.05265,21.41983],[74.06776,21.48118],[74.11085,21.44492],[74.26328,21.46265],[74.33332,21.50945],[74.30911,21.5655],[74.21161,21.53101],[74.10724,21.5639],[73.85662,21.49939],[73.79379,21.62041],[73.83636,21.84684],[74.14947,21.95323],[74.17282,22.06846],[74.07909,22.24652],[74.08149,22.35896],[74.27787,22.38373],[74.05677,22.48115],[74.26929,22.64633],[74.36714,22.62858],[74.47769,22.86004],[74.3201,23.0573],[74.24182,23.19244],[73.99017,23.33405],[73.89953,23.33248],[73.83172,23.44749],[73.64135,23.4393],[73.6211,23.66024],[73.35639,23.77591],[73.42231,23.92601],[73.37425,24.13359],[73.2431,24.00319],[73.0783,24.20829],[73.2328,24.36476],[73.00277,24.48777],[72.97084,24.34866],[72.74116,24.35491],[72.69996,24.44527],[72.50083,24.40088],[72.44144,24.49214],[72.18292,24.60894],[71.09405,24.69017],[70.97594,24.60904],[71.00341,24.46038],[71.12838,24.42662],[71.04461,24.34657],[70.94985,24.3791],[70.85784,24.30903],[70.88393,24.27398],[70.71502,24.23517],[70.57906,24.27774],[70.5667,24.43787],[70.11712,24.30915],[70.03428,24.172],[69.73335,24.17007],[69.59579,24.29777],[69.29778,24.28712],[69.19341,24.25646],[69.07806,24.29777],[68.97781,24.26021],[68.90914,24.33156],[68.7416,24.31904],[68.74643,23.97027],[68.39339,23.96838],[68.20763,23.85849],[68.11329,23.53945]]]}},{"type":"Feature","properties":{"id":"SO"},"geometry":{"type":"Polygon","coordinates":[[[40.98767,2.82959],[41.00099,-0.83068],[41.56,-1.59812],[41.56362,-1.66375],[41.75542,-1.85308],[49.16337,2.78611],[52.253,11.68582],[51.12877,12.56479],[48.95249,11.56816],[43.42425,11.70983],[42.95776,10.98533],[42.69452,10.62672],[42.87643,10.18441],[43.0937,9.90579],[43.23518,9.84605],[43.32613,9.59205],[44.19222,8.93028],[46.99339,7.9989],[47.92477,8.00111],[47.97917,8.00124],[44.98104,4.91821],[44.02436,4.9451],[43.40263,4.79289],[43.04177,4.57923],[42.97746,4.44032],[42.84526,4.28357],[42.55853,4.20518],[42.07619,4.17667],[41.89488,3.97375],[41.31368,3.14314],[40.98767,2.82959]]]}},{"type":"Feature","properties":{"id":"CN-SX"},"geometry":{"type":"Polygon","coordinates":[[[110.2272,34.90733],[110.23921,34.62784],[110.41122,34.58432],[110.8905,34.65354],[111.1576,34.81831],[111.22695,34.79294],[111.57508,34.84649],[111.82228,35.07159],[112.03994,35.04517],[112.05505,35.27981],[112.76916,35.20635],[113.02802,35.35937],[113.48945,35.52943],[113.60961,35.67626],[113.57597,35.81614],[113.65287,35.83507],[113.72909,36.36103],[113.59554,36.4616],[113.47297,36.6976],[113.78917,36.88071],[113.74488,37.0738],[114.09851,37.58594],[114.127,37.69387],[114.02984,37.72972],[113.82797,38.16263],[113.55194,38.23871],[113.53683,38.50787],[113.84101,38.76854],[113.75518,38.94499],[113.94676,39.09489],[114.3402,39.07997],[114.5613,39.55276],[114.39376,39.60568],[114.41093,39.83121],[113.89114,40.0192],[114.54482,40.3366],[114.30519,40.36695],[114.28527,40.51327],[114.1246,40.74569],[114.0652,40.67634],[114.07001,40.54093],[113.93783,40.50544],[113.85234,40.44511],[113.6721,40.4425],[113.53443,40.3345],[113.31504,40.31304],[113.24809,40.41349],[112.88761,40.32822],[112.84572,40.20169],[112.73963,40.1626],[112.61947,40.23891],[112.45399,40.29995],[112.30293,40.25463],[112.10758,39.97527],[111.97059,39.78822],[111.91909,39.61468],[111.72615,39.59537],[111.67121,39.62525],[111.53217,39.65698],[111.43295,39.64006],[111.4199,39.50245],[111.36188,39.47489],[111.33235,39.42081],[111.21288,39.42638],[111.12945,39.4025],[111.11881,39.36403],[111.19228,39.30508],[111.23897,39.30003],[111.1576,39.10741],[111.09134,39.02985],[111.04637,39.02158],[110.97495,38.97836],[111.00963,38.90706],[110.95745,38.75836],[110.88775,38.65522],[110.87333,38.45842],[110.80192,38.44713],[110.5149,38.20905],[110.49636,38.02862],[110.58357,37.92578],[110.77377,37.62946],[110.74665,37.45169],[110.39474,36.99816],[110.4895,36.56039],[110.43594,36.1606],[110.61035,35.64948],[110.40572,35.30728],[110.36041,35.139],[110.2272,34.90733]]]}},{"type":"Feature","properties":{"id":"IN-ML"},"geometry":{"type":"Polygon","coordinates":[[[89.81208,25.37244],[89.84086,25.31854],[89.83371,25.29548],[89.87629,25.28337],[89.90478,25.31038],[90.1155,25.22686],[90.40034,25.1534],[90.65042,25.17788],[90.87427,25.15799],[91.25517,25.20677],[91.63648,25.12846],[92.0316,25.1834],[92.33957,25.07593],[92.39147,25.01471],[92.79464,25.21612],[92.6477,25.57465],[92.13958,25.69846],[92.30438,26.06788],[91.94732,25.99755],[91.84432,26.10982],[91.24969,25.71887],[90.95443,25.95001],[90.61729,25.87714],[90.48065,26.0031],[90.10642,25.96113],[89.89906,25.73867],[90.01922,25.60314],[89.89562,25.5635],[89.81208,25.37244]]]}},{"type":"Feature","properties":{"id":"IN-TN"},"geometry":{"type":"Polygon","coordinates":[[[76.23344,11.51871],[76.53934,11.35079],[76.43531,11.19456],[76.7237,11.20736],[76.6492,10.924],[76.81777,10.86163],[76.84112,10.81138],[76.8988,10.77327],[76.85691,10.68051],[76.87236,10.63226],[76.80679,10.63159],[76.82327,10.3237],[76.96403,10.221],[77.21465,10.36423],[77.27645,10.13382],[77.20298,10.11759],[77.25997,10.02971],[77.24693,9.80447],[77.16865,9.61632],[77.42065,9.51543],[77.15045,9.01496],[77.26135,8.843],[77.17655,8.7385],[77.27954,8.52296],[77.20024,8.50734],[77.22358,8.44758],[76.72283,7.82138],[77.66782,7.85769],[79.37385,8.98767],[79.45362,9.159],[79.42124,9.80115],[80.48418,10.20786],[80.42198,10.81981],[79.8088,10.81374],[79.68658,10.99107],[80.41717,10.96613],[80.35262,11.6751],[79.59525,11.86735],[79.70031,12.10378],[80.31898,12.0091],[80.67259,13.46443],[80.32104,13.44372],[80.29632,13.37626],[80.2153,13.48512],[80.02784,13.52718],[79.92484,13.33884],[79.77996,13.21254],[79.53414,13.30944],[79.36832,13.30711],[79.40025,13.142],[79.2152,13.13063],[79.14997,13.00422],[78.84681,13.07613],[78.63292,12.97143],[78.46401,12.61454],[78.2151,12.68991],[78.23295,12.76526],[78.12309,12.76861],[78.08678,12.83146],[78.03322,12.85406],[78.00215,12.80359],[77.97005,12.83105],[77.95349,12.85966],[77.94782,12.8354],[77.91975,12.82828],[77.93683,12.88192],[77.83281,12.86151],[77.79247,12.84092],[77.81032,12.82987],[77.78062,12.76928],[77.79384,12.74768],[77.7662,12.73663],[77.76277,12.72658],[77.77582,12.72273],[77.76329,12.6956],[77.73994,12.69962],[77.74114,12.67065],[77.71196,12.6817],[77.71265,12.66395],[77.67943,12.65625],[77.67514,12.68363],[77.60089,12.66579],[77.58853,12.51803],[77.63523,12.49088],[77.61806,12.36649],[77.48931,12.2766],[77.45738,12.20681],[77.725,12.17963],[77.77925,12.112],[77.72724,12.05409],[77.67917,11.94898],[77.49704,11.9426],[77.46665,11.84887],[77.42906,11.76199],[77.25465,11.81241],[77.12059,11.71863],[76.98772,11.81291],[76.97193,11.77628],[76.91013,11.79308],[76.84249,11.66938],[76.85623,11.59506],[76.61281,11.60717],[76.539,11.69123],[76.42948,11.66568],[76.37145,11.59136],[76.23722,11.58699],[76.23344,11.51871]]]}},{"type":"Feature","properties":{"id":"TJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[67.33226,39.23739],[67.67833,39.14479],[67.68915,39.00775],[68.09704,39.02589],[68.19743,38.85985],[68.06948,38.82115],[68.12877,38.73677],[68.05598,38.71641],[68.0807,38.64136],[68.05873,38.56087],[68.11366,38.47169],[68.06274,38.39435],[68.13289,38.40822],[68.40343,38.19484],[68.27159,37.91477],[68.12635,37.93],[67.81566,37.43107],[67.8474,37.31594],[67.78329,37.1834],[67.7803,37.08978],[67.87917,37.0591],[68.02194,36.91923],[68.18542,37.02074],[68.27605,37.00977],[68.29253,37.10621],[68.41201,37.10402],[68.41888,37.13906],[68.61851,37.19815],[68.6798,37.27906],[68.81438,37.23862],[68.80889,37.32494],[68.91189,37.26704],[68.88168,37.33368],[68.96407,37.32603],[69.03274,37.25174],[69.25152,37.09426],[69.39529,37.16752],[69.45022,37.23315],[69.36645,37.40462],[69.44954,37.4869],[69.51888,37.5844],[69.80041,37.5746],[69.84435,37.60616],[69.93362,37.61378],[69.95971,37.5659],[70.15015,37.52519],[70.28243,37.66706],[70.27694,37.81258],[70.1863,37.84296],[70.17206,37.93276],[70.4898,38.12546],[70.54673,38.24541],[70.60407,38.28046],[70.61526,38.34774],[70.64966,38.34999],[70.69189,38.37031],[70.6761,38.39144],[70.67438,38.40597],[70.69807,38.41861],[70.72485,38.4131],[70.75455,38.4252],[70.77132,38.45548],[70.78581,38.45502],[70.78702,38.45031],[70.79766,38.44944],[70.80521,38.44447],[70.81697,38.44507],[70.82538,38.45394],[70.84376,38.44688],[70.88719,38.46826],[70.92728,38.43021],[70.98693,38.48862],[71.03545,38.44779],[71.0556,38.40176],[71.09542,38.42517],[71.10592,38.42077],[71.10957,38.40671],[71.1451,38.40106],[71.21291,38.32797],[71.33114,38.30339],[71.33869,38.27335],[71.37803,38.25641],[71.36444,38.15358],[71.29878,38.04429],[71.28922,38.01272],[71.27622,37.99946],[71.27278,37.96496],[71.24969,37.93031],[71.2809,37.91995],[71.296,37.93403],[71.32871,37.88564],[71.51565,37.95349],[71.58843,37.92425],[71.59255,37.79956],[71.55752,37.78677],[71.54324,37.77104],[71.53053,37.76534],[71.55234,37.73209],[71.54186,37.69691],[71.51972,37.61945],[71.5065,37.60912],[71.49693,37.53527],[71.50616,37.50733],[71.5256,37.47971],[71.49612,37.4279],[71.47685,37.40281],[71.4862,37.33405],[71.49821,37.31975],[71.50674,37.31502],[71.48536,37.26017],[71.4824,37.24921],[71.48339,37.23937],[71.47386,37.2269],[71.4555,37.21418],[71.4494,37.18137],[71.44127,37.11856],[71.43097,37.05855],[71.45578,37.03094],[71.46923,36.99925],[71.48481,36.93218],[71.51502,36.89128],[71.57195,36.74943],[71.67083,36.67346],[71.83229,36.68084],[72.31676,36.98115],[72.54095,37.00007],[72.66381,37.02014],[72.79693,37.22222],[73.06884,37.31729],[73.29633,37.46495],[73.77197,37.4417],[73.76647,37.33913],[73.61129,37.27469],[73.64974,37.23643],[73.82552,37.22659],[73.8564,37.26158],[74.20308,37.34208],[74.23339,37.41116],[74.41055,37.3948],[74.56161,37.37734],[74.68383,37.3948],[74.8294,37.3435],[74.88887,37.23275],[75.12328,37.31839],[75.09719,37.37297],[75.15899,37.41443],[75.06011,37.52779],[74.94338,37.55501],[74.8912,37.67576],[75.00935,37.77486],[74.92416,37.83428],[74.9063,38.03033],[74.82665,38.07359],[74.80331,38.19889],[74.69894,38.22155],[74.69619,38.42947],[74.51217,38.47034],[74.17022,38.65504],[73.97933,38.52945],[73.79806,38.61106],[73.80656,38.66449],[73.7033,38.84782],[73.7445,38.93867],[73.82964,38.91517],[73.81728,39.04007],[73.75823,39.023],[73.60638,39.24534],[73.54572,39.27567],[73.55396,39.3543],[73.5004,39.38402],[73.59241,39.40843],[73.59831,39.46425],[73.45096,39.46677],[73.31912,39.38615],[73.18454,39.35536],[72.85934,39.35116],[72.62027,39.39696],[72.33173,39.33093],[72.23834,39.17248],[72.17242,39.2661],[72.09689,39.26823],[72.04059,39.36704],[71.90601,39.27674],[71.79202,39.27355],[71.7522,39.32031],[71.80164,39.40631],[71.76816,39.45456],[71.62688,39.44056],[71.5517,39.45722],[71.55856,39.57588],[71.49814,39.61397],[71.08752,39.50704],[71.06418,39.41586],[70.7854,39.38933],[70.64087,39.58792],[70.44757,39.60128],[70.2869,39.53141],[70.11111,39.58223],[69.87491,39.53882],[69.68677,39.59281],[69.3594,39.52516],[69.26938,39.8127],[69.35649,40.01994],[69.43134,39.98431],[69.43557,39.92877],[69.53615,39.93991],[69.5057,40.03277],[69.53855,40.0887],[69.53794,40.11833],[69.55555,40.12296],[69.57615,40.10524],[69.64704,40.12165],[69.67001,40.10639],[70.01283,40.23288],[70.58297,40.00891],[70.57384,39.99394],[70.47557,39.93216],[70.55033,39.96619],[70.58912,39.95211],[70.65946,39.9878],[70.65827,40.0981],[70.7928,40.12797],[70.80495,40.16813],[70.9818,40.22392],[70.8607,40.217],[70.62342,40.17396],[70.56394,40.26421],[70.57149,40.3442],[70.37511,40.38605],[70.32626,40.45174],[70.49871,40.52503],[70.80009,40.72825],[70.45251,41.04438],[70.38028,41.02014],[70.36655,40.90296],[69.69434,40.62615],[69.59441,40.70181],[69.53021,40.77621],[69.38327,40.7918],[69.32834,40.70233],[69.3455,40.57988],[69.2643,40.57506],[69.21063,40.54469],[69.27066,40.49274],[69.28525,40.41894],[69.30774,40.36102],[69.33794,40.34819],[69.32833,40.29794],[69.30808,40.2821],[69.24817,40.30357],[69.25229,40.26362],[69.30104,40.24502],[69.30448,40.18774],[69.2074,40.21488],[69.15659,40.2162],[69.04544,40.22904],[68.85832,40.20885],[68.84357,40.18604],[68.79276,40.17555],[68.77902,40.20492],[68.5332,40.14826],[68.52771,40.11676],[68.62796,40.07789],[69.01523,40.15771],[69.01935,40.11466],[68.96579,40.06949],[68.84906,40.04952],[68.93695,39.91167],[68.88889,39.87163],[68.63071,39.85265],[68.61972,39.68905],[68.54166,39.53929],[68.12053,39.56317],[67.70992,39.66156],[67.62889,39.60234],[67.44899,39.57799],[67.46547,39.53564],[67.39681,39.52505],[67.46822,39.46146],[67.45998,39.315],[67.36522,39.31287],[67.33226,39.23739]]],[[[70.52631,39.86989],[70.54998,39.85137],[70.59667,39.83542],[70.63105,39.77923],[70.74189,39.86319],[70.53651,39.89155],[70.52631,39.86989]]],[[[70.54223,40.98787],[70.57501,40.98941],[70.6721,40.90555],[70.68112,40.90612],[70.6158,40.97661],[70.56077,41.00642],[70.54223,40.98787]]]]}},{"type":"Feature","properties":{"id":"US"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-179.55295,50.81807],[-133.92876,54.62289],[-130.61931,54.70835],[-130.64499,54.76912],[-130.44184,54.85377],[-130.27203,54.97174],[-130.18765,55.07744],[-130.08035,55.21556],[-129.97513,55.28029],[-130.15373,55.74895],[-130.00857,55.91344],[-130.00093,56.00325],[-130.10173,56.12178],[-130.33965,56.10849],[-130.77769,56.36185],[-131.8271,56.62247],[-133.38523,58.42773],[-133.84645,58.73543],[-134.27175,58.8634],[-134.48059,59.13231],[-134.55699,59.1297],[-134.7047,59.2458],[-135.00267,59.28745],[-135.03069,59.56208],[-135.48007,59.79937],[-136.31566,59.59083],[-136.22381,59.55526],[-136.33727,59.44466],[-136.47323,59.46617],[-136.52365,59.16752],[-136.82619,59.16198],[-137.4925,58.89415],[-137.60623,59.24465],[-138.62145,59.76431],[-138.71149,59.90728],[-139.05365,59.99655],[-139.20603,60.08896],[-139.05831,60.35205],[-139.68991,60.33693],[-139.98024,60.18027],[-140.45648,60.30919],[-140.5227,60.22077],[-141.00116,60.30648],[-141.00555,72.20369],[-168.25765,71.99091],[-168.95635,65.98512],[-169.03888,65.48473],[-172.76104,63.77445],[-179.55295,57.62081],[-179.55295,50.81807]]],[[[-179.2458,29.18869],[-179.23433,18.15359],[-154.13058,18.17333],[-154.14205,29.20682],[-176.83456,29.19028],[-177.8563,29.18961],[-179.2458,29.18869]]],[[[-177.43928,1.65656],[-177.43039,-1.43294],[-175.33482,-1.40631],[-175.33167,1.67574],[-177.43928,1.65656]]],[[[-174.18596,-12.48057],[-171.14953,-12.4725],[-171.14262,-14.93704],[-167.73854,-14.92809],[-167.75195,-10.12005],[-174.17993,-10.13616],[-174.18596,-12.48057]]],[[[-169.97341,16.32808],[-169.02347,16.32808],[-169.02347,17.13856],[-169.97341,17.13856],[-169.97341,16.32808]]],[[[-163.24994,7.12322],[-163.24478,5.24198],[-161.06795,5.2462],[-161.0731,7.1291],[-163.24994,7.12322]]],[[[-161.05669,1.11722],[-161.04969,-1.36251],[-158.62058,-1.35506],[-158.62734,1.1296],[-161.05669,1.11722]]],[[[-125.69978,42.00605],[-122.18305,33.57011],[-118.48109,32.5991],[-117.1243,32.53427],[-115.88053,32.63624],[-114.71871,32.71894],[-114.76736,32.64094],[-114.80584,32.62028],[-114.81141,32.55543],[-114.79524,32.55731],[-114.82011,32.49609],[-112.34553,31.7357],[-111.07523,31.33232],[-109.05235,31.3333],[-108.20979,31.33316],[-108.20899,31.78534],[-106.529,31.784],[-106.52266,31.77509],[-106.51251,31.76922],[-106.50962,31.76155],[-106.50111,31.75714],[-106.48815,31.74769],[-106.47298,31.75054],[-106.46726,31.75998],[-106.45244,31.76523],[-106.43419,31.75478],[-106.41773,31.75196],[-106.38003,31.73151],[-106.3718,31.71165],[-106.34864,31.69663],[-106.33419,31.66303],[-106.30305,31.62154],[-106.28084,31.56173],[-106.24612,31.54193],[-106.23711,31.51262],[-106.20346,31.46305],[-106.09025,31.40569],[-106.00363,31.39181],[-104.77674,30.4236],[-104.5171,29.64671],[-104.3969,29.57105],[-104.39363,29.55396],[-104.37752,29.54255],[-103.15787,28.93865],[-102.60596,29.8192],[-101.47277,29.7744],[-101.05686,29.44738],[-101.01128,29.36947],[-100.96725,29.3477],[-100.94579,29.34523],[-100.94056,29.33371],[-100.87982,29.296],[-100.79696,29.24688],[-100.67294,29.09744],[-100.63689,28.90812],[-100.59809,28.88197],[-100.52313,28.75598],[-100.5075,28.74066],[-100.51222,28.70679],[-100.50029,28.66117],[-99.55409,27.61314],[-99.51478,27.55836],[-99.52955,27.49747],[-99.50208,27.50021],[-99.48045,27.49016],[-99.482,27.47128],[-99.49744,27.43746],[-99.53573,27.30926],[-99.08477,26.39849],[-99.03053,26.41249],[-99.00546,26.3925],[-98.35126,26.15129],[-98.30491,26.10475],[-98.27075,26.09457],[-98.24603,26.07191],[-97.97017,26.05232],[-97.95155,26.0625],[-97.66511,26.01708],[-97.52025,25.88518],[-97.49828,25.89877],[-97.45669,25.86874],[-97.42511,25.83969],[-97.37332,25.83854],[-97.35946,25.92189],[-97.13927,25.96583],[-96.92418,25.97377],[-93.77551,29.43998],[-88.93054,28.25639],[-88.37952,30.00457],[-87.51915,30.07055],[-83.18732,24.36791],[-82.02215,24.23074],[-80.16442,23.44484],[-79.36558,27.02964],[-81.34929,30.71298],[-80.49837,32.0326],[-77.99552,33.38485],[-74.86753,35.41538],[-75.79776,36.55091],[-75.16879,38.02735],[-74.98718,38.4507],[-73.81773,39.66512],[-71.6391,40.94332],[-71.10101,41.43444],[-69.97282,40.56828],[-69.42513,41.52748],[-70.04999,42.81005],[-67.16117,44.20069],[-66.93432,44.82597],[-66.96824,44.83078],[-66.98249,44.87071],[-66.96824,44.90965],[-67.0216,44.95333],[-67.11316,45.11176],[-67.15965,45.16179],[-67.19603,45.16771],[-67.20349,45.1722],[-67.22751,45.16344],[-67.27039,45.1934],[-67.29748,45.18173],[-67.29754,45.14865],[-67.34927,45.122],[-67.48201,45.27351],[-67.42394,45.37969],[-67.50578,45.48971],[-67.42144,45.50584],[-67.43815,45.59162],[-67.6049,45.60725],[-67.80705,45.69528],[-67.80653,45.80022],[-67.75654,45.82324],[-67.80961,45.87531],[-67.75196,45.91814],[-67.78111,45.9392],[-67.78578,47.06473],[-67.87993,47.10377],[-67.94843,47.1925],[-68.23244,47.35712],[-68.37458,47.35851],[-68.38332,47.28723],[-68.57914,47.28431],[-68.60575,47.24659],[-68.70125,47.24399],[-68.89222,47.1807],[-69.05039,47.2456],[-69.05073,47.30076],[-69.05148,47.42012],[-69.22119,47.46461],[-69.99966,46.69543],[-70.05812,46.41768],[-70.18547,46.35357],[-70.29078,46.18832],[-70.23855,46.1453],[-70.31025,45.96424],[-70.24694,45.95138],[-70.25976,45.89675],[-70.41523,45.79497],[-70.38934,45.73215],[-70.54019,45.67291],[-70.68516,45.56964],[-70.72651,45.49771],[-70.62518,45.42286],[-70.65383,45.37592],[-70.78372,45.43269],[-70.82638,45.39828],[-70.80236,45.37444],[-70.84816,45.22698],[-70.89864,45.2398],[-70.91169,45.29849],[-70.95193,45.33895],[-71.0107,45.34819],[-71.01866,45.31573],[-71.08364,45.30623],[-71.14568,45.24128],[-71.19723,45.25438],[-71.22338,45.25184],[-71.29371,45.29996],[-71.37133,45.24624],[-71.44252,45.2361],[-71.40364,45.21382],[-71.42778,45.12624],[-71.48735,45.07784],[-71.50067,45.01357],[-73.35025,45.00942],[-74.32699,44.99029],[-74.66689,45.00646],[-74.8447,45.00606],[-74.99101,44.98051],[-75.01363,44.95608],[-75.2193,44.87821],[-75.41441,44.76614],[-75.76813,44.51537],[-75.8217,44.43176],[-75.95947,44.34463],[-76.00018,44.34896],[-76.16285,44.28262],[-76.1664,44.23051],[-76.244,44.19643],[-76.31222,44.19894],[-76.35324,44.13493],[-76.43859,44.09393],[-76.79706,43.63099],[-79.25796,43.54052],[-79.06921,43.26183],[-79.05512,43.25375],[-79.05544,43.21224],[-79.05002,43.20133],[-79.05384,43.17418],[-79.04652,43.16396],[-79.0427,43.13934],[-79.06881,43.12029],[-79.05671,43.10937],[-79.07486,43.07845],[-79.01055,43.06659],[-78.99941,43.05612],[-79.02424,43.01983],[-79.02074,42.98444],[-78.98126,42.97],[-78.96312,42.95509],[-78.93224,42.95229],[-78.90905,42.93022],[-78.90712,42.89733],[-78.93684,42.82887],[-79.77073,42.55308],[-80.53581,42.29896],[-82.67862,41.67615],[-83.11184,41.95671],[-83.14962,42.04089],[-83.12724,42.2376],[-83.09837,42.28877],[-83.07837,42.30978],[-83.02253,42.33045],[-82.82964,42.37355],[-82.64242,42.55594],[-82.58873,42.54984],[-82.57583,42.5718],[-82.51858,42.611],[-82.51063,42.66025],[-82.46613,42.76615],[-82.4826,42.8068],[-82.45331,42.93139],[-82.4253,42.95423],[-82.4146,42.97626],[-82.42469,42.992],[-82.48419,45.30225],[-83.59589,45.82131],[-83.43746,45.99749],[-83.57017,46.105],[-83.83329,46.12169],[-83.90453,46.05922],[-83.95399,46.05634],[-84.1096,46.23987],[-84.09756,46.25512],[-84.11615,46.2681],[-84.11254,46.32329],[-84.13451,46.39218],[-84.11196,46.50248],[-84.12885,46.53068],[-84.17723,46.52753],[-84.1945,46.54061],[-84.2264,46.53337],[-84.26351,46.49508],[-84.29893,46.49127],[-84.34174,46.50683],[-84.42101,46.49853],[-84.4481,46.48972],[-84.47607,46.45225],[-84.55635,46.45974],[-84.85871,46.88881],[-88.37033,48.30586],[-89.48837,48.01412],[-89.57972,48.00023],[-89.77248,48.02607],[-89.89974,47.98109],[-90.07418,48.11043],[-90.56312,48.09488],[-90.56444,48.12184],[-90.75045,48.09143],[-90.87588,48.2484],[-91.08016,48.18096],[-91.25025,48.08522],[-91.43248,48.04912],[-91.45829,48.07454],[-91.58025,48.04339],[-91.55649,48.10611],[-91.70451,48.11805],[-91.71231,48.19875],[-91.86125,48.21278],[-91.98929,48.25409],[-92.05339,48.35958],[-92.14732,48.36578],[-92.202,48.35252],[-92.26662,48.35651],[-92.30939,48.31251],[-92.27167,48.25046],[-92.37185,48.22259],[-92.48147,48.36609],[-92.45588,48.40624],[-92.50712,48.44921],[-92.65606,48.43471],[-92.71323,48.46081],[-92.69927,48.49573],[-92.62747,48.50278],[-92.6342,48.54133],[-92.7287,48.54005],[-92.94973,48.60866],[-93.25391,48.64266],[-93.33946,48.62787],[-93.3712,48.60599],[-93.39758,48.60364],[-93.40693,48.60948],[-93.44472,48.59147],[-93.47022,48.54357],[-93.66382,48.51845],[-93.79267,48.51631],[-93.80939,48.52439],[-93.80676,48.58232],[-93.83288,48.62745],[-93.85769,48.63284],[-94.23215,48.65202],[-94.25104,48.65729],[-94.25172,48.68404],[-94.27153,48.70232],[-94.4174,48.71049],[-94.44258,48.69223],[-94.53826,48.70216],[-94.54885,48.71543],[-94.58903,48.71803],[-94.69335,48.77883],[-94.69669,48.80918],[-94.70486,48.82365],[-94.70087,48.8339],[-94.687,48.84077],[-94.75017,49.09931],[-94.77355,49.11998],[-94.82487,49.29483],[-94.8159,49.32299],[-94.85381,49.32492],[-94.95681,49.37035],[-94.99532,49.36579],[-95.01419,49.35647],[-95.05825,49.35311],[-95.12903,49.37056],[-95.15357,49.384],[-95.15355,48.9996],[-97.24024,48.99952],[-101.36198,48.99935],[-104.05004,48.99925],[-110.0051,48.99901],[-114.0683,48.99885],[-116.04938,48.99999],[-117.03266,49.00056],[-123.32163,49.00419],[-123.0093,48.83186],[-123.0093,48.76586],[-123.26565,48.6959],[-123.15614,48.35395],[-123.50039,48.21223],[-125.03842,48.53282],[-125.2772,46.2631],[-125.69978,42.00605]]],[[[-75.27909,18.17213],[-74.76465,18.06252],[-74.7289,18.71009],[-75.24866,18.6531],[-75.27909,18.17213]]],[[[-68.20301,17.83927],[-65.27974,17.56928],[-64.35558,17.48384],[-64.646,18.10286],[-64.64067,18.36478],[-64.86049,18.39954],[-65.02435,18.73231],[-67.99519,18.97186],[-68.20301,17.83927]]],[[[143.82485,13.92273],[144.61642,12.82462],[146.25931,13.85876],[146.6755,21.00809],[144.18594,21.03576],[143.82485,13.92273]]],[[[166.27257,19.60026],[166.27567,19.02484],[166.94111,19.02804],[166.93801,19.60345],[166.27257,19.60026]]],[[[171.97544,51.06331],[180,51.0171],[180,53.34113],[172.01045,53.385],[171.97544,51.06331]]]]}},{"type":"Feature","properties":{"id":"UZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[55.97584,44.99322],[56.00314,41.32584],[57.03423,41.25435],[57.13796,41.36625],[57.03359,41.41777],[56.96218,41.80383],[57.03633,41.92043],[57.30275,42.14076],[57.6296,42.16519],[57.84932,42.18555],[57.92897,42.24047],[57.90975,42.4374],[57.99214,42.50021],[58.3492,42.43335],[58.40688,42.29535],[58.51674,42.30348],[58.29427,42.56497],[58.14321,42.62159],[58.27504,42.69632],[58.57991,42.64988],[58.6266,42.79314],[58.93422,42.5407],[59.17317,42.52248],[59.2955,42.37064],[59.4341,42.29738],[59.94633,42.27655],[60.00539,42.212],[59.96419,42.1428],[60.04659,42.08982],[60.0356,42.01028],[59.95046,41.97966],[60.33223,41.75058],[60.08504,41.80997],[60.06032,41.76287],[60.18117,41.60082],[60.06581,41.4363],[60.5078,41.21694],[61.03261,41.25691],[61.22212,41.14946],[61.33199,41.14946],[61.39732,41.19873],[61.4446,41.29407],[61.87856,41.12257],[62.11751,40.58242],[62.34273,40.43206],[62.43337,39.98528],[63.6913,39.27666],[63.70778,39.22349],[64.19086,38.95561],[64.32576,38.98691],[65.55873,38.29052],[65.83913,38.25733],[66.24013,38.16238],[66.41042,38.02403],[66.56697,38.0435],[66.67684,37.96776],[66.53676,37.80084],[66.52852,37.58568],[66.65761,37.45497],[66.52303,37.39827],[66.55743,37.35409],[66.64699,37.32958],[66.95598,37.40162],[67.08232,37.35469],[67.13039,37.27168],[67.2224,37.24545],[67.2581,37.17216],[67.51868,37.26102],[67.78329,37.1834],[67.8474,37.31594],[67.81566,37.43107],[68.12635,37.93],[68.27159,37.91477],[68.40343,38.19484],[68.13289,38.40822],[68.06274,38.39435],[68.11366,38.47169],[68.05873,38.56087],[68.0807,38.64136],[68.05598,38.71641],[68.12877,38.73677],[68.06948,38.82115],[68.19743,38.85985],[68.09704,39.02589],[67.68915,39.00775],[67.67833,39.14479],[67.33226,39.23739],[67.36522,39.31287],[67.45998,39.315],[67.46822,39.46146],[67.39681,39.52505],[67.46547,39.53564],[67.44899,39.57799],[67.62889,39.60234],[67.70992,39.66156],[68.12053,39.56317],[68.54166,39.53929],[68.61972,39.68905],[68.63071,39.85265],[68.88889,39.87163],[68.93695,39.91167],[68.84906,40.04952],[68.96579,40.06949],[69.01935,40.11466],[69.01523,40.15771],[68.62796,40.07789],[68.52771,40.11676],[68.5332,40.14826],[68.77902,40.20492],[68.79276,40.17555],[68.84357,40.18604],[68.85832,40.20885],[69.04544,40.22904],[69.15659,40.2162],[69.2074,40.21488],[69.30448,40.18774],[69.30104,40.24502],[69.25229,40.26362],[69.24817,40.30357],[69.30808,40.2821],[69.32833,40.29794],[69.33794,40.34819],[69.30774,40.36102],[69.28525,40.41894],[69.27066,40.49274],[69.21063,40.54469],[69.2643,40.57506],[69.3455,40.57988],[69.32834,40.70233],[69.38327,40.7918],[69.53021,40.77621],[69.59441,40.70181],[69.69434,40.62615],[70.36655,40.90296],[70.38028,41.02014],[70.45251,41.04438],[70.80009,40.72825],[70.49871,40.52503],[70.32626,40.45174],[70.37511,40.38605],[70.57149,40.3442],[70.56394,40.26421],[70.62342,40.17396],[70.8607,40.217],[70.9818,40.22392],[70.95789,40.28761],[71.05901,40.28765],[71.13042,40.34106],[71.36663,40.31593],[71.4246,40.28619],[71.51215,40.26943],[71.51549,40.22986],[71.61725,40.20615],[71.61931,40.26775],[71.68386,40.26984],[71.70569,40.20391],[71.69621,40.18492],[71.71719,40.17886],[71.73054,40.14818],[71.82646,40.21872],[71.85002,40.25647],[72.05464,40.27586],[71.96401,40.31907],[72.18648,40.49893],[72.24368,40.46091],[72.40346,40.4007],[72.44191,40.48222],[72.41513,40.50856],[72.38384,40.51535],[72.41714,40.55736],[72.34406,40.60144],[72.40517,40.61917],[72.47795,40.5532],[72.66713,40.5219],[72.66713,40.59076],[72.69579,40.59778],[72.73995,40.58409],[72.74768,40.58051],[72.74862,40.57131],[72.75982,40.57273],[72.74894,40.59592],[72.74866,40.60873],[72.80137,40.67856],[72.84754,40.67229],[72.85372,40.7116],[72.8722,40.71111],[72.93296,40.73089],[72.99133,40.76457],[73.0612,40.76678],[73.13412,40.79122],[73.13267,40.83512],[73.01869,40.84681],[72.94454,40.8094],[72.84291,40.85512],[72.68157,40.84942],[72.59136,40.86947],[72.55109,40.96046],[72.48742,40.97136],[72.45206,41.03018],[72.38511,41.02785],[72.36138,41.04384],[72.34757,41.06104],[72.34026,41.04539],[72.324,41.03381],[72.18339,40.99571],[72.17594,41.02377],[72.21061,41.05607],[72.1792,41.10621],[72.14864,41.13363],[72.17594,41.15522],[72.16433,41.16483],[72.10745,41.15483],[72.07249,41.11739],[71.85964,41.19081],[71.91457,41.2982],[71.83914,41.3546],[71.76625,41.4466],[71.71132,41.43012],[71.73054,41.54713],[71.65914,41.49599],[71.6787,41.42111],[71.57227,41.29175],[71.46688,41.31883],[71.43814,41.19644],[71.46148,41.13958],[71.40198,41.09436],[71.34877,41.16807],[71.27187,41.11015],[71.25813,41.18796],[71.11806,41.15359],[71.02193,41.19494],[70.9615,41.16393],[70.86263,41.23833],[70.77885,41.24813],[70.78572,41.36419],[70.67586,41.47953],[70.48909,41.40335],[70.17682,41.5455],[70.69777,41.92554],[71.28719,42.18033],[71.13263,42.28356],[70.94483,42.26238],[69.49545,41.545],[69.45751,41.56863],[69.39485,41.51518],[69.45081,41.46246],[69.37468,41.46555],[69.35554,41.47211],[69.29778,41.43673],[69.25059,41.46693],[69.23332,41.45847],[69.22671,41.46298],[69.20439,41.45391],[69.18528,41.45175],[69.17701,41.43769],[69.15137,41.43078],[69.05006,41.36183],[69.01308,41.22804],[68.7217,41.05025],[68.73945,40.96989],[68.65662,40.93861],[68.62221,41.03019],[68.49983,40.99669],[68.58444,40.91447],[68.63,40.59358],[68.49983,40.56437],[67.96736,40.83798],[68.1271,41.0324],[68.08273,41.08148],[67.98511,41.02794],[67.9644,41.14611],[66.69129,41.1311],[66.53302,41.87388],[66.00546,41.94455],[66.09482,42.93426],[65.85194,42.85481],[65.53277,43.31856],[65.18666,43.48835],[64.96464,43.74748],[64.53885,43.56941],[63.34656,43.64003],[62.01711,43.51008],[61.01475,44.41383],[58.59711,45.58671],[55.97842,44.99622],[55.97832,44.99622],[55.97822,44.99617],[55.97811,44.99617],[55.97801,44.99612],[55.97801,44.99607],[55.97791,44.99607],[55.9778,44.99607],[55.9777,44.99601],[55.9777,44.99596],[55.9776,44.99591],[55.97749,44.99591],[55.97739,44.99591],[55.97739,44.99586],[55.97729,44.99586],[55.97718,44.99581],[55.97708,44.99576],[55.97698,44.9957],[55.97698,44.99565],[55.97687,44.9956],[55.97677,44.9956],[55.97677,44.99555],[55.97677,44.9955],[55.97667,44.99545],[55.97656,44.99539],[55.97646,44.99534],[55.97646,44.99529],[55.97636,44.99524],[55.97636,44.99519],[55.97625,44.99514],[55.97615,44.99508],[55.97615,44.99503],[55.97615,44.99498],[55.97615,44.99493],[55.97615,44.99483],[55.97615,44.99477],[55.97605,44.99477],[55.97605,44.99467],[55.97605,44.99462],[55.97605,44.99457],[55.97605,44.99452],[55.97594,44.99446],[55.97584,44.99441],[55.97584,44.99436],[55.97584,44.99431],[55.97584,44.99426],[55.97584,44.99421],[55.97584,44.99415],[55.97584,44.99405],[55.97584,44.994],[55.97584,44.9939],[55.97584,44.99384],[55.97584,44.99374],[55.97584,44.99369],[55.97584,44.99359],[55.97584,44.99353],[55.97584,44.99348],[55.97584,44.99343],[55.97584,44.99338],[55.97584,44.99328],[55.97584,44.99322]],[[70.54223,40.98787],[70.56077,41.00642],[70.6158,40.97661],[70.68112,40.90612],[70.6721,40.90555],[70.57501,40.98941],[70.54223,40.98787]]],[[[71.00236,40.18154],[71.01035,40.05481],[71.11037,40.01984],[71.11668,39.99291],[71.09063,39.99],[71.10501,39.95568],[71.04979,39.89808],[71.10531,39.91354],[71.16101,39.88423],[71.23067,39.93581],[71.1427,39.95026],[71.21139,40.03369],[71.12218,40.03052],[71.06305,40.1771],[71.00236,40.18154]]],[[[71.71511,39.96348],[71.7504,39.93701],[71.84316,39.95582],[71.86463,39.98598],[71.78838,40.01404],[71.71511,39.96348]]]]}},{"type":"Feature","properties":{"id":"ZA"},"geometry":{"type":"Polygon","coordinates":[[[15.70388,-29.23989],[16.22632,-35.03863],[38.88176,-48.03306],[33.10054,-26.92273],[32.89816,-26.8579],[32.35222,-26.86027],[32.29584,-26.852],[32.22302,-26.84136],[32.19409,-26.84032],[32.13315,-26.84345],[32.09664,-26.80721],[32.00893,-26.8096],[31.97463,-27.11057],[31.97592,-27.31675],[31.49834,-27.31549],[31.15027,-27.20151],[30.96088,-27.0245],[30.97757,-26.92706],[30.88826,-26.79622],[30.81101,-26.84722],[30.78927,-26.48271],[30.95819,-26.26303],[31.13073,-25.91558],[31.31237,-25.7431],[31.4175,-25.71886],[31.86881,-25.99973],[31.974,-25.95387],[31.92649,-25.84216],[32.00631,-25.65044],[31.97875,-25.46356],[32.01676,-25.38117],[32.03196,-25.10785],[31.9835,-24.29983],[31.90368,-24.18892],[31.87707,-23.95293],[31.77445,-23.90082],[31.70223,-23.72695],[31.67942,-23.60858],[31.56539,-23.47268],[31.55779,-23.176],[31.30611,-22.422],[31.16344,-22.32599],[31.08932,-22.34884],[30.86696,-22.28907],[30.6294,-22.32599],[30.48686,-22.31368],[30.38614,-22.34533],[30.28351,-22.35587],[30.2265,-22.2961],[30.13147,-22.30841],[29.92242,-22.19408],[29.76848,-22.14128],[29.64609,-22.12917],[29.37703,-22.19581],[29.21955,-22.17771],[29.18974,-22.18599],[29.15268,-22.21399],[29.10881,-22.21202],[29.0151,-22.22907],[28.91889,-22.44299],[28.63287,-22.55887],[28.34874,-22.5694],[28.04562,-22.8394],[28.04752,-22.90243],[27.93729,-22.96194],[27.93539,-23.04941],[27.74154,-23.2137],[27.6066,-23.21894],[27.52393,-23.37952],[27.33768,-23.40917],[26.99749,-23.65486],[26.84165,-24.24885],[26.51667,-24.47219],[26.46346,-24.60358],[26.39409,-24.63468],[25.8515,-24.75727],[25.84295,-24.78661],[25.88571,-24.87802],[25.72702,-25.25503],[25.69661,-25.29284],[25.6643,-25.4491],[25.58543,-25.6343],[25.33076,-25.76616],[25.12266,-25.75931],[25.01718,-25.72507],[24.8946,-25.80723],[24.67319,-25.81749],[24.44703,-25.73021],[24.36531,-25.773],[24.18287,-25.62916],[23.9244,-25.64286],[23.47588,-25.29971],[23.03497,-25.29971],[22.86012,-25.50572],[22.70808,-25.99186],[22.56365,-26.19668],[22.41921,-26.23078],[22.21206,-26.3773],[22.06192,-26.61882],[21.90703,-26.66808],[21.83291,-26.65959],[21.77114,-26.69015],[21.7854,-26.79199],[21.69322,-26.86152],[21.37869,-26.82083],[21.13353,-26.86661],[20.87031,-26.80047],[20.68596,-26.9039],[20.63275,-26.78181],[20.61754,-26.4692],[20.86081,-26.14892],[20.64795,-25.47827],[20.29826,-24.94869],[20.03678,-24.81004],[20.02809,-24.78725],[19.99817,-24.76768],[19.99882,-28.42622],[18.99885,-28.89165],[17.4579,-28.68718],[17.15405,-28.08573],[16.90446,-28.057],[16.59922,-28.53246],[16.46592,-28.57126],[16.45332,-28.63117],[15.70388,-29.23989]],[[27.01016,-29.65439],[27.33464,-29.48161],[27.4358,-29.33465],[27.47254,-29.31968],[27.45125,-29.29708],[27.48679,-29.29349],[27.54258,-29.25575],[27.5158,-29.2261],[27.55974,-29.18954],[27.75458,-28.89839],[27.8907,-28.91612],[27.88933,-28.88156],[27.9392,-28.84864],[27.98675,-28.8787],[28.02503,-28.85991],[28.1317,-28.7293],[28.2348,-28.69471],[28.30518,-28.69531],[28.40612,-28.6215],[28.65091,-28.57025],[28.68043,-28.58744],[29.40524,-29.21246],[29.44883,-29.3772],[29.33204,-29.45598],[29.28545,-29.58456],[29.12553,-29.76266],[29.16548,-29.91706],[28.9338,-30.05072],[28.80222,-30.10579],[28.68627,-30.12885],[28.399,-30.1592],[28.2319,-30.28476],[28.12073,-30.68072],[27.74814,-30.60635],[27.69467,-30.55862],[27.67819,-30.53437],[27.6521,-30.51707],[27.62137,-30.50509],[27.56781,-30.44562],[27.56901,-30.42504],[27.45452,-30.32239],[27.38108,-30.33456],[27.36649,-30.27246],[27.37293,-30.19401],[27.40778,-30.14577],[27.32555,-30.14785],[27.29603,-30.05473],[27.22719,-30.00718],[27.09489,-29.72796],[27.01016,-29.65439]]]}},{"type":"Feature","properties":{"id":"EU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-63.35989,18.06012],[-63.33064,17.9615],[-63.13584,18.0541],[-63.11096,18.05368],[-63.09686,18.04608],[-63.07759,18.04943],[-63.0579,18.06614],[-63.04039,18.05619],[-63.02323,18.05757],[-62.93924,18.02904],[-62.75637,18.13489],[-62.86666,18.19278],[-63.35989,18.06012]]],[[[-62.17275,16.35721],[-61.81728,15.58058],[-61.44899,15.79571],[-60.95725,15.70997],[-60.71337,16.48911],[-61.44461,16.81958],[-61.83929,16.66647],[-62.17275,16.35721]]],[[[-61.51867,14.96709],[-61.26561,14.25664],[-60.5958,14.23076],[-60.69955,15.22234],[-61.51867,14.96709]]],[[[-54.6084,2.32856],[-54.16286,2.10779],[-53.78743,2.34412],[-52.96539,2.1881],[-52.6906,2.37298],[-52.31787,3.17896],[-51.85573,3.83427],[-51.82312,3.85825],[-51.79599,3.89336],[-51.61983,4.14596],[-51.63798,4.51394],[-51.35485,4.8383],[-53.7094,6.2264],[-54.01074,5.68785],[-54.01877,5.52789],[-54.26916,5.26909],[-54.4717,4.91964],[-54.38444,4.13222],[-54.19367,3.84387],[-54.05128,3.63557],[-53.98914,3.627],[-53.9849,3.58697],[-54.28534,2.67798],[-54.42864,2.42442],[-54.6084,2.32856]]],[[[-32.42346,39.07068],[-15.92339,29.50503],[-18.67893,29.62861],[-18.8556,26.96222],[-14.43883,27.02969],[-12.42686,29.61659],[-14.33337,30.94071],[-7.37282,36.96896],[-7.27694,35.93599],[-5.64962,35.93752],[-5.10878,36.05227],[-2.85819,35.63219],[-2.27707,35.35051],[2.46645,37.97429],[5.18061,39.43581],[3.4481,42.4358],[7.52234,41.54558],[7.89009,38.19924],[11.2718,37.6713],[12.02012,35.25036],[12.80065,35.1178],[13.4634,35.88474],[14.74801,35.36688],[15.10171,36.26215],[18.75365,39.82496],[18.83516,40.36999],[16.15283,42.18525],[18.45131,42.21682],[18.54128,42.39171],[18.52152,42.42302],[18.43588,42.48556],[18.44307,42.51077],[18.43735,42.55921],[18.36197,42.61423],[18.24318,42.6112],[17.88201,42.83668],[17.80854,42.9182],[17.7948,42.89556],[17.68151,42.92725],[17.6444,42.88641],[17.5392,42.92787],[17.70879,42.97223],[17.64268,43.08595],[17.46986,43.16559],[17.286,43.33065],[17.25579,43.40353],[17.29699,43.44542],[17.24411,43.49376],[17.15828,43.49376],[17.00585,43.58037],[16.80736,43.76011],[16.75316,43.77157],[16.70922,43.84887],[16.55472,43.95326],[16.50528,44.0244],[16.43629,44.02826],[16.43662,44.07523],[16.36864,44.08263],[16.18688,44.27012],[16.21346,44.35231],[16.12969,44.38275],[16.16814,44.40679],[16.10566,44.52586],[16.03012,44.55572],[16.00884,44.58605],[16.05828,44.61538],[15.89348,44.74964],[15.8255,44.71501],[15.72584,44.82334],[15.79472,44.8455],[15.76096,44.87045],[15.74723,44.96818],[15.78568,44.97401],[15.74585,45.0638],[15.78842,45.11519],[15.76371,45.16508],[15.83512,45.22459],[15.98412,45.23088],[16.12153,45.09616],[16.29036,44.99732],[16.35404,45.00241],[16.35863,45.03529],[16.3749,45.05206],[16.38219,45.05139],[16.38309,45.05955],[16.40023,45.1147],[16.4634,45.14522],[16.49155,45.21153],[16.52982,45.22713],[16.5501,45.2212],[16.56559,45.22307],[16.60194,45.23042],[16.64962,45.20714],[16.74845,45.20393],[16.78219,45.19002],[16.81137,45.18434],[16.83804,45.18951],[16.92405,45.27607],[16.9385,45.22742],[17.0415,45.20759],[17.18438,45.14764],[17.24325,45.146],[17.25131,45.14957],[17.26815,45.18444],[17.32092,45.16246],[17.33573,45.14521],[17.41229,45.13335],[17.4498,45.16119],[17.45615,45.12523],[17.47589,45.12656],[17.51469,45.10791],[17.59104,45.10816],[17.66571,45.13408],[17.84826,45.04489],[17.87148,45.04645],[17.93706,45.08016],[17.97336,45.12245],[17.97834,45.13831],[17.99479,45.14958],[18.01594,45.15163],[18.03121,45.12632],[18.1624,45.07654],[18.24387,45.13699],[18.32077,45.1021],[18.41896,45.11083],[18.47939,45.05871],[18.65723,45.07544],[18.78357,44.97741],[18.80661,44.93561],[18.76369,44.93707],[18.76347,44.90669],[18.8704,44.85097],[19.01994,44.85493],[18.98957,44.90645],[19.02871,44.92541],[19.06853,44.89915],[19.15573,44.95409],[19.05205,44.97692],[19.1011,45.01191],[19.07952,45.14668],[19.14063,45.12972],[19.19144,45.17863],[19.43589,45.17137],[19.41941,45.23475],[19.28208,45.23813],[19.10774,45.29547],[18.97446,45.37528],[18.99918,45.49333],[19.08364,45.48804],[19.07471,45.53086],[18.94562,45.53712],[18.88776,45.57253],[18.96691,45.66731],[18.90305,45.71863],[18.85783,45.85493],[18.81394,45.91329],[18.99712,45.93537],[19.01284,45.96529],[19.0791,45.96458],[19.10388,46.04015],[19.14543,45.9998],[19.28826,45.99694],[19.52473,46.1171],[19.56113,46.16824],[19.66007,46.19005],[19.81491,46.1313],[19.93508,46.17553],[20.01816,46.17696],[20.03533,46.14509],[20.09713,46.17315],[20.26068,46.12332],[20.35862,45.99356],[20.54818,45.89939],[20.65645,45.82801],[20.70069,45.7493],[20.77416,45.75601],[20.78446,45.78522],[20.82364,45.77738],[20.80361,45.65875],[20.76798,45.60969],[20.83321,45.53567],[20.77217,45.49788],[20.86026,45.47295],[20.87948,45.42743],[21.09894,45.30144],[21.17612,45.32566],[21.20392,45.2677],[21.29398,45.24148],[21.48278,45.19557],[21.51299,45.15345],[21.4505,45.04294],[21.35855,45.01941],[21.54938,44.9327],[21.56328,44.89502],[21.48202,44.87199],[21.44013,44.87613],[21.35643,44.86364],[21.38802,44.78133],[21.55007,44.77304],[21.60019,44.75208],[21.61942,44.67059],[21.67504,44.67107],[21.71692,44.65349],[21.7795,44.66165],[21.99364,44.63395],[22.08016,44.49844],[22.13234,44.47444],[22.18315,44.48179],[22.30844,44.6619],[22.45301,44.7194],[22.61917,44.61489],[22.69196,44.61587],[22.76749,44.54446],[22.70981,44.51852],[22.61368,44.55719],[22.56493,44.53419],[22.54021,44.47836],[22.45436,44.47258],[22.56012,44.30712],[22.68166,44.28206],[22.67173,44.21564],[22.61711,44.16938],[22.61688,44.06534],[22.41449,44.00514],[22.35558,43.81281],[22.41043,43.69566],[22.47582,43.6558],[22.53397,43.47225],[22.82036,43.33665],[22.89727,43.22417],[23.00806,43.19279],[22.98104,43.11199],[22.89521,43.03625],[22.78397,42.98253],[22.74826,42.88701],[22.54302,42.87774],[22.43309,42.82057],[22.4997,42.74144],[22.43983,42.56851],[22.55669,42.50144],[22.51961,42.3991],[22.47498,42.3915],[22.45919,42.33822],[22.34773,42.31725],[22.38136,42.30339],[22.47251,42.20393],[22.50289,42.19527],[22.51224,42.15457],[22.67701,42.06614],[22.86749,42.02275],[22.90254,41.87587],[22.96682,41.77137],[23.01239,41.76527],[23.03342,41.71034],[22.95513,41.63265],[22.96331,41.35782],[22.93334,41.34104],[22.81199,41.3398],[22.76408,41.32225],[22.74538,41.16321],[22.71266,41.13945],[22.65306,41.18168],[22.62852,41.14385],[22.58295,41.11568],[22.5549,41.13065],[22.42285,41.11921],[22.26744,41.16409],[22.17629,41.15969],[22.1424,41.12449],[22.06527,41.15617],[21.90869,41.09191],[21.91102,41.04786],[21.7556,40.92525],[21.69601,40.9429],[21.57448,40.86076],[21.53007,40.90759],[21.41555,40.9173],[21.35595,40.87578],[21.25779,40.86165],[21.21105,40.8855],[21.15262,40.85546],[20.97887,40.85475],[20.98396,40.79109],[20.95752,40.76982],[20.98134,40.76046],[21.05833,40.66586],[21.03932,40.56299],[20.96908,40.51526],[20.94925,40.46625],[20.83688,40.47882],[20.7906,40.42726],[20.78234,40.35803],[20.71789,40.27739],[20.67162,40.09433],[20.62566,40.0897],[20.61081,40.07866],[20.55593,40.06524],[20.51297,40.08168],[20.48487,40.06271],[20.42373,40.06777],[20.37911,39.99058],[20.31135,39.99438],[20.41546,39.82832],[20.41475,39.81437],[20.38572,39.78516],[20.30804,39.81563],[20.29152,39.80421],[20.31961,39.72799],[20.27412,39.69884],[20.22707,39.67459],[20.22376,39.64532],[20.15988,39.652],[20.12956,39.65805],[20.05189,39.69112],[20.00957,39.69227],[19.98042,39.6504],[19.92466,39.69533],[19.97622,39.78684],[19.95905,39.82857],[19.0384,40.35325],[19.20409,39.7532],[22.5213,33.45682],[29.73302,35.92555],[29.69611,36.10365],[29.61805,36.14179],[29.61002,36.1731],[29.48192,36.18377],[29.30783,36.01033],[28.23708,36.56812],[27.95037,36.46155],[27.89482,36.69898],[27.46117,36.53789],[27.24613,36.71622],[27.45627,36.9008],[27.20312,36.94571],[27.14757,37.32],[26.95583,37.64989],[26.99377,37.69034],[27.16428,37.72343],[27.05537,37.9131],[26.21136,38.17558],[26.24183,38.44695],[26.32173,38.48731],[26.21136,38.65436],[26.61814,38.81372],[26.70773,39.0312],[26.43357,39.43096],[25.94257,39.39358],[25.61285,40.17161],[26.04292,40.3958],[25.94795,40.72797],[26.03489,40.73051],[26.0754,40.72772],[26.08638,40.73214],[26.12495,40.74283],[26.12854,40.77339],[26.15685,40.80709],[26.21351,40.83298],[26.20856,40.86048],[26.26169,40.9168],[26.29441,40.89119],[26.28623,40.93005],[26.32259,40.94042],[26.35894,40.94292],[26.33297,40.98388],[26.3606,41.02027],[26.31928,41.07386],[26.32259,41.24929],[26.39861,41.25053],[26.5209,41.33993],[26.5837,41.32131],[26.62997,41.34613],[26.61767,41.42281],[26.59742,41.48058],[26.59196,41.60491],[26.5209,41.62592],[26.47958,41.67037],[26.35957,41.71149],[26.32952,41.73637],[26.33589,41.76802],[26.36952,41.82265],[26.53968,41.82653],[26.57961,41.90024],[26.56051,41.92995],[26.62996,41.97644],[26.79143,41.97386],[26.95638,42.00741],[27.03277,42.0809],[27.08486,42.08735],[27.19251,42.06028],[27.22376,42.10152],[27.27411,42.10409],[27.45478,41.96591],[27.52379,41.93756],[27.55191,41.90928],[27.69949,41.97515],[27.81235,41.94803],[27.83492,41.99709],[27.91479,41.97902],[28.02971,41.98066],[28.32297,41.98371],[29.24336,43.70874],[30.04414,45.08461],[29.69272,45.19227],[29.65428,45.25629],[29.68175,45.26885],[29.59798,45.38857],[29.42632,45.44545],[29.24779,45.43388],[28.96077,45.33164],[28.94292,45.28045],[28.81383,45.3384],[28.78911,45.24179],[28.71358,45.22631],[28.5735,45.24759],[28.34554,45.32102],[28.28504,45.43907],[28.21139,45.46895],[28.18741,45.47358],[28.08927,45.6051],[28.16568,45.6421],[28.13111,45.92819],[28.08612,46.01105],[28.13684,46.18099],[28.10937,46.22852],[28.19864,46.31869],[28.18902,46.35283],[28.25769,46.43334],[28.22281,46.50481],[28.24808,46.64305],[28.12173,46.82283],[28.09095,46.97621],[27.81892,47.1381],[27.73172,47.29248],[27.68706,47.28962],[27.60263,47.32507],[27.55731,47.46637],[27.47942,47.48113],[27.3979,47.59473],[27.32202,47.64009],[27.25519,47.71366],[27.29069,47.73722],[27.1618,47.92391],[27.15622,47.98538],[27.02985,48.09083],[27.04118,48.12522],[26.96119,48.13003],[26.98042,48.15752],[26.94265,48.1969],[26.87708,48.19919],[26.81161,48.25049],[26.62823,48.25804],[26.55202,48.22445],[26.33504,48.18418],[26.17711,47.99246],[26.05901,47.9897],[25.77723,47.93919],[25.63878,47.94924],[25.23778,47.89403],[25.11144,47.75203],[24.88896,47.7234],[24.81893,47.82031],[24.70632,47.84428],[24.61994,47.95062],[24.43578,47.97131],[24.34926,47.9244],[24.22566,47.90231],[24.11281,47.91487],[24.06466,47.95317],[24.02999,47.95087],[24.00801,47.968],[23.98553,47.96076],[23.96337,47.96672],[23.94192,47.94868],[23.89352,47.94512],[23.8602,47.9329],[23.80904,47.98142],[23.75188,47.99705],[23.66262,47.98786],[23.63894,48.00293],[23.5653,48.00499],[23.52803,48.01818],[23.4979,47.96858],[23.33577,48.0237],[23.27397,48.08245],[23.15999,48.12188],[23.1133,48.08061],[23.08858,48.00716],[23.0158,47.99338],[22.92241,48.02002],[22.94301,47.96672],[22.89849,47.95851],[22.84276,47.98602],[22.87847,48.04665],[22.81804,48.11363],[22.73427,48.12005],[22.66835,48.09162],[22.58733,48.10813],[22.59007,48.15121],[22.49806,48.25189],[22.38133,48.23726],[22.2083,48.42534],[22.14689,48.4005],[22.16023,48.56548],[22.21379,48.6218],[22.34151,48.68893],[22.42934,48.92857],[22.48296,48.99172],[22.54338,49.01424],[22.56155,49.08865],[22.89122,49.00725],[22.86336,49.10513],[22.72009,49.20288],[22.748,49.32759],[22.69444,49.49378],[22.64534,49.53094],[22.78304,49.65543],[22.80261,49.69098],[22.83179,49.69875],[22.99329,49.84249],[23.28221,50.0957],[23.67635,50.33385],[23.71382,50.38248],[23.79445,50.40481],[23.99563,50.41289],[24.03668,50.44507],[24.07048,50.5071],[24.0996,50.60752],[24.0595,50.71625],[23.95925,50.79271],[23.99254,50.83847],[24.0952,50.83262],[24.14524,50.86128],[24.04576,50.90196],[23.92217,51.00836],[23.90376,51.07697],[23.80678,51.18405],[23.63858,51.32182],[23.69905,51.40871],[23.62751,51.50512],[23.56236,51.53673],[23.57053,51.55938],[23.53198,51.74298],[23.62691,51.78208],[23.61523,51.92066],[23.68733,51.9906],[23.64066,52.07626],[23.61,52.11264],[23.54314,52.12148],[23.47859,52.18215],[23.20071,52.22848],[23.18196,52.28812],[23.34141,52.44845],[23.45112,52.53774],[23.58296,52.59868],[23.73615,52.6149],[23.93763,52.71332],[23.91805,52.94016],[23.94689,52.95919],[23.92184,53.02079],[23.87548,53.0831],[23.91393,53.16469],[23.85657,53.22923],[23.81995,53.24131],[23.62004,53.60942],[23.51284,53.95052],[23.61677,53.92691],[23.71726,53.93379],[23.80543,53.89558],[23.81309,53.94205],[23.95098,53.9613],[23.98837,53.92554],[24.19638,53.96405],[24.34128,53.90076],[24.44411,53.90076],[24.62275,54.00217],[24.69652,54.01901],[24.69185,53.96543],[24.74279,53.96663],[24.85311,54.02862],[24.77131,54.11091],[24.96894,54.17589],[24.991,54.14241],[25.0728,54.13419],[25.19199,54.219],[25.22705,54.26271],[25.35559,54.26544],[25.509,54.30267],[25.56823,54.25212],[25.51452,54.17799],[25.54724,54.14925],[25.64875,54.1259],[25.71084,54.16704],[25.78563,54.15747],[25.78553,54.23327],[25.68513,54.31727],[25.55425,54.31591],[25.5376,54.33158],[25.63371,54.42075],[25.62203,54.4656],[25.64813,54.48704],[25.68045,54.5321],[25.75977,54.57252],[25.74122,54.80108],[25.89462,54.93438],[25.99129,54.95705],[26.05907,54.94631],[26.13386,54.98924],[26.20397,54.99729],[26.26941,55.08032],[26.23202,55.10439],[26.30628,55.12536],[26.35121,55.1525],[26.46249,55.12814],[26.51481,55.16051],[26.54753,55.14181],[26.69243,55.16718],[26.68075,55.19787],[26.72983,55.21788],[26.73017,55.24226],[26.835,55.28182],[26.83266,55.30444],[26.80929,55.31642],[26.6714,55.33902],[26.5709,55.32572],[26.44937,55.34832],[26.5522,55.40277],[26.55094,55.5093],[26.63167,55.57887],[26.63231,55.67968],[26.64888,55.70515],[26.71802,55.70645],[26.76872,55.67658],[26.87448,55.7172],[26.97153,55.8102],[27.1559,55.85032],[27.27804,55.78299],[27.3541,55.8089],[27.61683,55.78558],[27.63065,55.89687],[27.97865,56.11849],[28.15217,56.16964],[28.23716,56.27588],[28.16599,56.37806],[28.19057,56.44637],[28.10069,56.524],[28.13526,56.57989],[28.04768,56.59004],[27.86101,56.88204],[27.66511,56.83921],[27.86101,57.29402],[27.52453,57.42826],[27.56832,57.53728],[27.34698,57.52242],[27.31919,57.57672],[27.40393,57.62125],[27.3746,57.66834],[27.52615,57.72843],[27.50171,57.78842],[27.56689,57.83356],[27.78526,57.83963],[27.81841,57.89244],[27.67282,57.92627],[27.62393,58.09462],[27.48541,58.22615],[27.55489,58.39525],[27.36366,58.78381],[27.74429,58.98351],[27.80482,59.1116],[27.87978,59.18097],[27.90911,59.24353],[28.00689,59.28351],[28.14215,59.28934],[28.19284,59.35791],[28.20537,59.36491],[28.21137,59.38058],[28.19061,59.39962],[28.04187,59.47017],[27.85643,59.58538],[26.90044,59.63819],[26.32936,60.00121],[27.44953,60.22766],[27.71177,60.3893],[27.77352,60.52722],[28.47974,60.93365],[28.82816,61.1233],[29.01829,61.17448],[31.10136,62.43042],[31.38369,62.66284],[31.58535,62.91642],[31.29294,63.09035],[31.23244,63.22239],[30.49637,63.46666],[29.98213,63.75795],[30.25437,63.83364],[30.55687,64.09036],[30.4762,64.25728],[30.06279,64.35782],[30.01238,64.57513],[30.12329,64.64862],[30.05271,64.79072],[29.68972,64.80789],[29.61914,65.05993],[29.84096,65.1109],[29.8813,65.22101],[29.61914,65.23791],[29.68972,65.31803],[29.84096,65.56945],[29.74013,65.64025],[29.97205,65.70256],[30.16363,65.66935],[29.91155,66.13863],[28.9839,66.94139],[29.91155,67.51507],[30.02041,67.67523],[29.66955,67.79872],[29.34179,68.06655],[28.62982,68.19816],[28.43941,68.53366],[28.78224,68.86696],[28.45957,68.91417],[28.91738,69.04774],[28.81248,69.11997],[28.8629,69.22395],[29.31664,69.47994],[29.12697,69.69193],[28.36883,69.81658],[28.32849,69.88605],[27.97558,69.99671],[27.95542,70.0965],[27.57226,70.06215],[27.05802,69.92069],[26.64461,69.96565],[26.40261,69.91377],[25.96904,69.68397],[25.69679,69.27039],[25.75729,68.99383],[25.61613,68.89602],[25.42455,68.90328],[25.12206,68.78684],[25.10189,68.63307],[24.93048,68.61102],[24.90023,68.55579],[24.74898,68.65143],[24.18432,68.73936],[24.02299,68.81601],[23.781,68.84514],[23.68017,68.70276],[23.13064,68.64684],[22.53321,68.74393],[22.38367,68.71561],[22.27276,68.89514],[21.63833,69.27485],[21.27827,69.31281],[21.00732,69.22755],[20.98641,69.18809],[21.11099,69.10291],[21.05775,69.0356],[20.72171,69.11874],[20.55258,69.06069],[20.0695,69.04469],[20.28444,68.93283],[20.33435,68.80174],[20.22027,68.67246],[19.95647,68.55546],[20.22027,68.48759],[19.93508,68.35911],[18.97255,68.52416],[18.63032,68.50849],[18.39503,68.58672],[18.1241,68.53721],[18.13836,68.20874],[17.90787,67.96537],[17.30416,68.11591],[16.7409,67.91037],[16.38441,67.52923],[16.12774,67.52106],[16.09922,67.4364],[16.39154,67.21653],[16.35589,67.06419],[15.37197,66.48217],[15.49318,66.28509],[15.05113,66.15572],[14.53778,66.12399],[14.50926,65.31786],[13.64276,64.58402],[14.11117,64.46674],[14.16051,64.18725],[13.98222,64.00953],[13.23411,64.09087],[12.74105,64.02171],[12.14928,63.59373],[12.19919,63.47935],[11.98529,63.27487],[12.19919,63.00104],[12.07085,62.6297],[12.29187,62.25699],[12.14746,61.7147],[12.40595,61.57226],[12.57707,61.56547],[12.86939,61.35427],[12.69115,61.06584],[12.2277,61.02442],[12.59133,60.50559],[12.52003,60.13846],[12.36317,59.99259],[12.15641,59.8926],[11.87121,59.86039],[11.92112,59.69531],[11.69297,59.59442],[11.8213,59.24985],[11.65732,58.90177],[11.45199,58.89604],[11.4601,58.99022],[11.34459,59.11672],[11.15367,59.07862],[11.08911,58.98745],[10.64958,58.89391],[10.40861,58.38489],[7.28637,57.35913],[8.02459,55.09613],[5.45168,54.20039],[2.56575,51.85301],[2.18458,51.52087],[1.17405,50.74239],[-2.02963,49.91866],[-2.09454,49.46288],[-1.83944,49.23037],[-2.00491,48.86706],[-2.5234,48.91595],[-2.56423,49.22209],[-2.9511,49.31141],[-5.81385,48.52441],[-1.81005,43.59738],[-10.14298,44.17365],[-9.14112,41.86623],[-30.18705,41.4962],[-32.42346,39.07068]],[[-5.40134,36.14896],[-5.38545,36.15481],[-5.36494,36.15496],[-5.34536,36.15501],[-5.33822,36.15272],[-5.27801,36.14942],[-5.28217,36.09907],[-5.3004,36.07439],[-5.32837,36.05935],[-5.36503,36.06205],[-5.39074,36.10278],[-5.40134,36.14896]],[[1.41245,42.53539],[1.4234,42.55959],[1.44529,42.56722],[1.42512,42.58292],[1.44197,42.60217],[1.47986,42.61346],[1.46718,42.63296],[1.48043,42.65203],[1.50867,42.64483],[1.55418,42.65669],[1.60085,42.62703],[1.63485,42.62957],[1.6625,42.61982],[1.68267,42.62533],[1.73452,42.61515],[1.72588,42.59098],[1.7858,42.57698],[1.73683,42.55492],[1.72515,42.50338],[1.70571,42.48867],[1.66826,42.50779],[1.65674,42.47125],[1.58933,42.46275],[1.57953,42.44957],[1.55937,42.45808],[1.55073,42.43299],[1.5127,42.42959],[1.44529,42.43724],[1.43838,42.47848],[1.41648,42.48315],[1.46661,42.50949],[1.44759,42.54431],[1.41245,42.53539]],[[5.95781,46.12925],[5.9641,46.14412],[5.97508,46.15863],[5.98188,46.17392],[5.98846,46.17046],[5.99573,46.18587],[5.96515,46.19638],[5.97542,46.21525],[6.02461,46.23313],[6.03342,46.2383],[6.04602,46.23127],[6.05029,46.23518],[6.0633,46.24583],[6.07072,46.24085],[6.08563,46.24651],[6.10071,46.23772],[6.12446,46.25059],[6.11926,46.2634],[6.1013,46.28512],[6.11697,46.29547],[6.1198,46.31157],[6.13876,46.33844],[6.15738,46.3491],[6.16987,46.36759],[6.15985,46.37721],[6.15016,46.3778],[6.09926,46.40768],[6.06407,46.41676],[6.08427,46.44305],[6.07269,46.46244],[6.1567,46.54402],[6.11084,46.57649],[6.27135,46.68251],[6.38351,46.73171],[6.45209,46.77502],[6.43216,46.80336],[6.46456,46.88865],[6.43341,46.92703],[6.71531,47.0494],[6.68823,47.06616],[6.76788,47.1208],[6.8489,47.15933],[6.9508,47.24338],[6.95108,47.26428],[6.94316,47.28747],[7.05305,47.33304],[7.0564,47.35134],[7.03125,47.36996],[6.87959,47.35335],[6.88542,47.37262],[6.93744,47.40714],[6.93953,47.43388],[7.0024,47.45264],[6.98425,47.49432],[7.0231,47.50522],[7.07425,47.48863],[7.12781,47.50371],[7.16249,47.49025],[7.19583,47.49455],[7.17026,47.44312],[7.24669,47.4205],[7.33526,47.44186],[7.35603,47.43432],[7.40308,47.43638],[7.43088,47.45846],[7.4462,47.46264],[7.4583,47.47216],[7.42923,47.48628],[7.43356,47.49712],[7.47534,47.47932],[7.51076,47.49651],[7.49804,47.51798],[7.5229,47.51644],[7.53199,47.5284],[7.51904,47.53515],[7.50588,47.52856],[7.49691,47.53821],[7.50873,47.54546],[7.51723,47.54578],[7.52831,47.55347],[7.53634,47.55553],[7.55652,47.56779],[7.55689,47.57232],[7.56548,47.57617],[7.56684,47.57785],[7.58386,47.57536],[7.58945,47.59017],[7.60523,47.58519],[7.60459,47.57869],[7.61929,47.57683],[7.64309,47.59151],[7.64213,47.5944],[7.64599,47.59695],[7.67395,47.59212],[7.68229,47.59905],[7.69385,47.60099],[7.68486,47.59601],[7.67115,47.5871],[7.68904,47.57133],[7.67655,47.56435],[7.63338,47.56256],[7.65083,47.54662],[7.66174,47.54554],[7.6656,47.53752],[7.68101,47.53232],[7.69642,47.53297],[7.71961,47.54219],[7.75261,47.54599],[7.79486,47.55691],[7.80098,47.56335],[7.811279,47.56946],[7.81901,47.58798],[7.833466,47.58663],[7.84167,47.58196],[7.862692,47.58808],[7.88664,47.58854],[7.898354,47.58408],[7.90673,47.57674],[7.911615,47.56686],[7.906809,47.56037],[7.91251,47.55031],[7.920585,47.5469],[7.932644,47.54704],[7.94614,47.5436],[7.95682,47.55789],[7.97581,47.55493],[8.00113,47.55616],[8.02136,47.55096],[8.04383,47.55443],[8.06663,47.56374],[8.08557,47.55768],[8.10002,47.56504],[8.10395,47.57918],[8.11543,47.5841],[8.13662,47.58432],[8.13823,47.59147],[8.14947,47.59558],[8.1652,47.5945],[8.19378,47.61636],[8.20617,47.62141],[8.22011,47.6181],[8.22577,47.60385],[8.23809,47.61204],[8.25863,47.61571],[8.26313,47.6103],[8.2824,47.61225],[8.29722,47.60603],[8.29524,47.5919],[8.30277,47.58607],[8.32735,47.57133],[8.38273,47.56608],[8.39477,47.57826],[8.43235,47.56617],[8.49431,47.58107],[8.48949,47.588],[8.46637,47.58389],[8.45578,47.60121],[8.50747,47.61897],[8.51686,47.63476],[8.55756,47.62394],[8.57586,47.59537],[8.60348,47.61204],[8.59545,47.64298],[8.60701,47.65271],[8.61471,47.64514],[8.60412,47.63735],[8.62049,47.63757],[8.62884,47.65098],[8.61113,47.66332],[8.6052,47.67258],[8.57683,47.66158],[8.56141,47.67088],[8.52801,47.66059],[8.5322,47.64687],[8.49656,47.64709],[8.46605,47.64103],[8.4667,47.65747],[8.44711,47.65379],[8.42264,47.66667],[8.41346,47.66676],[8.40473,47.67499],[8.4211,47.68407],[8.40569,47.69855],[8.44807,47.72426],[8.45771,47.7493],[8.48868,47.77215],[8.56814,47.78001],[8.56415,47.80633],[8.61657,47.79998],[8.62408,47.7626],[8.64425,47.76398],[8.65292,47.80066],[8.68022,47.78599],[8.68985,47.75686],[8.71778,47.76571],[8.74251,47.75168],[8.70543,47.73121],[8.73671,47.7169],[8.72617,47.69651],[8.72809,47.69282],[8.75856,47.68969],[8.79511,47.67462],[8.79966,47.70222],[8.76965,47.7075],[8.77309,47.72059],[8.80663,47.73821],[8.82002,47.71458],[8.86989,47.70504],[8.85065,47.68209],[8.87383,47.67045],[8.87625,47.65441],[8.89946,47.64769],[8.94093,47.65596],[9.02093,47.6868],[9.09891,47.67801],[9.13845,47.66389],[9.15181,47.66904],[9.1705,47.65513],[9.1755,47.65584],[9.17593,47.65399],[9.18203,47.65598],[9.25619,47.65939],[9.55125,47.53629],[9.56312,47.49495],[9.58208,47.48344],[9.59482,47.46305],[9.60205,47.46165],[9.60484,47.46358],[9.60841,47.47178],[9.62158,47.45858],[9.62475,47.45685],[9.6423,47.45599],[9.65728,47.45383],[9.65863,47.44847],[9.64483,47.43842],[9.6446,47.43233],[9.65043,47.41937],[9.65136,47.40504],[9.6629,47.39591],[9.67334,47.39191],[9.67445,47.38429],[9.6711,47.37824],[9.66243,47.37136],[9.65427,47.36824],[9.62476,47.36639],[9.59978,47.34671],[9.58513,47.31334],[9.55857,47.29919],[9.54773,47.2809],[9.53116,47.27029],[9.56766,47.24281],[9.55176,47.22585],[9.56981,47.21926],[9.58264,47.20673],[9.56539,47.17124],[9.62623,47.14685],[9.63395,47.08443],[9.61216,47.07732],[9.60717,47.06091],[9.87935,47.01337],[9.88266,46.93343],[9.98058,46.91434],[10.10715,46.84296],[10.22675,46.86942],[10.24128,46.93147],[10.30031,46.92093],[10.36933,47.00212],[10.48376,46.93891],[10.47197,46.85698],[10.38659,46.67847],[10.40475,46.63671],[10.44686,46.64162],[10.49375,46.62049],[10.46136,46.53164],[10.25309,46.57432],[10.23674,46.63484],[10.10307,46.61003],[10.03715,46.44479],[10.165,46.41051],[10.10506,46.3372],[10.17862,46.25626],[10.14439,46.22992],[10.07055,46.21668],[9.95249,46.38045],[9.73086,46.35071],[9.71273,46.29266],[9.57015,46.2958],[9.46117,46.37481],[9.45936,46.50873],[9.40487,46.46621],[9.36128,46.5081],[9.28136,46.49685],[9.25502,46.43743],[9.29226,46.32717],[9.24503,46.23616],[9.01618,46.04928],[8.99257,45.9698],[9.09065,45.89906],[9.06642,45.8761],[9.04546,45.84968],[9.04059,45.8464],[9.03505,45.83976],[9.03793,45.83548],[9.03279,45.82865],[9.0298,45.82127],[9.00324,45.82055],[8.99663,45.83466],[8.9621,45.83707],[8.94737,45.84285],[8.91129,45.8388],[8.93504,45.86245],[8.94372,45.86587],[8.93649,45.86775],[8.88904,45.95465],[8.86688,45.96135],[8.85121,45.97239],[8.8319,45.9879],[8.79362,45.99207],[8.78585,45.98973],[8.79414,46.00913],[8.85617,46.0748],[8.80778,46.10085],[8.75697,46.10395],[8.62242,46.12112],[8.45032,46.26869],[8.46317,46.43712],[8.42464,46.46367],[8.30648,46.41587],[8.31162,46.38044],[8.08814,46.26692],[8.16866,46.17817],[8.11383,46.11577],[8.02906,46.10331],[7.98881,45.99867],[7.9049,45.99945],[7.85949,45.91485],[7.56343,45.97421],[7.10685,45.85653],[7.04151,45.92435],[7.00946,45.9944],[6.93862,46.06502],[6.87868,46.03855],[6.89321,46.12548],[6.78968,46.14058],[6.86052,46.28512],[6.77152,46.34784],[6.8024,46.39171],[6.82312,46.42661],[6.53358,46.45431],[6.25432,46.3632],[6.21981,46.31304],[6.24826,46.30175],[6.25137,46.29014],[6.23775,46.27822],[6.24952,46.26255],[6.26749,46.24745],[6.29474,46.26221],[6.31041,46.24417],[6.29663,46.22688],[6.27694,46.21566],[6.26007,46.21165],[6.24821,46.20531],[6.23913,46.20511],[6.23544,46.20714],[6.22175,46.20045],[6.22222,46.19888],[6.21844,46.19837],[6.21603,46.19507],[6.21273,46.19409],[6.21114,46.1927],[6.20539,46.19163],[6.19807,46.18369],[6.19552,46.18401],[6.18707,46.17999],[6.18871,46.16644],[6.18116,46.16187],[6.15305,46.15194],[6.13397,46.1406],[6.09926,46.14373],[6.09199,46.15191],[6.07491,46.14879],[6.05203,46.15191],[6.04564,46.14031],[6.03614,46.13712],[6.01791,46.14228],[5.9871,46.14499],[5.97893,46.13303],[5.95781,46.12925]],[[7.40903,43.7296],[7.41113,43.73156],[7.41291,43.73168],[7.41298,43.73311],[7.41233,43.73439],[7.42062,43.73977],[7.42299,43.74176],[7.42443,43.74087],[7.42809,43.74396],[7.43013,43.74895],[7.43624,43.75014],[7.43708,43.75197],[7.4389,43.75151],[7.4379,43.74963],[7.45448,43.7432],[7.53358,43.53609],[7.50102,43.51859],[7.41855,43.72479],[7.40903,43.7296]],[[12.40415,43.95485],[12.41414,43.95273],[12.42005,43.9578],[12.43662,43.95698],[12.44684,43.96597],[12.46205,43.97463],[12.47853,43.98052],[12.49406,43.98492],[12.50678,43.99113],[12.51463,43.99122],[12.5154,43.98508],[12.51064,43.98165],[12.51109,43.97201],[12.50622,43.97131],[12.50875,43.96198],[12.50655,43.95796],[12.51427,43.94897],[12.51553,43.94096],[12.50496,43.93017],[12.50269,43.92363],[12.49724,43.92248],[12.49247,43.91774],[12.49429,43.90973],[12.48771,43.89706],[12.45648,43.89369],[12.44184,43.90498],[12.41641,43.89991],[12.40935,43.9024],[12.41233,43.90956],[12.40733,43.92379],[12.41551,43.92984],[12.41165,43.93769],[12.40506,43.94325],[12.40415,43.95485]],[[12.44582,41.90194],[12.44815,41.90326],[12.44984,41.90545],[12.45091,41.90625],[12.45543,41.90738],[12.45561,41.90629],[12.45762,41.9058],[12.45755,41.9033],[12.45826,41.90281],[12.45834,41.90174],[12.4577,41.90115],[12.45691,41.90125],[12.45626,41.90172],[12.45435,41.90143],[12.45446,41.90028],[12.45181,41.90056],[12.44834,41.90095],[12.44582,41.90194]],[[18.57853,55.25302],[20.60454,55.40986],[20.95181,55.27994],[21.26425,55.24456],[21.35465,55.28427],[21.38446,55.29348],[21.46766,55.21115],[21.51095,55.18507],[21.55605,55.20311],[21.64954,55.1791],[21.85521,55.09493],[21.96505,55.07353],[21.99543,55.08691],[22.03984,55.07888],[22.02582,55.05078],[22.06087,55.02935],[22.11697,55.02131],[22.14267,55.05345],[22.31562,55.0655],[22.47688,55.04408],[22.58907,55.07085],[22.60075,55.01863],[22.65451,54.97037],[22.68723,54.9811],[22.76422,54.92521],[22.85083,54.88711],[22.87317,54.79492],[22.73631,54.72952],[22.73397,54.66604],[22.75467,54.6483],[22.74225,54.64339],[22.7522,54.63525],[22.68021,54.58486],[22.71293,54.56454],[22.67788,54.532],[22.70208,54.45312],[22.7253,54.41732],[22.79705,54.36264],[21.41123,54.32395],[20.63871,54.3706],[19.8038,54.44203],[19.64312,54.45423],[18.57853,55.25302]]],[[[-13.3292,54.24486],[-10.17712,50.85267],[-5.79914,52.03902],[-5.37267,53.63269],[-5.83481,53.87749],[-6.26218,54.09785],[-6.29003,54.11278],[-6.32694,54.09337],[-6.36279,54.11248],[-6.36605,54.07234],[-6.47849,54.06947],[-6.62842,54.03503],[-6.66264,54.0666],[-6.6382,54.17071],[-6.70175,54.20218],[-6.74575,54.18788],[-6.81583,54.22791],[-6.85179,54.29176],[-6.87775,54.34682],[-7.02034,54.4212],[-7.19145,54.31296],[-7.14908,54.22732],[-7.25012,54.20063],[-7.26316,54.13863],[-7.29493,54.12013],[-7.29687,54.1354],[-7.28017,54.16714],[-7.29157,54.17191],[-7.34005,54.14698],[-7.30553,54.11869],[-7.32834,54.11475],[-7.44567,54.1539],[-7.4799,54.12239],[-7.55812,54.12239],[-7.69501,54.20731],[-7.81397,54.20159],[-7.8596,54.21779],[-7.87101,54.29299],[-8.04555,54.36292],[-8.179,54.46763],[-8.04538,54.48941],[-7.99812,54.54427],[-7.8596,54.53671],[-7.70315,54.62077],[-7.93293,54.66603],[-7.83352,54.73854],[-7.75041,54.7103],[-7.64449,54.75265],[-7.54671,54.74606],[-7.54508,54.79401],[-7.47626,54.83084],[-7.4473,54.87003],[-7.44404,54.9403],[-7.40004,54.94498],[-7.4033,55.00391],[-7.34464,55.04688],[-7.2471,55.06933],[-6.9734,55.19878],[-6.71944,55.27952],[-6.79943,55.54107],[-7.93366,55.84142],[-13.3292,54.24486]]],[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]],[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]],[[[8.65769,47.68928],[8.66837,47.68437],[8.68985,47.69552],[8.70847,47.68904],[8.71773,47.69088],[8.70237,47.71453],[8.66416,47.71367],[8.67508,47.6979],[8.65769,47.68928]]],[[[8.95861,45.96485],[8.97604,45.96151],[8.97741,45.98317],[8.96668,45.98436],[8.95861,45.96485]]],[[[31.71872,35.1606],[32.10341,34.37973],[32.74412,34.43926],[32.75515,34.64985],[32.76136,34.68318],[32.79433,34.67883],[32.82717,34.70622],[32.86014,34.70585],[32.86167,34.68734],[32.9068,34.66102],[32.91398,34.67343],[32.93043,34.67091],[32.92807,34.66736],[32.93449,34.66241],[32.93693,34.67027],[32.94379,34.67111],[32.94683,34.67907],[32.95539,34.68471],[32.99135,34.68061],[32.98668,34.67268],[32.99014,34.65518],[32.97736,34.65277],[32.97079,34.66112],[32.95325,34.66462],[32.94796,34.6587],[32.94976,34.65204],[32.95471,34.64528],[32.95323,34.64075],[32.95891,34.62919],[32.96718,34.63446],[32.96968,34.64046],[33.0138,34.64424],[33.26744,34.49942],[33.83531,34.73974],[33.70575,34.97947],[33.70639,34.99303],[33.71514,35.00294],[33.69731,35.01754],[33.69938,35.03123],[33.67678,35.03866],[33.67742,35.05963],[33.68474,35.06602],[33.69095,35.06237],[33.70861,35.07644],[33.7161,35.07279],[33.70209,35.04882],[33.71482,35.03722],[33.73824,35.05321],[33.76106,35.04253],[33.78581,35.05104],[33.82067,35.07826],[33.84168,35.06823],[33.8541,35.07201],[33.87479,35.08881],[33.87097,35.09389],[33.87622,35.10457],[33.87224,35.12293],[33.88561,35.12449],[33.88943,35.12007],[33.88737,35.11408],[33.89853,35.11377],[33.91789,35.08688],[33.91299,35.07579],[33.90247,35.07686],[33.89485,35.06873],[33.88367,35.07877],[33.85261,35.0574],[33.8355,35.05777],[33.82051,35.0667],[33.8012,35.04786],[33.81524,35.04192],[33.83055,35.02865],[33.82875,35.01685],[33.84045,35.00616],[33.85216,35.00579],[33.85891,35.001],[33.85621,34.98956],[33.83505,34.98108],[33.84811,34.97075],[33.86432,34.97592],[33.90075,34.96623],[33.98684,34.76642],[35.48515,34.70851],[35.51152,36.10954],[32.82353,35.70297],[31.71872,35.1606]]],[[[33.7343,35.01178],[33.74144,35.01053],[33.7492,35.01319],[33.74983,35.02274],[33.74265,35.02329],[33.73781,35.02181],[33.7343,35.01178]]],[[[33.75682,34.99916],[33.76605,34.99543],[33.76738,34.99188],[33.7778,34.98981],[33.77843,34.988],[33.78149,34.98854],[33.78318,34.98699],[33.78571,34.98951],[33.78917,34.98854],[33.79191,34.98914],[33.78516,34.99582],[33.77553,34.99518],[33.77312,34.9976],[33.75994,35.00113],[33.75682,34.99916]]],[[[44.75722,-12.58368],[44.82644,-13.30845],[45.54824,-13.22353],[45.45962,-12.30345],[44.75722,-12.58368]]],[[[54.32269,-20.37973],[54.43368,-22.02482],[56.73473,-21.9174],[56.62373,-20.2711],[54.32269,-20.37973]]]]}},{"type":"Feature","properties":{"id":"UM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-177.8563,29.18961],[-177.84531,27.68616],[-176.81808,27.68129],[-176.83456,29.19028],[-177.8563,29.18961]]],[[[-177.43928,1.65656],[-177.43039,-1.43294],[-175.33482,-1.40631],[-175.33167,1.67574],[-177.43928,1.65656]]],[[[-169.97341,16.32808],[-169.02347,16.32808],[-169.02347,17.13856],[-169.97341,17.13856],[-169.97341,16.32808]]],[[[-163.24994,7.12322],[-163.24478,5.24198],[-161.06795,5.2462],[-161.0731,7.1291],[-163.24994,7.12322]]],[[[-161.05669,1.11722],[-161.04969,-1.36251],[-158.62058,-1.35506],[-158.62734,1.1296],[-161.05669,1.11722]]],[[[-75.27909,18.17213],[-74.76465,18.06252],[-74.7289,18.71009],[-75.24866,18.6531],[-75.27909,18.17213]]],[[[166.27257,19.60026],[166.27567,19.02484],[166.94111,19.02804],[166.93801,19.60345],[166.27257,19.60026]]]]}},{"type":"Feature","properties":{"id":"PS"},"geometry":{"type":"MultiPolygon","coordinates":[[[[34.052,31.46619],[34.21853,31.32363],[34.23572,31.2966],[34.24012,31.29591],[34.26742,31.21998],[34.29417,31.24194],[34.36523,31.28963],[34.37381,31.30598],[34.36505,31.36404],[34.40077,31.40926],[34.48892,31.48365],[34.56797,31.54197],[34.48681,31.59711],[34.29262,31.70393],[34.052,31.46619]]],[[[34.87833,31.39321],[34.88932,31.37093],[34.92571,31.34337],[35.02459,31.35979],[35.13033,31.3551],[35.22921,31.37445],[35.39675,31.49572],[35.47672,31.49578],[35.55941,31.76535],[35.52758,31.9131],[35.54375,31.96587],[35.52012,32.04076],[35.57111,32.21877],[35.55807,32.38674],[35.42078,32.41562],[35.41048,32.43706],[35.41598,32.45593],[35.42034,32.46009],[35.40224,32.50136],[35.35212,32.52047],[35.30685,32.51024],[35.29306,32.50947],[35.25049,32.52453],[35.2244,32.55289],[35.15937,32.50466],[35.10882,32.4757],[35.10024,32.47856],[35.09236,32.47614],[35.08564,32.46948],[35.07059,32.4585],[35.05423,32.41754],[35.05311,32.4024],[35.0421,32.38242],[35.05142,32.3667],[35.04243,32.35008],[35.01772,32.33863],[35.01119,32.28684],[35.02939,32.2671],[35.01841,32.23981],[34.98885,32.20758],[34.95703,32.19522],[34.96009,32.17503],[34.99039,32.14626],[34.98507,32.12606],[34.99437,32.10962],[34.9863,32.09551],[35.00261,32.027],[34.98682,31.96935],[35.00124,31.93264],[35.03489,31.92448],[35.03978,31.89276],[35.03489,31.85919],[34.99712,31.85569],[34.9724,31.83352],[35.01978,31.82944],[35.05617,31.85685],[35.07677,31.85627],[35.14174,31.81325],[35.18603,31.80901],[35.18169,31.82542],[35.19461,31.82687],[35.21469,31.81835],[35.216,31.83894],[35.21128,31.863],[35.20381,31.86716],[35.20673,31.88151],[35.20791,31.8821],[35.20945,31.8815],[35.21016,31.88237],[35.21276,31.88153],[35.2136,31.88241],[35.22014,31.88264],[35.22294,31.87889],[35.22567,31.86745],[35.22817,31.8638],[35.2249,31.85433],[35.2304,31.84222],[35.24816,31.8458],[35.25753,31.8387],[35.251,31.83085],[35.26404,31.82567],[35.25573,31.81362],[35.26058,31.79064],[35.25225,31.7678],[35.26319,31.74846],[35.25182,31.73945],[35.24981,31.72543],[35.2438,31.7201],[35.24315,31.71244],[35.23972,31.70896],[35.22392,31.71899],[35.21937,31.71578],[35.20538,31.72388],[35.18023,31.72067],[35.16478,31.73242],[35.15474,31.73352],[35.15119,31.73634],[35.13931,31.73012],[35.12933,31.7325],[35.11895,31.71454],[35.10782,31.71594],[35.08226,31.69107],[35.00879,31.65426],[34.95249,31.59813],[34.9415,31.55601],[34.94356,31.50743],[34.93258,31.47816],[34.89756,31.43891],[34.87833,31.39321]]]]}},{"type":"Feature","properties":{"id":"TF"},"geometry":{"type":"MultiPolygon","coordinates":[[[[39.10324,-21.48967],[40.40841,-23.17181],[43.72277,-16.09877],[41.06663,-17.08802],[39.10324,-21.48967]]],[[[46.31615,-46.28749],[70.67507,-51.14192],[80.15867,-36.04977],[46.31615,-46.28749]]],[[[46.52682,-10.83678],[47.29063,-12.45583],[48.86266,-10.8109],[46.52682,-10.83678]]],[[[54.08717,-15.5001],[54.13761,-16.33002],[54.96649,-16.28353],[54.91606,-15.45342],[54.08717,-15.5001]]]]}},{"type":"Feature","properties":{"id":"EA"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]],[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]]]}},{"type":"Feature","properties":{"id":"MA"},"geometry":{"type":"Polygon","coordinates":[[[-17.27295,21.93519],[-17.21511,21.34226],[-17.02707,21.34022],[-16.9978,21.36239],[-16.44269,21.39745],[-14.78487,21.36587],[-14.47329,21.63839],[-14.48112,22.00886],[-14.1291,22.41636],[-14.10361,22.75501],[-13.75627,23.77231],[-13.00628,24.01923],[-12.92147,24.39502],[-12.12281,25.13682],[-12.06001,26.04442],[-11.62052,26.05229],[-11.38635,26.611],[-11.23622,26.72023],[-11.35695,26.8505],[-10.68417,26.90984],[-9.81998,26.71379],[-9.56957,26.90042],[-9.08698,26.98639],[-8.71787,26.9898],[-8.77527,27.66663],[-8.66879,27.6666],[-8.6715,28.71194],[-7.61585,29.36252],[-6.95824,29.50924],[-6.78351,29.44634],[-6.69965,29.51623],[-5.75616,29.61407],[-5.72121,29.52322],[-5.58831,29.48103],[-5.21671,29.95253],[-4.6058,30.28343],[-4.31774,30.53229],[-3.64735,30.67539],[-3.65418,30.85566],[-3.54944,31.0503],[-3.77103,31.14984],[-3.77647,31.31912],[-3.66386,31.39202],[-3.66314,31.6339],[-2.82784,31.79459],[-2.93873,32.06557],[-2.46166,32.16603],[-1.22829,32.07832],[-1.15735,32.12096],[-1.24453,32.1917],[-1.24998,32.32993],[-0.9912,32.52467],[-1.37794,32.73628],[-1.54244,32.95499],[-1.46249,33.0499],[-1.67067,33.27084],[-1.59508,33.59929],[-1.73494,33.71721],[-1.64666,34.10405],[-1.78042,34.39018],[-1.69788,34.48056],[-1.84569,34.61907],[-1.73707,34.74226],[-1.97469,34.886],[-1.97833,34.93218],[-2.04734,34.93218],[-2.21445,35.04378],[-2.21248,35.08532],[-2.27707,35.35051],[-2.85819,35.63219],[-5.10878,36.05227],[-5.64962,35.93752],[-7.27694,35.93599],[-12.42686,29.61659],[-14.43883,27.02969],[-17.27295,21.93519]],[[-5.38491,35.92591],[-5.27635,35.91222],[-5.27056,35.88794],[-5.34379,35.8711],[-5.35844,35.87375],[-5.37338,35.88417],[-5.38491,35.92591]],[[-4.30191,35.17419],[-4.29436,35.17149],[-4.30112,35.17058],[-4.30191,35.17419]],[[-3.90602,35.21494],[-3.90288,35.22024],[-3.88617,35.21406],[-3.88926,35.20841],[-3.90602,35.21494]],[[-2.97035,35.28852],[-2.96978,35.29459],[-2.96648,35.30475],[-2.96038,35.31609],[-2.92224,35.3401],[-2.92181,35.28599],[-2.92674,35.27313],[-2.93893,35.26737],[-2.95065,35.26576],[-2.95431,35.2728],[-2.96516,35.27967],[-2.96826,35.28296],[-2.96507,35.28801],[-2.97035,35.28852]],[[-2.44896,35.18777],[-2.41265,35.1877],[-2.41312,35.17111],[-2.44887,35.17075],[-2.44896,35.18777]]]}},{"type":"Feature","properties":{"id":"AU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[71.08716,-53.87687],[75.44182,-53.99822],[72.87012,-51.48322],[71.08716,-53.87687]]],[[[96.53724,-12.46709],[97.24513,-12.47233],[97.25212,-11.57036],[96.54423,-11.5651],[96.53724,-12.46709]]],[[[99.6403,-26.70736],[129,-43.08851],[137.66184,-47.26522],[158.89388,-55.66823],[159.92772,-54.25538],[152.57175,-39.16128],[155.3142,-27.34698],[159.35793,-33.24807],[165.46901,-28.32101],[169.35326,-30.60259],[169.6687,-29.09191],[166.75333,-27.25416],[158.4748,-21.86428],[157.46481,-18.93777],[158.60851,-15.7108],[155.22803,-12.9001],[144.30183,-9.48146],[142.81927,-9.31709],[142.5723,-9.35994],[142.31447,-9.24611],[142.23304,-9.19253],[142.1462,-9.19923],[142.0953,-9.23534],[142.0601,-9.56571],[140.88922,-9.34945],[139.41724,-8.97063],[127.55165,-9.05052],[125.68138,-9.85176],[122.91521,-11.65621],[99.6403,-26.70736]]],[[[105.29647,-10.80676],[105.9699,-10.81333],[105.97626,-10.18257],[105.30283,-10.176],[105.29647,-10.80676]]]]}},{"type":"Feature","properties":{"id":"CH"},"geometry":{"type":"Polygon","coordinates":[[[5.95781,46.12925],[5.97893,46.13303],[5.9871,46.14499],[6.01791,46.14228],[6.03614,46.13712],[6.04564,46.14031],[6.05203,46.15191],[6.07491,46.14879],[6.09199,46.15191],[6.09926,46.14373],[6.13397,46.1406],[6.15305,46.15194],[6.18116,46.16187],[6.18871,46.16644],[6.18707,46.17999],[6.19552,46.18401],[6.19807,46.18369],[6.20539,46.19163],[6.21114,46.1927],[6.21273,46.19409],[6.21603,46.19507],[6.21844,46.19837],[6.22222,46.19888],[6.22175,46.20045],[6.23544,46.20714],[6.23913,46.20511],[6.24821,46.20531],[6.26007,46.21165],[6.27694,46.21566],[6.29663,46.22688],[6.31041,46.24417],[6.29474,46.26221],[6.26749,46.24745],[6.24952,46.26255],[6.23775,46.27822],[6.25137,46.29014],[6.24826,46.30175],[6.21981,46.31304],[6.25432,46.3632],[6.53358,46.45431],[6.82312,46.42661],[6.8024,46.39171],[6.77152,46.34784],[6.86052,46.28512],[6.78968,46.14058],[6.89321,46.12548],[6.87868,46.03855],[6.93862,46.06502],[7.00946,45.9944],[7.04151,45.92435],[7.10685,45.85653],[7.56343,45.97421],[7.85949,45.91485],[7.9049,45.99945],[7.98881,45.99867],[8.02906,46.10331],[8.11383,46.11577],[8.16866,46.17817],[8.08814,46.26692],[8.31162,46.38044],[8.30648,46.41587],[8.42464,46.46367],[8.46317,46.43712],[8.45032,46.26869],[8.62242,46.12112],[8.75697,46.10395],[8.80778,46.10085],[8.85617,46.0748],[8.79414,46.00913],[8.78585,45.98973],[8.79362,45.99207],[8.8319,45.9879],[8.85121,45.97239],[8.86688,45.96135],[8.88904,45.95465],[8.93649,45.86775],[8.94372,45.86587],[8.93504,45.86245],[8.91129,45.8388],[8.94737,45.84285],[8.9621,45.83707],[8.99663,45.83466],[9.00324,45.82055],[9.0298,45.82127],[9.03279,45.82865],[9.03793,45.83548],[9.03505,45.83976],[9.04059,45.8464],[9.04546,45.84968],[9.06642,45.8761],[9.09065,45.89906],[8.99257,45.9698],[9.01618,46.04928],[9.24503,46.23616],[9.29226,46.32717],[9.25502,46.43743],[9.28136,46.49685],[9.36128,46.5081],[9.40487,46.46621],[9.45936,46.50873],[9.46117,46.37481],[9.57015,46.2958],[9.71273,46.29266],[9.73086,46.35071],[9.95249,46.38045],[10.07055,46.21668],[10.14439,46.22992],[10.17862,46.25626],[10.10506,46.3372],[10.165,46.41051],[10.03715,46.44479],[10.10307,46.61003],[10.23674,46.63484],[10.25309,46.57432],[10.46136,46.53164],[10.49375,46.62049],[10.44686,46.64162],[10.40475,46.63671],[10.38659,46.67847],[10.47197,46.85698],[10.48376,46.93891],[10.36933,47.00212],[10.30031,46.92093],[10.24128,46.93147],[10.22675,46.86942],[10.10715,46.84296],[9.98058,46.91434],[9.88266,46.93343],[9.87935,47.01337],[9.60717,47.06091],[9.55721,47.04762],[9.54041,47.06495],[9.47548,47.05257],[9.47139,47.06402],[9.51362,47.08505],[9.52089,47.10019],[9.51044,47.13727],[9.48774,47.17402],[9.4891,47.19346],[9.50318,47.22153],[9.52406,47.24959],[9.53116,47.27029],[9.54773,47.2809],[9.55857,47.29919],[9.58513,47.31334],[9.59978,47.34671],[9.62476,47.36639],[9.65427,47.36824],[9.66243,47.37136],[9.6711,47.37824],[9.67445,47.38429],[9.67334,47.39191],[9.6629,47.39591],[9.65136,47.40504],[9.65043,47.41937],[9.6446,47.43233],[9.64483,47.43842],[9.65863,47.44847],[9.65728,47.45383],[9.6423,47.45599],[9.62475,47.45685],[9.62158,47.45858],[9.60841,47.47178],[9.60484,47.46358],[9.60205,47.46165],[9.59482,47.46305],[9.58208,47.48344],[9.56312,47.49495],[9.55125,47.53629],[9.25619,47.65939],[9.18203,47.65598],[9.17593,47.65399],[9.1755,47.65584],[9.1705,47.65513],[9.15181,47.66904],[9.13845,47.66389],[9.09891,47.67801],[9.02093,47.6868],[8.94093,47.65596],[8.89946,47.64769],[8.87625,47.65441],[8.87383,47.67045],[8.85065,47.68209],[8.86989,47.70504],[8.82002,47.71458],[8.80663,47.73821],[8.77309,47.72059],[8.76965,47.7075],[8.79966,47.70222],[8.79511,47.67462],[8.75856,47.68969],[8.72809,47.69282],[8.72617,47.69651],[8.73671,47.7169],[8.70543,47.73121],[8.74251,47.75168],[8.71778,47.76571],[8.68985,47.75686],[8.68022,47.78599],[8.65292,47.80066],[8.64425,47.76398],[8.62408,47.7626],[8.61657,47.79998],[8.56415,47.80633],[8.56814,47.78001],[8.48868,47.77215],[8.45771,47.7493],[8.44807,47.72426],[8.40569,47.69855],[8.4211,47.68407],[8.40473,47.67499],[8.41346,47.66676],[8.42264,47.66667],[8.44711,47.65379],[8.4667,47.65747],[8.46605,47.64103],[8.49656,47.64709],[8.5322,47.64687],[8.52801,47.66059],[8.56141,47.67088],[8.57683,47.66158],[8.6052,47.67258],[8.61113,47.66332],[8.62884,47.65098],[8.62049,47.63757],[8.60412,47.63735],[8.61471,47.64514],[8.60701,47.65271],[8.59545,47.64298],[8.60348,47.61204],[8.57586,47.59537],[8.55756,47.62394],[8.51686,47.63476],[8.50747,47.61897],[8.45578,47.60121],[8.46637,47.58389],[8.48949,47.588],[8.49431,47.58107],[8.43235,47.56617],[8.39477,47.57826],[8.38273,47.56608],[8.32735,47.57133],[8.30277,47.58607],[8.29524,47.5919],[8.29722,47.60603],[8.2824,47.61225],[8.26313,47.6103],[8.25863,47.61571],[8.23809,47.61204],[8.22577,47.60385],[8.22011,47.6181],[8.20617,47.62141],[8.19378,47.61636],[8.1652,47.5945],[8.14947,47.59558],[8.13823,47.59147],[8.13662,47.58432],[8.11543,47.5841],[8.10395,47.57918],[8.10002,47.56504],[8.08557,47.55768],[8.06663,47.56374],[8.04383,47.55443],[8.02136,47.55096],[8.00113,47.55616],[7.97581,47.55493],[7.95682,47.55789],[7.94614,47.5436],[7.932644,47.54704],[7.920585,47.5469],[7.91251,47.55031],[7.906809,47.56037],[7.911615,47.56686],[7.90673,47.57674],[7.898354,47.58408],[7.88664,47.58854],[7.862692,47.58808],[7.84167,47.58196],[7.833466,47.58663],[7.81901,47.58798],[7.811279,47.56946],[7.80098,47.56335],[7.79486,47.55691],[7.75261,47.54599],[7.71961,47.54219],[7.69642,47.53297],[7.68101,47.53232],[7.6656,47.53752],[7.66174,47.54554],[7.65083,47.54662],[7.63338,47.56256],[7.67655,47.56435],[7.68904,47.57133],[7.67115,47.5871],[7.68486,47.59601],[7.69385,47.60099],[7.68229,47.59905],[7.67395,47.59212],[7.64599,47.59695],[7.64213,47.5944],[7.64309,47.59151],[7.61929,47.57683],[7.60459,47.57869],[7.60523,47.58519],[7.58945,47.59017],[7.58386,47.57536],[7.56684,47.57785],[7.56548,47.57617],[7.55689,47.57232],[7.55652,47.56779],[7.53634,47.55553],[7.52831,47.55347],[7.51723,47.54578],[7.50873,47.54546],[7.49691,47.53821],[7.50588,47.52856],[7.51904,47.53515],[7.53199,47.5284],[7.5229,47.51644],[7.49804,47.51798],[7.51076,47.49651],[7.47534,47.47932],[7.43356,47.49712],[7.42923,47.48628],[7.4583,47.47216],[7.4462,47.46264],[7.43088,47.45846],[7.40308,47.43638],[7.35603,47.43432],[7.33526,47.44186],[7.24669,47.4205],[7.17026,47.44312],[7.19583,47.49455],[7.16249,47.49025],[7.12781,47.50371],[7.07425,47.48863],[7.0231,47.50522],[6.98425,47.49432],[7.0024,47.45264],[6.93953,47.43388],[6.93744,47.40714],[6.88542,47.37262],[6.87959,47.35335],[7.03125,47.36996],[7.0564,47.35134],[7.05305,47.33304],[6.94316,47.28747],[6.95108,47.26428],[6.9508,47.24338],[6.8489,47.15933],[6.76788,47.1208],[6.68823,47.06616],[6.71531,47.0494],[6.43341,46.92703],[6.46456,46.88865],[6.43216,46.80336],[6.45209,46.77502],[6.38351,46.73171],[6.27135,46.68251],[6.11084,46.57649],[6.1567,46.54402],[6.07269,46.46244],[6.08427,46.44305],[6.06407,46.41676],[6.09926,46.40768],[6.15016,46.3778],[6.15985,46.37721],[6.16987,46.36759],[6.15738,46.3491],[6.13876,46.33844],[6.1198,46.31157],[6.11697,46.29547],[6.1013,46.28512],[6.11926,46.2634],[6.12446,46.25059],[6.10071,46.23772],[6.08563,46.24651],[6.07072,46.24085],[6.0633,46.24583],[6.05029,46.23518],[6.04602,46.23127],[6.03342,46.2383],[6.02461,46.23313],[5.97542,46.21525],[5.96515,46.19638],[5.99573,46.18587],[5.98846,46.17046],[5.98188,46.17392],[5.97508,46.15863],[5.9641,46.14412],[5.95781,46.12925]],[[8.65769,47.68928],[8.67508,47.6979],[8.66416,47.71367],[8.70237,47.71453],[8.71773,47.69088],[8.70847,47.68904],[8.68985,47.69552],[8.66837,47.68437],[8.65769,47.68928]],[[8.95861,45.96485],[8.96668,45.98436],[8.97741,45.98317],[8.97604,45.96151],[8.95861,45.96485]]]}},{"type":"Feature","properties":{"id":"DE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[5.45168,54.20039],[6.91025,53.44221],[7.00198,53.32672],[7.19052,53.31866],[7.21679,53.20058],[7.22681,53.18165],[7.17898,53.13817],[7.21694,53.00742],[7.07253,52.81083],[7.04557,52.63318],[6.77307,52.65375],[6.71641,52.62905],[6.69507,52.488],[6.94293,52.43597],[6.99041,52.47235],[7.03417,52.40237],[7.07044,52.37805],[7.02703,52.27941],[7.06365,52.23789],[7.03729,52.22695],[6.9897,52.2271],[6.97189,52.20329],[6.83984,52.11728],[6.76117,52.11895],[6.68128,52.05052],[6.83035,51.9905],[6.82357,51.96711],[6.72319,51.89518],[6.68386,51.91861],[6.58556,51.89386],[6.50231,51.86313],[6.47179,51.85395],[6.38815,51.87257],[6.40704,51.82771],[6.30593,51.84998],[6.29872,51.86801],[6.21443,51.86801],[6.15349,51.90439],[6.11551,51.89769],[6.16902,51.84094],[6.10337,51.84829],[6.06705,51.86136],[5.99848,51.83195],[5.94568,51.82786],[5.98665,51.76944],[5.95003,51.7493],[6.04091,51.71821],[6.02767,51.6742],[6.11759,51.65609],[6.09055,51.60564],[6.18017,51.54096],[6.21724,51.48568],[6.20654,51.40049],[6.22641,51.39948],[6.22674,51.36135],[6.16977,51.33169],[6.07889,51.24432],[6.07889,51.17038],[6.17384,51.19589],[6.16706,51.15677],[5.98292,51.07469],[5.9541,51.03496],[5.9134,51.06736],[5.86735,51.05182],[5.87849,51.01969],[5.90493,51.00198],[5.90296,50.97356],[5.95282,50.98728],[6.02697,50.98303],[6.01615,50.93367],[6.09297,50.92066],[6.07486,50.89307],[6.08805,50.87223],[6.07693,50.86025],[6.07431,50.84674],[6.05702,50.85179],[6.05623,50.8572],[6.01921,50.84435],[6.02328,50.81694],[6.00462,50.80065],[5.98404,50.80988],[5.97497,50.79992],[6.02624,50.77453],[6.01976,50.75398],[6.03889,50.74618],[6.0326,50.72647],[6.0406,50.71848],[6.04428,50.72861],[6.11707,50.72231],[6.17852,50.6245],[6.26957,50.62444],[6.2476,50.60392],[6.24888,50.59869],[6.24005,50.58732],[6.22581,50.5907],[6.20281,50.56952],[6.17739,50.55875],[6.17802,50.54179],[6.19735,50.53576],[6.19579,50.5313],[6.18716,50.52653],[6.19193,50.5212],[6.20599,50.52089],[6.22335,50.49578],[6.26637,50.50272],[6.30809,50.50058],[6.3465,50.48833],[6.34005,50.46083],[6.37219,50.45397],[6.36852,50.40776],[6.34406,50.37994],[6.3688,50.35898],[6.40785,50.33557],[6.40641,50.32425],[6.35701,50.31139],[6.32488,50.32333],[6.29949,50.30887],[6.28797,50.27458],[6.208,50.25179],[6.16853,50.2234],[6.18364,50.20815],[6.18739,50.1822],[6.14588,50.17106],[6.14132,50.14971],[6.15298,50.14126],[6.1379,50.12964],[6.12055,50.09171],[6.11274,50.05916],[6.13458,50.04141],[6.13044,50.02929],[6.14666,50.02207],[6.13794,50.01466],[6.13273,50.02019],[6.1295,50.01849],[6.13806,50.01056],[6.14948,50.00908],[6.14147,49.99563],[6.1701,49.98518],[6.16466,49.97086],[6.17872,49.9537],[6.18554,49.95622],[6.18045,49.96611],[6.19089,49.96991],[6.19856,49.95053],[6.22094,49.94955],[6.22608,49.929],[6.21882,49.92403],[6.22926,49.92096],[6.23496,49.89972],[6.26146,49.88203],[6.28874,49.87592],[6.29692,49.86685],[6.30963,49.87021],[6.32303,49.85133],[6.32098,49.83728],[6.33585,49.83785],[6.34267,49.84974],[6.36576,49.85032],[6.40022,49.82029],[6.42521,49.81591],[6.42905,49.81091],[6.44131,49.81443],[6.45425,49.81164],[6.47111,49.82263],[6.48718,49.81267],[6.50647,49.80916],[6.51215,49.80124],[6.52121,49.81338],[6.53122,49.80666],[6.52169,49.79787],[6.50534,49.78952],[6.51669,49.78336],[6.51056,49.77515],[6.51828,49.76855],[6.51646,49.75961],[6.50174,49.75292],[6.50193,49.73291],[6.51805,49.72425],[6.51397,49.72058],[6.50261,49.72718],[6.49535,49.72645],[6.49694,49.72205],[6.5042,49.71808],[6.50647,49.71353],[6.49785,49.71118],[6.48014,49.69767],[6.46048,49.69092],[6.44654,49.67799],[6.42937,49.66857],[6.42726,49.66078],[6.43768,49.66021],[6.4413,49.65722],[6.41861,49.61723],[6.39822,49.60081],[6.385,49.59946],[6.37464,49.58886],[6.38342,49.5799],[6.38024,49.57593],[6.36676,49.57813],[6.35825,49.57053],[6.38228,49.55855],[6.38072,49.55171],[6.35666,49.52931],[6.36788,49.50377],[6.36907,49.48931],[6.36778,49.46937],[6.38352,49.46463],[6.39168,49.4667],[6.40274,49.46546],[6.42432,49.47683],[6.55404,49.42464],[6.533,49.40748],[6.60091,49.36864],[6.58807,49.35358],[6.572,49.35027],[6.60186,49.31055],[6.66583,49.28065],[6.69274,49.21661],[6.71843,49.2208],[6.73256,49.20486],[6.71137,49.18808],[6.73765,49.16375],[6.78265,49.16793],[6.83385,49.15162],[6.84703,49.15734],[6.86225,49.18185],[6.85016,49.19354],[6.85119,49.20038],[6.83555,49.21249],[6.85939,49.22376],[6.89298,49.20863],[6.91875,49.22261],[6.93831,49.2223],[6.94028,49.21641],[6.95963,49.203],[6.97273,49.2099],[7.01318,49.19018],[7.03459,49.19096],[7.0274,49.17042],[7.03178,49.15734],[7.04662,49.13724],[7.04409,49.12123],[7.04843,49.11422],[7.05548,49.11185],[7.06642,49.11415],[7.07162,49.1255],[7.09007,49.13094],[7.07859,49.15031],[7.10715,49.15631],[7.10384,49.13787],[7.12504,49.14253],[7.1358,49.1282],[7.1593,49.1204],[7.23473,49.12971],[7.29514,49.11426],[7.3195,49.14231],[7.35995,49.14399],[7.3662,49.17308],[7.44052,49.18354],[7.44455,49.16765],[7.49473,49.17],[7.49172,49.13915],[7.53012,49.09818],[7.56416,49.08136],[7.62575,49.07654],[7.63618,49.05428],[7.75948,49.04562],[7.79557,49.06583],[7.86386,49.03499],[7.93641,49.05544],[7.97783,49.03161],[8.14189,48.97833],[8.22604,48.97352],[8.20031,48.95856],[8.19989,48.95825],[8.12813,48.87985],[8.10253,48.81829],[8.06802,48.78957],[8.0326,48.79017],[8.01534,48.76085],[7.96994,48.75606],[7.96812,48.72491],[7.89002,48.66317],[7.84098,48.64217],[7.80057,48.5857],[7.80167,48.54758],[7.80647,48.51239],[7.76833,48.48945],[7.73109,48.39192],[7.74562,48.32736],[7.69022,48.30018],[7.6648,48.22219],[7.57137,48.12292],[7.56966,48.03265],[7.62302,47.97898],[7.55673,47.87371],[7.52921,47.77747],[7.54761,47.72912],[7.53722,47.71635],[7.51266,47.70197],[7.51915,47.68335],[7.52067,47.66437],[7.53384,47.65115],[7.5591,47.63849],[7.57423,47.61628],[7.58851,47.60794],[7.59301,47.60058],[7.58945,47.59017],[7.60523,47.58519],[7.60459,47.57869],[7.61929,47.57683],[7.64309,47.59151],[7.64213,47.5944],[7.64599,47.59695],[7.67395,47.59212],[7.68229,47.59905],[7.69385,47.60099],[7.68486,47.59601],[7.67115,47.5871],[7.68904,47.57133],[7.67655,47.56435],[7.63338,47.56256],[7.65083,47.54662],[7.66174,47.54554],[7.6656,47.53752],[7.68101,47.53232],[7.69642,47.53297],[7.71961,47.54219],[7.75261,47.54599],[7.79486,47.55691],[7.80098,47.56335],[7.811279,47.56946],[7.81901,47.58798],[7.833466,47.58663],[7.84167,47.58196],[7.862692,47.58808],[7.88664,47.58854],[7.898354,47.58408],[7.90673,47.57674],[7.911615,47.56686],[7.906809,47.56037],[7.91251,47.55031],[7.920585,47.5469],[7.932644,47.54704],[7.94614,47.5436],[7.95682,47.55789],[7.97581,47.55493],[8.00113,47.55616],[8.02136,47.55096],[8.04383,47.55443],[8.06663,47.56374],[8.08557,47.55768],[8.10002,47.56504],[8.10395,47.57918],[8.11543,47.5841],[8.13662,47.58432],[8.13823,47.59147],[8.14947,47.59558],[8.1652,47.5945],[8.19378,47.61636],[8.20617,47.62141],[8.22011,47.6181],[8.22577,47.60385],[8.23809,47.61204],[8.25863,47.61571],[8.26313,47.6103],[8.2824,47.61225],[8.29722,47.60603],[8.29524,47.5919],[8.30277,47.58607],[8.32735,47.57133],[8.35512,47.57014],[8.38273,47.56608],[8.39477,47.57826],[8.43235,47.56617],[8.49431,47.58107],[8.48949,47.588],[8.46637,47.58389],[8.45578,47.60121],[8.50747,47.61897],[8.51686,47.63476],[8.55756,47.62394],[8.57586,47.59537],[8.60348,47.61204],[8.59545,47.64298],[8.60701,47.65271],[8.61471,47.64514],[8.60412,47.63735],[8.62049,47.63757],[8.62884,47.65098],[8.61113,47.66332],[8.6052,47.67258],[8.57683,47.66158],[8.56141,47.67088],[8.52801,47.66059],[8.5322,47.64687],[8.49656,47.64709],[8.46605,47.64103],[8.4667,47.65747],[8.44711,47.65379],[8.42264,47.66667],[8.41346,47.66676],[8.40473,47.67499],[8.4211,47.68407],[8.40569,47.69855],[8.44807,47.72426],[8.45771,47.7493],[8.48868,47.77215],[8.56814,47.78001],[8.56415,47.80633],[8.61657,47.79998],[8.62408,47.7626],[8.64425,47.76398],[8.65292,47.80066],[8.68022,47.78599],[8.68985,47.75686],[8.71778,47.76571],[8.74251,47.75168],[8.70543,47.73121],[8.73671,47.7169],[8.72617,47.69651],[8.72809,47.69282],[8.75856,47.68969],[8.79511,47.67462],[8.79966,47.70222],[8.76965,47.7075],[8.77309,47.72059],[8.80663,47.73821],[8.82002,47.71458],[8.86989,47.70504],[8.85065,47.68209],[8.87383,47.67045],[8.87625,47.65441],[8.89946,47.64769],[8.94093,47.65596],[9.02093,47.6868],[9.09891,47.67801],[9.13845,47.66389],[9.15181,47.66904],[9.1705,47.65513],[9.1755,47.65584],[9.17593,47.65399],[9.18203,47.65598],[9.25619,47.65939],[9.55125,47.53629],[9.72736,47.53457],[9.76748,47.5934],[9.80254,47.59419],[9.82591,47.58158],[9.8189,47.54688],[9.87499,47.52953],[9.87733,47.54688],[9.92407,47.53111],[9.96029,47.53899],[10.00003,47.48216],[10.03859,47.48927],[10.07131,47.45531],[10.09001,47.46005],[10.1052,47.4316],[10.06897,47.40709],[10.09819,47.35724],[10.11805,47.37228],[10.16362,47.36674],[10.17648,47.38889],[10.2127,47.38019],[10.22774,47.38904],[10.23757,47.37609],[10.19998,47.32832],[10.2147,47.31014],[10.17648,47.29149],[10.17531,47.27167],[10.23257,47.27088],[10.33424,47.30813],[10.39851,47.37623],[10.4324,47.38494],[10.4359,47.41183],[10.47446,47.43318],[10.46278,47.47901],[10.44291,47.48453],[10.4324,47.50111],[10.44992,47.5524],[10.43473,47.58394],[10.47329,47.58552],[10.48849,47.54057],[10.56912,47.53584],[10.60337,47.56755],[10.63456,47.5591],[10.68832,47.55752],[10.6965,47.54253],[10.7596,47.53228],[10.77596,47.51729],[10.88814,47.53701],[10.91268,47.51334],[10.86945,47.5015],[10.87061,47.4786],[10.90918,47.48571],[10.93839,47.48018],[10.92437,47.46991],[10.98513,47.42882],[10.97111,47.41617],[10.97111,47.39561],[11.11835,47.39719],[11.12536,47.41222],[11.20482,47.43198],[11.25157,47.43277],[11.22002,47.3964],[11.27844,47.39956],[11.29597,47.42566],[11.33804,47.44937],[11.4175,47.44621],[11.38128,47.47465],[11.4362,47.51413],[11.52618,47.50939],[11.58578,47.52281],[11.58811,47.55515],[11.60681,47.57881],[11.63934,47.59202],[11.84052,47.58354],[11.85572,47.60166],[12.0088,47.62451],[12.02282,47.61033],[12.05788,47.61742],[12.13734,47.60639],[12.17824,47.61506],[12.18145,47.61019],[12.17737,47.60121],[12.18568,47.6049],[12.20398,47.60667],[12.20801,47.61082],[12.19895,47.64085],[12.18507,47.65984],[12.18347,47.66663],[12.16769,47.68167],[12.16217,47.70105],[12.18303,47.70065],[12.22571,47.71776],[12.2542,47.7433],[12.26238,47.73544],[12.24017,47.69534],[12.26004,47.67725],[12.27991,47.68827],[12.336,47.69534],[12.37222,47.68433],[12.43883,47.6977],[12.44117,47.6741],[12.50076,47.62293],[12.53816,47.63553],[12.57438,47.63238],[12.6071,47.6741],[12.7357,47.6787],[12.77777,47.66689],[12.76492,47.64485],[12.82101,47.61493],[12.77427,47.58025],[12.80699,47.54477],[12.84672,47.54556],[12.85256,47.52741],[12.9624,47.47452],[12.98344,47.48716],[12.9998,47.46267],[13.04537,47.49426],[13.03252,47.53373],[13.05355,47.56291],[13.04537,47.58183],[13.06641,47.58577],[13.06407,47.60075],[13.09562,47.63304],[13.07692,47.68814],[13.01382,47.72116],[12.98578,47.7078],[12.92969,47.71094],[12.91333,47.7178],[12.90274,47.72513],[12.91711,47.74026],[12.9353,47.74788],[12.94371,47.76281],[12.93202,47.77302],[12.96311,47.79957],[12.98543,47.82896],[13.00588,47.84374],[12.94163,47.92927],[12.93886,47.94046],[12.93642,47.94436],[12.93419,47.94063],[12.92668,47.93879],[12.91985,47.94069],[12.9211,47.95135],[12.91683,47.95647],[12.87476,47.96195],[12.8549,48.01122],[12.76141,48.07373],[12.74973,48.10885],[12.7617,48.12796],[12.78595,48.12445],[12.80676,48.14979],[12.82673,48.15245],[12.8362,48.15876],[12.836,48.1647],[12.84475,48.16556],[12.87126,48.20318],[12.95306,48.20629],[13.02083,48.25689],[13.0851,48.27711],[13.126,48.27867],[13.18093,48.29577],[13.26039,48.29422],[13.30897,48.31575],[13.40709,48.37292],[13.43929,48.43386],[13.42527,48.45711],[13.45727,48.51092],[13.43695,48.55776],[13.45214,48.56472],[13.46967,48.55157],[13.50663,48.57506],[13.50131,48.58091],[13.51291,48.59023],[13.57535,48.55912],[13.59705,48.57013],[13.62508,48.55501],[13.65186,48.55092],[13.66113,48.53558],[13.72802,48.51208],[13.74816,48.53058],[13.7513,48.5624],[13.76921,48.55324],[13.80519,48.58026],[13.80038,48.59487],[13.82609,48.62345],[13.81901,48.6761],[13.81283,48.68426],[13.81791,48.69832],[13.79337,48.71375],[13.81863,48.73257],[13.82266,48.75544],[13.84023,48.76988],[13.8096,48.77877],[13.78977,48.83319],[13.76994,48.83537],[13.73854,48.88538],[13.67739,48.87886],[13.61624,48.9462],[13.58319,48.96899],[13.50552,48.97441],[13.50221,48.93752],[13.40802,48.98851],[13.39479,49.04812],[13.28242,49.1228],[13.23689,49.11412],[13.20405,49.12303],[13.17019,49.14339],[13.17665,49.16713],[13.05883,49.26259],[13.02957,49.27399],[13.03618,49.30417],[12.94859,49.34079],[12.88249,49.35479],[12.88414,49.33541],[12.78168,49.34618],[12.75854,49.3989],[12.71227,49.42363],[12.669,49.42935],[12.64121,49.47628],[12.64782,49.52565],[12.60155,49.52887],[12.56188,49.6146],[12.53544,49.61888],[12.52553,49.68415],[12.4462,49.70233],[12.40489,49.76321],[12.46603,49.78882],[12.48256,49.83575],[12.55197,49.92094],[12.47264,49.94222],[12.49908,49.97305],[12.30798,50.05719],[12.26111,50.06331],[12.27433,50.0771],[12.23709,50.10213],[12.2073,50.10315],[12.1917,50.13434],[12.21484,50.16399],[12.19335,50.19997],[12.09287,50.25032],[12.13716,50.27396],[12.10907,50.32041],[12.18013,50.32146],[12.20823,50.2729],[12.25119,50.27079],[12.26953,50.25189],[12.24791,50.25525],[12.23943,50.24594],[12.28755,50.22429],[12.28063,50.19544],[12.29232,50.17524],[12.32596,50.17146],[12.33847,50.19432],[12.32445,50.20442],[12.33263,50.24367],[12.35425,50.23993],[12.36594,50.28289],[12.40158,50.29521],[12.39924,50.32302],[12.43371,50.32506],[12.43722,50.33774],[12.46643,50.35527],[12.48256,50.34784],[12.49214,50.35228],[12.48747,50.37278],[12.51356,50.39694],[12.67261,50.41949],[12.70731,50.39948],[12.73044,50.42268],[12.73476,50.43237],[12.82465,50.45738],[12.94058,50.40944],[12.98433,50.42016],[13.02147,50.44763],[13.02038,50.4734],[13.0312,50.50944],[13.08301,50.50132],[13.13424,50.51709],[13.19043,50.50237],[13.25158,50.59268],[13.29454,50.57904],[13.32594,50.58009],[13.32264,50.60317],[13.37805,50.627],[13.37485,50.64931],[13.42189,50.61243],[13.46413,50.60102],[13.49742,50.63133],[13.5226,50.64721],[13.53748,50.67654],[13.52474,50.70394],[13.65977,50.73096],[13.70204,50.71771],[13.76316,50.73487],[13.82942,50.7251],[13.89444,50.74142],[13.89113,50.78533],[13.98864,50.8177],[14.02982,50.80662],[14.22331,50.86049],[14.24314,50.88761],[14.27123,50.89386],[14.30098,50.88448],[14.38691,50.89907],[14.39848,50.93866],[14.31422,50.95243],[14.30251,50.96606],[14.32793,50.97379],[14.32353,50.98556],[14.28776,50.97718],[14.25665,50.98935],[14.30098,51.05515],[14.41335,51.02086],[14.45827,51.03712],[14.49202,51.02286],[14.49154,51.04382],[14.49991,51.04692],[14.50809,51.0427],[14.49873,51.02242],[14.53321,51.01679],[14.53438,51.00374],[14.56432,51.01008],[14.58215,50.99306],[14.59908,50.98685],[14.59702,50.96148],[14.56374,50.922],[14.58024,50.91443],[14.64802,50.93241],[14.65259,50.90513],[14.63434,50.8883],[14.61993,50.86049],[14.70661,50.84096],[14.79139,50.81438],[14.82803,50.86966],[14.81664,50.88148],[14.89681,50.9422],[14.89252,50.94999],[14.92942,50.99744],[14.95529,51.04552],[14.97938,51.07742],[14.98229,51.11354],[14.99689,51.12205],[14.99079,51.14284],[14.99646,51.14365],[15.00083,51.14974],[14.99414,51.15813],[14.99311,51.16249],[15.0047,51.16874],[15.01242,51.21285],[15.04288,51.28387],[14.98008,51.33449],[14.96899,51.38367],[14.9652,51.44793],[14.94749,51.47155],[14.73219,51.52922],[14.72652,51.53902],[14.73047,51.54606],[14.71125,51.56209],[14.7727,51.61263],[14.75759,51.62318],[14.75392,51.67445],[14.69065,51.70842],[14.66386,51.73282],[14.64625,51.79472],[14.60493,51.80473],[14.59089,51.83302],[14.6588,51.88359],[14.6933,51.9044],[14.70601,51.92944],[14.7177,51.94048],[14.72163,51.95188],[14.71836,51.95606],[14.7139,51.95643],[14.70488,51.97679],[14.71339,52.00337],[14.76026,52.06624],[14.72971,52.09167],[14.6917,52.10283],[14.67683,52.13936],[14.70616,52.16927],[14.68344,52.19612],[14.71319,52.22144],[14.70139,52.25038],[14.58149,52.28007],[14.56378,52.33838],[14.55228,52.35264],[14.54423,52.42568],[14.63056,52.48993],[14.60081,52.53116],[14.6289,52.57136],[14.61073,52.59847],[14.22071,52.81175],[14.13806,52.82392],[14.12256,52.84311],[14.15873,52.87715],[14.14056,52.95786],[14.25954,53.00264],[14.35044,53.05829],[14.38679,53.13669],[14.36696,53.16444],[14.37853,53.20405],[14.40662,53.21098],[14.45125,53.26241],[14.44133,53.27427],[14.4215,53.27724],[14.35209,53.49506],[14.3273,53.50587],[14.30416,53.55499],[14.31904,53.61581],[14.2853,53.63392],[14.28477,53.65955],[14.27133,53.66613],[14.2836,53.67721],[14.26782,53.69866],[14.27249,53.74464],[14.21323,53.8664],[14.20823,53.90776],[14.18544,53.91258],[14.20647,53.91671],[14.22634,53.9291],[14.20811,54.12784],[13.93395,54.84044],[12.85844,54.82438],[11.90309,54.38543],[11.00303,54.63689],[10.31111,54.65968],[10.16755,54.73883],[9.89314,54.84171],[9.73563,54.8247],[9.61187,54.85548],[9.62734,54.88057],[9.58937,54.88785],[9.4659,54.83131],[9.43155,54.82586],[9.41213,54.84254],[9.38532,54.83968],[9.36496,54.81749],[9.33849,54.80233],[9.32771,54.80602],[9.2474,54.8112],[9.23445,54.83432],[9.24631,54.84726],[9.20571,54.85841],[9.14275,54.87421],[9.04629,54.87249],[8.92795,54.90452],[8.81178,54.90518],[8.76387,54.8948],[8.63979,54.91069],[8.55769,54.91837],[8.45719,55.06747],[8.02459,55.09613],[5.45168,54.20039]]],[[[8.65769,47.68928],[8.66837,47.68437],[8.68985,47.69552],[8.70847,47.68904],[8.71773,47.69088],[8.70237,47.71453],[8.66416,47.71367],[8.67508,47.6979],[8.65769,47.68928]]]]}},{"type":"Feature","properties":{"id":"BE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[2.18458,51.52087],[2.55904,51.07014],[2.57551,51.00326],[2.63074,50.94746],[2.59093,50.91751],[2.63331,50.81457],[2.71165,50.81295],[2.81056,50.71773],[2.8483,50.72276],[2.86985,50.7033],[2.87937,50.70298],[2.88504,50.70656],[2.90069,50.69263],[2.91036,50.6939],[2.90873,50.702],[2.95019,50.75138],[2.96778,50.75242],[3.00537,50.76588],[3.04314,50.77674],[3.09163,50.77717],[3.10614,50.78303],[3.11206,50.79416],[3.11987,50.79188],[3.1257,50.78603],[3.15017,50.79031],[3.16476,50.76843],[3.18339,50.74981],[3.18811,50.74025],[3.20064,50.73547],[3.19017,50.72569],[3.20845,50.71662],[3.22042,50.71019],[3.24593,50.71389],[3.26063,50.70086],[3.26141,50.69151],[3.2536,50.68977],[3.264,50.67668],[3.23951,50.6585],[3.2729,50.60718],[3.28575,50.52724],[3.37693,50.49538],[3.44629,50.51009],[3.47385,50.53397],[3.51564,50.5256],[3.49509,50.48885],[3.5683,50.50192],[3.58361,50.49049],[3.61014,50.49568],[3.64426,50.46275],[3.66153,50.45165],[3.67494,50.40239],[3.67262,50.38663],[3.65709,50.36873],[3.66976,50.34563],[3.71009,50.30305],[3.70987,50.3191],[3.73911,50.34809],[3.84314,50.35219],[3.90781,50.32814],[3.96771,50.34989],[4.0268,50.35793],[4.0689,50.3254],[4.10237,50.31247],[4.10957,50.30234],[4.11954,50.30425],[4.13665,50.25609],[4.16808,50.25786],[4.15524,50.2833],[4.17347,50.28838],[4.17861,50.27443],[4.20651,50.27333],[4.21945,50.25539],[4.15524,50.21103],[4.16014,50.19239],[4.13561,50.13078],[4.20147,50.13535],[4.23101,50.06945],[4.16294,50.04719],[4.13508,50.01976],[4.14239,49.98034],[4.20532,49.95803],[4.31963,49.97043],[4.35051,49.95315],[4.43488,49.94122],[4.51098,49.94659],[4.5414,49.96911],[4.68695,49.99685],[4.70064,50.09384],[4.75237,50.11314],[4.82438,50.16878],[4.83279,50.15331],[4.88602,50.15182],[4.8382,50.06738],[4.78827,49.95609],[4.88529,49.9236],[4.85134,49.86457],[4.86965,49.82271],[4.85464,49.78995],[4.96714,49.79872],[5.09249,49.76193],[5.14545,49.70287],[5.26232,49.69456],[5.31465,49.66846],[5.33039,49.6555],[5.30214,49.63055],[5.3137,49.61225],[5.33851,49.61599],[5.34837,49.62889],[5.3974,49.61596],[5.43713,49.5707],[5.46734,49.52648],[5.46541,49.49825],[5.55001,49.52729],[5.60909,49.51228],[5.64505,49.55146],[5.75649,49.54321],[5.7577,49.55915],[5.77435,49.56298],[5.79195,49.55228],[5.81838,49.54777],[5.84143,49.5533],[5.84692,49.55663],[5.8424,49.56082],[5.87256,49.57539],[5.86986,49.58756],[5.84971,49.58674],[5.84826,49.5969],[5.8762,49.60898],[5.87609,49.62047],[5.88393,49.62802],[5.88552,49.63507],[5.90599,49.63853],[5.90164,49.6511],[5.9069,49.66377],[5.86175,49.67862],[5.86527,49.69291],[5.88677,49.70951],[5.86503,49.72739],[5.84193,49.72161],[5.82562,49.72395],[5.83149,49.74729],[5.82245,49.75048],[5.78871,49.7962],[5.75409,49.79239],[5.74953,49.81428],[5.74364,49.82058],[5.74844,49.82435],[5.7404,49.83452],[5.74076,49.83823],[5.74975,49.83933],[5.74953,49.84709],[5.75884,49.84811],[5.74567,49.85368],[5.75861,49.85631],[5.75269,49.8711],[5.78415,49.87922],[5.73621,49.89796],[5.77314,49.93646],[5.77291,49.96056],[5.80833,49.96451],[5.81163,49.97142],[5.83467,49.97823],[5.83968,49.9892],[5.82331,49.99662],[5.81866,50.01286],[5.8551,50.02683],[5.86904,50.04614],[5.85474,50.06342],[5.8857,50.07824],[5.89488,50.11476],[5.95929,50.13295],[5.96453,50.17259],[6.02488,50.18283],[6.03093,50.16362],[6.06406,50.15344],[6.08577,50.17246],[6.12028,50.16374],[6.1137,50.13668],[6.1379,50.12964],[6.15298,50.14126],[6.14132,50.14971],[6.14588,50.17106],[6.18739,50.1822],[6.18364,50.20815],[6.16853,50.2234],[6.208,50.25179],[6.28797,50.27458],[6.29949,50.30887],[6.32488,50.32333],[6.35701,50.31139],[6.40641,50.32425],[6.40785,50.33557],[6.3688,50.35898],[6.34406,50.37994],[6.36852,50.40776],[6.37219,50.45397],[6.34005,50.46083],[6.3465,50.48833],[6.30809,50.50058],[6.26637,50.50272],[6.22335,50.49578],[6.20599,50.52089],[6.19193,50.5212],[6.18716,50.52653],[6.19579,50.5313],[6.19735,50.53576],[6.17802,50.54179],[6.17739,50.55875],[6.20281,50.56952],[6.22581,50.5907],[6.24005,50.58732],[6.24888,50.59869],[6.2476,50.60392],[6.26957,50.62444],[6.17852,50.6245],[6.11707,50.72231],[6.04428,50.72861],[6.0406,50.71848],[6.0326,50.72647],[6.03889,50.74618],[6.01976,50.75398],[5.97545,50.75441],[5.95942,50.7622],[5.89132,50.75124],[5.89129,50.75125],[5.88734,50.77092],[5.84888,50.75448],[5.84548,50.76542],[5.80673,50.7558],[5.77513,50.78308],[5.76533,50.78159],[5.74356,50.7691],[5.73904,50.75674],[5.72216,50.76398],[5.69469,50.75529],[5.68091,50.75804],[5.70107,50.7827],[5.68995,50.79641],[5.70118,50.80764],[5.65259,50.82309],[5.64009,50.84742],[5.64504,50.87107],[5.67886,50.88142],[5.69858,50.91046],[5.71626,50.90796],[5.72644,50.91167],[5.72545,50.92312],[5.74644,50.94723],[5.75927,50.95601],[5.74752,50.96202],[5.72875,50.95428],[5.71864,50.96092],[5.76242,50.99703],[5.77688,51.02483],[5.75961,51.03113],[5.77258,51.06196],[5.79835,51.05834],[5.79903,51.09371],[5.82921,51.09328],[5.83226,51.10585],[5.8109,51.10861],[5.80798,51.11661],[5.85508,51.14445],[5.82564,51.16753],[5.77697,51.1522],[5.77735,51.17845],[5.74617,51.18928],[5.70344,51.1829],[5.65528,51.18736],[5.65145,51.19788],[5.5603,51.22249],[5.5569,51.26544],[5.515,51.29462],[5.48476,51.30053],[5.46519,51.2849],[5.4407,51.28169],[5.41672,51.26248],[5.347,51.27502],[5.33886,51.26314],[5.29716,51.26104],[5.26461,51.26693],[5.23814,51.26064],[5.22542,51.26888],[5.24244,51.30495],[5.2002,51.32243],[5.16222,51.31035],[5.13377,51.31592],[5.13105,51.34791],[5.07102,51.39469],[5.10456,51.43163],[5.07891,51.4715],[5.04774,51.47022],[5.03281,51.48679],[5.0106,51.47167],[5.00393,51.44406],[4.92152,51.39487],[4.90016,51.41404],[4.84988,51.41502],[4.78941,51.41102],[4.77229,51.41337],[4.76577,51.43046],[4.78314,51.43319],[4.82946,51.4213],[4.82409,51.44736],[4.84139,51.4799],[4.78803,51.50284],[4.77321,51.50529],[4.74578,51.48937],[4.72935,51.48424],[4.65442,51.42352],[4.57489,51.4324],[4.53521,51.4243],[4.52846,51.45002],[4.54675,51.47265],[4.5388,51.48184],[4.47736,51.4778],[4.38122,51.44905],[4.39747,51.43316],[4.38064,51.41965],[4.43777,51.36989],[4.39292,51.35547],[4.34086,51.35738],[4.33265,51.37687],[4.21923,51.37443],[4.24024,51.35371],[4.16721,51.29348],[4.05165,51.24171],[4.01957,51.24504],[3.97889,51.22537],[3.90125,51.20371],[3.78783,51.2151],[3.78999,51.25766],[3.58939,51.30064],[3.51502,51.28697],[3.52698,51.2458],[3.43488,51.24135],[3.41704,51.25933],[3.38289,51.27331],[3.35847,51.31572],[3.38696,51.33436],[3.36263,51.37112],[2.56575,51.85301],[2.18458,51.52087]]],[[[4.91493,51.4353],[4.92652,51.43329],[4.92952,51.42984],[4.93986,51.43064],[4.94265,51.44003],[4.93471,51.43861],[4.93416,51.44185],[4.94025,51.44193],[4.93544,51.44634],[4.92879,51.44161],[4.92815,51.43856],[4.92566,51.44273],[4.92811,51.4437],[4.92287,51.44741],[4.91811,51.44621],[4.92227,51.44252],[4.91935,51.43634],[4.91493,51.4353]]],[[[4.93295,51.44945],[4.93909,51.44632],[4.9524,51.45014],[4.95244,51.45207],[4.93295,51.44945]]]]}},{"type":"Feature","properties":{"id":"AU-NSW"},"geometry":{"type":"Polygon","coordinates":[[[140.99934,-28.99903],[141.00268,-34.02172],[141.5377,-34.18902],[141.71349,-34.0924],[142.0211,-34.12651],[142.22023,-34.18334],[142.24495,-34.3014],[142.37404,-34.34563],[142.50999,-34.74267],[142.61711,-34.77765],[142.76268,-34.56871],[143.34221,-34.79344],[143.3271,-34.99618],[143.39027,-35.18047],[143.57155,-35.20741],[143.56743,-35.33634],[144.08241,-35.57238],[144.74022,-36.11895],[144.94896,-36.05236],[144.9778,-35.86673],[145.1165,-35.81774],[145.34447,-35.86005],[145.50789,-35.80772],[145.80589,-35.98461],[146.36894,-36.03571],[146.42387,-35.96794],[146.59966,-35.97349],[146.85097,-36.08788],[147.04185,-36.09898],[147.10503,-36.00683],[147.31926,-36.05458],[147.39616,-35.94681],[147.71202,-35.94237],[147.99217,-36.0457],[148.04573,-36.39248],[148.20366,-36.59782],[148.10753,-36.79272],[148.19405,-36.79602],[150.19768,-37.59458],[152.57175,-39.16128],[155.3142,-27.34698],[153.55096,-28.16364],[153.53414,-28.17635],[153.47715,-28.15789],[153.36385,-28.242],[153.18121,-28.25289],[153.10293,-28.35445],[152.87222,-28.30852],[152.74725,-28.35929],[152.60442,-28.27466],[152.57696,-28.3327],[152.49731,-28.25168],[151.94662,-28.54282],[152.06472,-28.69351],[152.01391,-28.89449],[151.7777,-28.9606],[151.72552,-28.86683],[151.55248,-28.94858],[151.39318,-29.17186],[151.30392,-29.15627],[151.27508,-28.94017],[150.74499,-28.63446],[150.43737,-28.66098],[150.30004,-28.53558],[149.67519,-28.62723],[149.58044,-28.57056],[149.48705,-28.58202],[149.37788,-28.68628],[149.19248,-28.77479],[148.95806,-28.99906],[140.99934,-28.99903]],[[148.76247,-35.49504],[148.80951,-35.30698],[149.12159,-35.1241],[149.19746,-35.18502],[149.18956,-35.20157],[149.2469,-35.2285],[149.23488,-35.24336],[149.39418,-35.30362],[149.39796,-35.32435],[149.35058,-35.3518],[149.33719,-35.33976],[149.25136,-35.33024],[149.2057,-35.34732],[149.13429,-35.45338],[149.15283,-35.50566],[149.12983,-35.55288],[149.14219,-35.59337],[149.07936,-35.58193],[149.09549,-35.6411],[149.09824,-35.81223],[149.04811,-35.91684],[148.96194,-35.8971],[148.89602,-35.82504],[148.89431,-35.75095],[148.8768,-35.715],[148.85723,-35.76043],[148.78891,-35.69995],[148.76247,-35.49504]],[[150.59126,-35.17225],[150.60036,-35.1672],[150.60534,-35.15429],[150.59564,-35.15197],[150.59384,-35.14376],[150.66156,-35.11779],[150.70087,-35.12312],[150.78069,-35.10852],[150.7431,-35.20971],[150.59307,-35.18389],[150.59126,-35.17225]]]}},{"type":"Feature","properties":{"id":"AE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[51.41644,24.39615],[51.58871,24.27256],[51.59617,24.12041],[52.56622,22.94341],[55.13599,22.63334],[55.2137,22.71065],[55.22634,23.10378],[55.57358,23.669],[55.48677,23.94946],[55.73301,24.05994],[55.8308,24.01633],[56.01799,24.07426],[55.95472,24.2172],[55.83367,24.20193],[55.77658,24.23476],[55.76558,24.23227],[55.75257,24.23466],[55.75382,24.2466],[55.75939,24.26114],[55.76781,24.26209],[55.79145,24.27914],[55.80747,24.31069],[55.83395,24.32776],[55.83271,24.41521],[55.76461,24.5287],[55.83271,24.68567],[55.83408,24.77858],[55.81348,24.80102],[55.81116,24.9116],[55.85094,24.96858],[55.90849,24.96771],[55.96316,25.00857],[56.05715,24.95727],[56.05106,24.87461],[55.97467,24.89639],[55.97836,24.87673],[56.03535,24.81161],[56.06128,24.74457],[56.13684,24.73699],[56.20062,24.78565],[56.20568,24.85063],[56.30269,24.88334],[56.34873,24.93205],[56.3227,24.97284],[56.86325,25.03856],[56.82555,25.7713],[56.26534,25.62825],[56.25341,25.61443],[56.26636,25.60643],[56.25365,25.60211],[56.20473,25.61119],[56.18363,25.65508],[56.14826,25.66351],[56.13579,25.73524],[56.17416,25.77239],[56.13963,25.82765],[56.19334,25.9795],[56.15498,26.06828],[56.08666,26.05038],[55.81777,26.18798],[55.14145,25.62624],[53.97892,24.64436],[52.82259,25.51697],[52.35509,25.00368],[52.02277,24.75635],[51.83108,24.71675],[51.58834,24.66608],[51.41644,24.39615]],[[56.20838,25.25668],[56.24465,25.27505],[56.25008,25.28843],[56.23362,25.31253],[56.26062,25.33108],[56.3005,25.31815],[56.3111,25.30107],[56.35172,25.30681],[56.34438,25.26653],[56.27628,25.23404],[56.24341,25.22867],[56.20872,25.24104],[56.20838,25.25668]]],[[[56.27086,25.26128],[56.28423,25.26344],[56.29379,25.2754],[56.28102,25.28486],[56.2716,25.27916],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"BQ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-68.90012,12.62309],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309]]],[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-63.29212,17.90532],[-63.58819,17.61311]]]]}},{"type":"Feature","properties":{"id":"AM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[43.44984,41.0988],[43.47319,41.02251],[43.58683,40.98961],[43.67712,40.93084],[43.67712,40.84846],[43.74872,40.7365],[43.7425,40.66805],[43.63664,40.54159],[43.54791,40.47413],[43.60862,40.43267],[43.59928,40.34019],[43.71136,40.16673],[43.65221,40.14889],[43.65688,40.11199],[43.92307,40.01787],[44.1057,40.03555],[44.1778,40.02845],[44.26973,40.04866],[44.46635,39.97733],[44.61845,39.8281],[44.75779,39.7148],[44.88354,39.74432],[44.92869,39.72157],[45.06604,39.79277],[45.18554,39.67846],[45.17464,39.58614],[45.21784,39.58074],[45.23535,39.61373],[45.30385,39.61373],[45.29606,39.57654],[45.46992,39.49888],[45.70547,39.60174],[45.80804,39.56716],[45.83,39.46487],[45.79225,39.3695],[45.99774,39.28931],[46.02303,39.09978],[46.06973,39.0744],[46.14785,38.84206],[46.20601,38.85262],[46.34059,38.92076],[46.53497,38.86548],[46.51805,38.94982],[46.54296,39.07078],[46.44022,39.19636],[46.52584,39.18912],[46.54141,39.15895],[46.58032,39.21204],[46.63481,39.23013],[46.56476,39.24942],[46.50093,39.33736],[46.43244,39.35181],[46.37795,39.42039],[46.4013,39.45405],[46.53051,39.47809],[46.51027,39.52373],[46.57721,39.54414],[46.57098,39.56694],[46.52117,39.58734],[46.42465,39.57534],[46.40286,39.63651],[46.18493,39.60533],[45.96543,39.78859],[45.82533,39.82925],[45.7833,39.9475],[45.60895,39.97733],[45.59806,40.0131],[45.78642,40.03218],[45.83779,39.98925],[45.97944,40.181],[45.95609,40.27846],[45.65098,40.37696],[45.42994,40.53804],[45.45484,40.57707],[45.35366,40.65979],[45.4206,40.7424],[45.55914,40.78366],[45.60584,40.87436],[45.40814,40.97904],[45.44083,41.01663],[45.39725,41.02603],[45.35677,40.99784],[45.28859,41.03757],[45.26162,41.0228],[45.25897,41.0027],[45.1994,41.04518],[45.16493,41.05068],[45.1634,41.08082],[45.1313,41.09369],[45.12923,41.06059],[45.06784,41.05379],[45.08028,41.10917],[45.19942,41.13299],[45.1969,41.168],[45.11811,41.19967],[45.05201,41.19211],[45.02932,41.2101],[45.05497,41.2464],[45.0133,41.29747],[44.93493,41.25685],[44.81437,41.30371],[44.80053,41.25949],[44.81749,41.23488],[44.84358,41.23088],[44.89911,41.21366],[44.87887,41.20195],[44.82084,41.21513],[44.72814,41.20338],[44.61462,41.24018],[44.59322,41.1933],[44.46791,41.18204],[44.34417,41.2382],[44.34337,41.20312],[44.32139,41.2079],[44.18148,41.24644],[44.16591,41.19141],[43.84835,41.16329],[43.74717,41.1117],[43.67712,41.13398],[43.4717,41.12611],[43.44984,41.0988]],[[44.95383,41.07553],[44.97169,41.09176],[45.00864,41.09407],[45.03406,41.07931],[45.04517,41.06653],[45.03792,41.03938],[45.00864,41.03411],[44.9903,41.05657],[44.96031,41.06345],[44.95383,41.07553]],[[45.18382,41.0066],[45.20625,41.01484],[45.23487,41.00226],[45.23095,40.97828],[45.21324,40.9817],[45.21219,40.99001],[45.20518,40.99348],[45.19312,40.98998],[45.18382,41.0066]]],[[[45.47927,40.65023],[45.50279,40.58424],[45.56071,40.64765],[45.51825,40.67382],[45.47927,40.65023]]]]}},{"type":"Feature","properties":{"id":"SJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-10.71459,70.09565],[-5.93364,70.76368],[-9.68082,72.73731],[-10.71459,70.09565]]],[[[-3.52068,82.6752],[16.4353,73.61229],[33.12005,75.46568],[35.22046,80.57056],[-3.52068,82.6752]]]]}},{"type":"Feature","properties":{"id":"CN-GD"},"geometry":{"type":"Polygon","coordinates":[[[108.26073,20.07614],[111.04979,20.2622],[117.76968,23.10828],[117.05863,23.73884],[116.96353,23.90655],[116.97589,23.95739],[116.97933,24.00099],[116.91478,24.09097],[116.99512,24.18402],[116.93023,24.23256],[116.89693,24.40025],[116.7517,24.5415],[116.81076,24.68352],[116.51859,24.60332],[116.48735,24.67977],[116.37062,24.80231],[116.40975,24.84313],[116.34761,24.86899],[116.29886,24.79857],[116.20754,24.84718],[116.05201,24.85528],[115.96618,24.92193],[115.88859,24.94123],[115.89889,24.87833],[115.86696,24.86743],[115.82405,24.91539],[115.76362,24.79109],[115.76499,24.71221],[115.80379,24.69131],[115.78559,24.63703],[115.84327,24.5671],[115.69358,24.54056],[115.65444,24.61799],[115.56381,24.62954],[115.46699,24.76584],[115.37738,24.76803],[115.10822,24.66792],[115.06599,24.70753],[114.93415,24.6467],[114.9266,24.67478],[114.85897,24.55805],[114.8442,24.58178],[114.72576,24.6055],[114.4178,24.48464],[114.35462,24.58865],[114.17541,24.6545],[114.57092,25.08746],[114.74739,25.13036],[114.5565,25.42281],[114.19464,25.29685],[114.00924,25.28319],[113.98315,25.40916],[113.56635,25.30802],[113.26217,25.51486],[113.12725,25.47039],[113.01721,25.34976],[112.89653,25.31501],[112.84984,25.34278],[112.8713,25.24857],[112.98923,25.24857],[113.02854,25.20059],[112.96674,25.16361],[113.00571,24.93999],[112.7798,24.89328],[112.70393,25.08622],[112.65174,25.13751],[112.18963,25.18987],[112.13504,25.00535],[112.11547,24.96582],[112.17143,24.91944],[112.15015,24.82662],[112.04166,24.77987],[111.93145,24.69506],[112.05711,24.35522],[111.87171,24.10978],[111.92596,23.97339],[111.79515,23.8133],[111.65645,23.83308],[111.63276,23.64641],[111.47724,23.62313],[111.34094,23.19654],[111.42711,23.03013],[111.35158,22.96123],[111.31965,22.85086],[110.99452,22.63682],[110.74424,22.56931],[110.73738,22.46307],[110.67626,22.47576],[110.78475,22.27353],[110.64193,22.23349],[110.67317,22.17659],[110.62442,22.15274],[110.34702,22.19567],[110.36556,21.93476],[110.39234,21.91199],[110.3841,21.89096],[110.32127,21.89351],[110.28573,21.91756],[110.24642,21.88332],[110.19458,21.90148],[109.98481,21.87185],[109.94585,21.84397],[109.92216,21.71198],[109.8904,21.65008],[109.76371,21.67178],[109.73968,21.6054],[109.78912,21.47351],[108.26073,20.07614]],[[113.52659,22.18271],[113.53552,22.20607],[113.53301,22.21235],[113.53593,22.2137],[113.54093,22.21314],[113.54333,22.21688],[113.5508,22.21672],[113.56865,22.20973],[113.57123,22.20416],[113.60504,22.20464],[113.63011,22.10782],[113.57191,22.07696],[113.54839,22.10909],[113.54942,22.14519],[113.54093,22.15497],[113.52659,22.18271]],[[113.81621,22.2163],[113.86771,22.42972],[114.03113,22.5065],[114.05438,22.5026],[114.05729,22.51104],[114.06272,22.51617],[114.07267,22.51855],[114.07817,22.52997],[114.08606,22.53276],[114.09048,22.53716],[114.09692,22.53435],[114.1034,22.5352],[114.11181,22.52878],[114.11656,22.53415],[114.12665,22.54003],[114.13823,22.54319],[114.1482,22.54091],[114.15123,22.55163],[114.1597,22.56041],[114.17247,22.55944],[114.18338,22.55444],[114.20655,22.55706],[114.22185,22.55343],[114.22888,22.5436],[114.25154,22.55977],[114.44998,22.55977],[114.50148,22.15017],[113.92195,22.13873],[113.83338,22.1826],[113.81621,22.2163]]]}},{"type":"Feature","properties":{"id":"AZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[44.75779,39.7148],[44.80977,39.65768],[44.81043,39.62677],[44.88916,39.59653],[44.96746,39.42998],[45.05932,39.36435],[45.08751,39.35052],[45.16168,39.21952],[45.30489,39.18333],[45.40148,39.09007],[45.40452,39.07224],[45.44811,39.04927],[45.44966,38.99243],[45.6131,38.964],[45.6155,38.94304],[45.65172,38.95199],[45.83883,38.90768],[45.90266,38.87739],[45.94624,38.89072],[46.00228,38.87376],[46.06766,38.87861],[46.14785,38.84206],[46.06973,39.0744],[46.02303,39.09978],[45.99774,39.28931],[45.79225,39.3695],[45.83,39.46487],[45.80804,39.56716],[45.70547,39.60174],[45.46992,39.49888],[45.29606,39.57654],[45.30385,39.61373],[45.23535,39.61373],[45.21784,39.58074],[45.17464,39.58614],[45.18554,39.67846],[45.06604,39.79277],[44.92869,39.72157],[44.88354,39.74432],[44.75779,39.7148]]],[[[44.95383,41.07553],[44.96031,41.06345],[44.9903,41.05657],[45.00864,41.03411],[45.03792,41.03938],[45.04517,41.06653],[45.03406,41.07931],[45.00864,41.09407],[44.97169,41.09176],[44.95383,41.07553]]],[[[45.0133,41.29747],[45.05497,41.2464],[45.02932,41.2101],[45.05201,41.19211],[45.11811,41.19967],[45.1969,41.168],[45.19942,41.13299],[45.08028,41.10917],[45.06784,41.05379],[45.12923,41.06059],[45.1313,41.09369],[45.1634,41.08082],[45.16493,41.05068],[45.1994,41.04518],[45.25897,41.0027],[45.26162,41.0228],[45.28859,41.03757],[45.35677,40.99784],[45.39725,41.02603],[45.44083,41.01663],[45.40814,40.97904],[45.60584,40.87436],[45.55914,40.78366],[45.4206,40.7424],[45.35366,40.65979],[45.45484,40.57707],[45.42994,40.53804],[45.65098,40.37696],[45.95609,40.27846],[45.97944,40.181],[45.83779,39.98925],[45.78642,40.03218],[45.59806,40.0131],[45.60895,39.97733],[45.7833,39.9475],[45.82533,39.82925],[45.96543,39.78859],[46.18493,39.60533],[46.40286,39.63651],[46.42465,39.57534],[46.52117,39.58734],[46.57098,39.56694],[46.57721,39.54414],[46.51027,39.52373],[46.53051,39.47809],[46.4013,39.45405],[46.37795,39.42039],[46.43244,39.35181],[46.50093,39.33736],[46.56476,39.24942],[46.63481,39.23013],[46.58032,39.21204],[46.54141,39.15895],[46.52584,39.18912],[46.44022,39.19636],[46.54296,39.07078],[46.51805,38.94982],[46.53497,38.86548],[46.75752,39.03231],[46.83822,39.13143],[46.92539,39.16644],[46.95341,39.13505],[47.05771,39.20143],[47.05927,39.24846],[47.31301,39.37492],[47.38978,39.45999],[47.50099,39.49615],[47.84774,39.66285],[47.98977,39.70999],[48.34264,39.42935],[48.37385,39.37584],[48.15984,39.30028],[48.12404,39.25208],[48.15361,39.19419],[48.31239,39.09278],[48.33884,39.03022],[48.28437,38.97186],[48.08627,38.94434],[48.07734,38.91616],[48.01409,38.90333],[48.02581,38.82705],[48.24773,38.71883],[48.3146,38.59958],[48.45084,38.61013],[48.58793,38.45076],[48.62217,38.40198],[48.70001,38.40564],[48.78979,38.45026],[48.81072,38.44853],[48.84969,38.45015],[48.88288,38.43975],[49.20805,38.40869],[51.7708,40.29239],[48.80971,41.95365],[48.5867,41.84306],[48.55078,41.77917],[48.42301,41.65444],[48.40277,41.60441],[48.2878,41.56221],[48.22064,41.51472],[48.07587,41.49957],[47.87973,41.21798],[47.75831,41.19455],[47.62288,41.22969],[47.54504,41.20275],[47.49004,41.26366],[47.34579,41.27884],[47.10762,41.59044],[47.03757,41.55434],[46.99554,41.59743],[47.00955,41.63583],[46.8134,41.76252],[46.75269,41.8623],[46.58924,41.80547],[46.5332,41.87389],[46.42738,41.91323],[46.3984,41.84399],[46.30863,41.79133],[46.23962,41.75811],[46.20538,41.77205],[46.17891,41.72094],[46.19759,41.62327],[46.24429,41.59883],[46.26531,41.63339],[46.28182,41.60089],[46.3253,41.60912],[46.34039,41.5947],[46.34126,41.57454],[46.29794,41.5724],[46.33925,41.4963],[46.40307,41.48464],[46.4669,41.43331],[46.63658,41.37727],[46.72375,41.28609],[46.66148,41.20533],[46.63969,41.09515],[46.55096,41.1104],[46.48558,41.0576],[46.456,41.09984],[46.37661,41.10805],[46.27698,41.19011],[46.13221,41.19479],[45.95786,41.17956],[45.80842,41.2229],[45.69946,41.29545],[45.75705,41.35157],[45.71035,41.36208],[45.68389,41.3539],[45.45973,41.45898],[45.4006,41.42402],[45.31352,41.47168],[45.26285,41.46433],[45.1797,41.42231],[45.09867,41.34065],[45.0133,41.29747]],[[45.47927,40.65023],[45.51825,40.67382],[45.56071,40.64765],[45.50279,40.58424],[45.47927,40.65023]]],[[[45.18382,41.0066],[45.19312,40.98998],[45.20518,40.99348],[45.21219,40.99001],[45.21324,40.9817],[45.23095,40.97828],[45.23487,41.00226],[45.20625,41.01484],[45.18382,41.0066]]]]}},{"type":"Feature","properties":{"id":"OM-MU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[55.81777,26.18798],[56.08666,26.05038],[56.15498,26.06828],[56.19334,25.9795],[56.13963,25.82765],[56.17416,25.77239],[56.13579,25.73524],[56.14826,25.66351],[56.18363,25.65508],[56.20473,25.61119],[56.25365,25.60211],[56.26636,25.60643],[56.25341,25.61443],[56.26534,25.62825],[56.82555,25.7713],[56.79239,26.41236],[56.68954,26.76645],[56.2644,26.58649],[55.81777,26.18798]]],[[[56.20838,25.25668],[56.20872,25.24104],[56.24341,25.22867],[56.27628,25.23404],[56.34438,25.26653],[56.35172,25.30681],[56.3111,25.30107],[56.3005,25.31815],[56.26062,25.33108],[56.23362,25.31253],[56.25008,25.28843],[56.24465,25.27505],[56.20838,25.25668]],[[56.27086,25.26128],[56.2716,25.27916],[56.28102,25.28486],[56.29379,25.2754],[56.28423,25.26344],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"CY"},"geometry":{"type":"MultiPolygon","coordinates":[[[[31.71872,35.1606],[32.10341,34.37973],[32.74412,34.43926],[32.75515,34.64985],[32.76136,34.68318],[32.79433,34.67883],[32.82717,34.70622],[32.86014,34.70585],[32.86167,34.68734],[32.9068,34.66102],[32.91398,34.67343],[32.93043,34.67091],[32.92807,34.66736],[32.93449,34.66241],[32.93693,34.67027],[32.94379,34.67111],[32.94683,34.67907],[32.95539,34.68471],[32.99135,34.68061],[32.98668,34.67268],[32.99014,34.65518],[32.97736,34.65277],[32.97079,34.66112],[32.95325,34.66462],[32.94796,34.6587],[32.94976,34.65204],[32.95471,34.64528],[32.95323,34.64075],[32.95891,34.62919],[32.96718,34.63446],[32.96968,34.64046],[33.0138,34.64424],[33.26744,34.49942],[33.83531,34.73974],[33.70575,34.97947],[33.70639,34.99303],[33.71514,35.00294],[33.69731,35.01754],[33.69938,35.03123],[33.67678,35.03866],[33.67742,35.05963],[33.68474,35.06602],[33.69095,35.06237],[33.70861,35.07644],[33.7161,35.07279],[33.70209,35.04882],[33.71482,35.03722],[33.73824,35.05321],[33.76106,35.04253],[33.78581,35.05104],[33.82067,35.07826],[33.84168,35.06823],[33.8541,35.07201],[33.87479,35.08881],[33.87097,35.09389],[33.87622,35.10457],[33.87224,35.12293],[33.88561,35.12449],[33.88943,35.12007],[33.88737,35.11408],[33.89853,35.11377],[33.91789,35.08688],[33.91299,35.07579],[33.90247,35.07686],[33.89485,35.06873],[33.88367,35.07877],[33.85261,35.0574],[33.8355,35.05777],[33.82051,35.0667],[33.8012,35.04786],[33.81524,35.04192],[33.83055,35.02865],[33.82875,35.01685],[33.84045,35.00616],[33.85216,35.00579],[33.85891,35.001],[33.85621,34.98956],[33.83505,34.98108],[33.84811,34.97075],[33.86432,34.97592],[33.90075,34.96623],[33.98684,34.76642],[35.48515,34.70851],[35.51152,36.10954],[32.82353,35.70297],[31.71872,35.1606]]],[[[33.7343,35.01178],[33.74144,35.01053],[33.7492,35.01319],[33.74983,35.02274],[33.74265,35.02329],[33.73781,35.02181],[33.7343,35.01178]]],[[[33.75682,34.99916],[33.76605,34.99543],[33.76738,34.99188],[33.7778,34.98981],[33.77843,34.988],[33.78149,34.98854],[33.78318,34.98699],[33.78571,34.98951],[33.78917,34.98854],[33.79191,34.98914],[33.78516,34.99582],[33.77553,34.99518],[33.77312,34.9976],[33.75994,35.00113],[33.75682,34.99916]]]]}},{"type":"Feature","properties":{"id":"IN-PY"},"geometry":{"type":"MultiPolygon","coordinates":[[[[75.18012,11.478],[75.19042,11.45378],[75.53787,11.6872],[75.55864,11.72317],[75.53821,11.76384],[75.52551,11.6993],[75.18012,11.478]]],[[[79.59525,11.86735],[80.35262,11.6751],[80.31898,12.0091],[79.70031,12.10378],[79.59525,11.86735]]],[[[79.68658,10.99107],[79.8088,10.81374],[80.42198,10.81981],[80.41717,10.96613],[79.68658,10.99107]]],[[[82.19078,16.71874],[82.9708,16.27499],[83.0072,16.31915],[82.21618,16.76378],[82.19078,16.71874]]]]}},{"type":"Feature","properties":{"id":"DK"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-74.12379,75.70014],[-53.68108,62.9266],[-45.64471,55.43944],[-25.70385,67.46637],[-10.71459,70.09565],[-9.68082,72.73731],[-3.52068,82.6752],[-34.32457,84.11035],[-59.93819,82.31398],[-63.1988,81.66522],[-67.48417,80.75493],[-73.91222,78.42484],[-74.12379,75.70014]]],[[[-8.51774,62.35338],[-6.51083,60.95272],[-5.70102,62.77194],[-8.51774,62.35338]]],[[[7.28637,57.35913],[8.02459,55.09613],[8.45719,55.06747],[8.55769,54.91837],[8.63979,54.91069],[8.76387,54.8948],[8.81178,54.90518],[8.92795,54.90452],[9.04629,54.87249],[9.14275,54.87421],[9.20571,54.85841],[9.24631,54.84726],[9.23445,54.83432],[9.2474,54.8112],[9.32771,54.80602],[9.33849,54.80233],[9.36496,54.81749],[9.38532,54.83968],[9.41213,54.84254],[9.43155,54.82586],[9.4659,54.83131],[9.58937,54.88785],[9.62734,54.88057],[9.61187,54.85548],[9.73563,54.8247],[9.89314,54.84171],[10.16755,54.73883],[10.31111,54.65968],[11.00303,54.63689],[11.90309,54.38543],[12.85844,54.82438],[13.93395,54.84044],[15.36991,54.73263],[15.79951,55.54655],[14.89259,55.5623],[14.28399,55.1553],[12.84405,55.13257],[12.60345,55.42675],[12.88472,55.63369],[12.6372,55.91371],[12.65312,56.04345],[12.07466,56.29488],[12.16597,56.60205],[10.40861,58.38489],[7.28637,57.35913]]]]}},{"type":"Feature","properties":{"id":"ES"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-18.8556,26.96222],[-14.43883,27.02969],[-12.42686,29.61659],[-14.33337,30.94071],[-15.92339,29.50503],[-18.67893,29.62861],[-18.8556,26.96222]]],[[[-10.14298,44.17365],[-9.14112,41.86623],[-8.87157,41.86488],[-8.81794,41.90375],[-8.75712,41.92833],[-8.74606,41.9469],[-8.7478,41.96282],[-8.69071,41.98862],[-8.6681,41.99703],[-8.65832,42.02972],[-8.64626,42.03668],[-8.63791,42.04691],[-8.59493,42.05708],[-8.58086,42.05147],[-8.54563,42.0537],[-8.5252,42.06264],[-8.52837,42.07658],[-8.48185,42.0811],[-8.44123,42.08218],[-8.42512,42.07199],[-8.40143,42.08052],[-8.38323,42.07683],[-8.36353,42.09065],[-8.33912,42.08358],[-8.32161,42.10218],[-8.29809,42.106],[-8.2732,42.12396],[-8.24681,42.13993],[-8.22406,42.1328],[-8.1986,42.15402],[-8.18947,42.13853],[-8.19406,42.12141],[-8.18178,42.06436],[-8.11729,42.08537],[-8.08847,42.05767],[-8.08796,42.01398],[-8.16232,41.9828],[-8.2185,41.91237],[-8.19551,41.87459],[-8.16944,41.87944],[-8.16455,41.81753],[-8.0961,41.81024],[-8.01136,41.83453],[-7.9804,41.87337],[-7.92336,41.8758],[-7.90707,41.92432],[-7.88751,41.92553],[-7.88055,41.84571],[-7.84188,41.88065],[-7.69848,41.90977],[-7.65774,41.88308],[-7.58603,41.87944],[-7.62188,41.83089],[-7.52737,41.83939],[-7.49803,41.87095],[-7.45566,41.86488],[-7.44759,41.84451],[-7.42854,41.83262],[-7.42864,41.80589],[-7.37092,41.85031],[-7.32366,41.8406],[-7.18677,41.88793],[-7.18549,41.97515],[-7.14115,41.98855],[-7.08574,41.97401],[-7.07596,41.94977],[-7.01078,41.94977],[-6.98144,41.9728],[-6.95537,41.96553],[-6.94396,41.94403],[-6.82174,41.94493],[-6.81196,41.99097],[-6.76959,41.98734],[-6.75004,41.94129],[-6.61967,41.94008],[-6.58544,41.96674],[-6.5447,41.94371],[-6.56752,41.88429],[-6.51374,41.8758],[-6.56426,41.74219],[-6.54633,41.68623],[-6.49907,41.65823],[-6.44204,41.68258],[-6.29863,41.66432],[-6.19128,41.57638],[-6.26777,41.48796],[-6.3306,41.37677],[-6.38553,41.38655],[-6.38551,41.35274],[-6.55937,41.24417],[-6.65046,41.24725],[-6.68286,41.21641],[-6.69711,41.1858],[-6.77319,41.13049],[-6.75655,41.10187],[-6.79241,41.05397],[-6.80942,41.03629],[-6.84781,41.02692],[-6.88843,41.03027],[-6.913,41.03922],[-6.9357,41.02888],[-6.8527,40.93958],[-6.84292,40.89771],[-6.80707,40.88047],[-6.79892,40.84842],[-6.82337,40.84472],[-6.82826,40.74603],[-6.79567,40.65955],[-6.84292,40.56801],[-6.80218,40.55067],[-6.7973,40.51723],[-6.84944,40.46394],[-6.84618,40.42177],[-6.78426,40.36468],[-6.80218,40.33239],[-6.86085,40.2976],[-6.86085,40.26776],[-7.00426,40.23169],[-7.02544,40.18564],[-7.00589,40.12087],[-6.94233,40.10716],[-6.86737,40.01986],[-6.91463,39.86618],[-6.97492,39.81488],[-7.01613,39.66877],[-7.24707,39.66576],[-7.33507,39.64569],[-7.54121,39.66717],[-7.49477,39.58794],[-7.2927,39.45847],[-7.3149,39.34857],[-7.23403,39.27579],[-7.23566,39.20132],[-7.12811,39.17101],[-7.14929,39.11287],[-7.10692,39.10275],[-7.04011,39.11919],[-6.97004,39.07619],[-6.95211,39.0243],[-7.051,38.907],[-7.03848,38.87221],[-7.26174,38.72107],[-7.265,38.61674],[-7.32529,38.44336],[-7.15581,38.27597],[-7.09389,38.17227],[-6.93418,38.21454],[-7.00375,38.01914],[-7.05966,38.01966],[-7.10366,38.04404],[-7.12648,38.00296],[-7.24544,37.98884],[-7.27314,37.90145],[-7.33441,37.81193],[-7.41981,37.75729],[-7.51759,37.56119],[-7.46878,37.47127],[-7.43974,37.38913],[-7.43227,37.25152],[-7.41854,37.23813],[-7.41133,37.20314],[-7.39769,37.16868],[-7.37282,36.96896],[-7.27694,35.93599],[-5.64962,35.93752],[-5.10878,36.05227],[-2.85819,35.63219],[-2.27707,35.35051],[2.46645,37.97429],[5.18061,39.43581],[3.4481,42.4358],[3.17156,42.43545],[3.11379,42.43646],[3.10027,42.42621],[3.08167,42.42748],[3.03734,42.47363],[2.96518,42.46692],[2.94283,42.48174],[2.92107,42.4573],[2.88413,42.45938],[2.86983,42.46843],[2.85675,42.45444],[2.84335,42.45724],[2.77464,42.41046],[2.75497,42.42578],[2.72056,42.42298],[2.65311,42.38771],[2.6747,42.33974],[2.57934,42.35808],[2.55516,42.35351],[2.54382,42.33406],[2.48457,42.33933],[2.43508,42.37568],[2.43299,42.39423],[2.38504,42.39977],[2.25551,42.43757],[2.20578,42.41633],[2.16599,42.42314],[2.12789,42.41291],[2.11621,42.38393],[2.06241,42.35906],[2.00488,42.35399],[1.96482,42.37787],[1.9574,42.42401],[1.94084,42.43039],[1.94061,42.43333],[1.94292,42.44316],[1.93663,42.45439],[1.88853,42.4501],[1.83037,42.48395],[1.76335,42.48863],[1.72515,42.50338],[1.70571,42.48867],[1.66826,42.50779],[1.65674,42.47125],[1.58933,42.46275],[1.57953,42.44957],[1.55937,42.45808],[1.55073,42.43299],[1.5127,42.42959],[1.44529,42.43724],[1.43838,42.47848],[1.41648,42.48315],[1.46661,42.50949],[1.44759,42.54431],[1.41245,42.53539],[1.4234,42.55959],[1.44529,42.56722],[1.42512,42.58292],[1.44197,42.60217],[1.35562,42.71944],[1.15928,42.71407],[1.0804,42.78569],[0.98292,42.78754],[0.96166,42.80629],[0.93089,42.79154],[0.711,42.86372],[0.66121,42.84021],[0.65421,42.75872],[0.67873,42.69458],[0.40214,42.69779],[0.36251,42.72282],[0.29407,42.67431],[0.25336,42.7174],[0.17569,42.73424],[-0.02468,42.68513],[-0.10519,42.72761],[-0.16141,42.79535],[-0.17939,42.78974],[-0.3122,42.84788],[-0.38833,42.80132],[-0.41319,42.80776],[-0.44334,42.79939],[-0.50863,42.82713],[-0.55497,42.77846],[-0.67637,42.88303],[-0.69837,42.87945],[-0.72608,42.89318],[-0.73422,42.91228],[-0.72037,42.92541],[-0.75478,42.96916],[-0.81652,42.95166],[-0.97133,42.96239],[-1.00963,42.99279],[-1.10333,43.0059],[-1.22881,43.05534],[-1.25244,43.04164],[-1.30531,43.06859],[-1.30052,43.09581],[-1.27118,43.11961],[-1.32209,43.1127],[-1.34419,43.09665],[-1.35272,43.02658],[-1.44067,43.047],[-1.47555,43.08372],[-1.41562,43.12815],[-1.3758,43.24511],[-1.40942,43.27272],[-1.45289,43.27049],[-1.50992,43.29481],[-1.55963,43.28828],[-1.57674,43.25269],[-1.61341,43.25269],[-1.63052,43.28591],[-1.62481,43.30726],[-1.69407,43.31378],[-1.73074,43.29481],[-1.7397,43.32979],[-1.75079,43.3317],[-1.75334,43.34107],[-1.77068,43.34396],[-1.78714,43.35476],[-1.78332,43.36399],[-1.79319,43.37497],[-1.77289,43.38957],[-1.81005,43.59738],[-10.14298,44.17365]],[[-5.40134,36.14896],[-5.38545,36.15481],[-5.36494,36.15496],[-5.34536,36.15501],[-5.33822,36.15272],[-5.27801,36.14942],[-5.28217,36.09907],[-5.3004,36.07439],[-5.32837,36.05935],[-5.36503,36.06205],[-5.39074,36.10278],[-5.40134,36.14896]]],[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]],[[[-4.30191,35.17419],[-4.30112,35.17058],[-4.29436,35.17149],[-4.30191,35.17419]]],[[[-3.90602,35.21494],[-3.88926,35.20841],[-3.88617,35.21406],[-3.90288,35.22024],[-3.90602,35.21494]]],[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]],[[[-2.44896,35.18777],[-2.44887,35.17075],[-2.41312,35.17111],[-2.41265,35.1877],[-2.44896,35.18777]]],[[[1.95606,42.45785],[1.96125,42.45364],[1.98378,42.44697],[1.99838,42.44682],[2.01564,42.45171],[1.99216,42.46208],[1.98579,42.47486],[1.99766,42.4858],[1.98916,42.49351],[1.98022,42.49569],[1.97697,42.48568],[1.97227,42.48487],[1.97003,42.48081],[1.96215,42.47854],[1.95606,42.45785]]]]}},{"type":"Feature","properties":{"id":"FJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-180,-22.90585],[-176.74538,-22.89767],[-176.76826,-14.95183],[-178.60161,-14.95666],[-180,-14.96041],[-180,-22.90585]]],[[[174,-22.5],[179.99999,-22.5],[179.99999,-11.5],[174,-11.5],[174,-22.5]]]]}},{"type":"Feature","properties":{"id":"FR"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-178.60852,-12.49232],[-178.60161,-14.95666],[-176.76826,-14.95183],[-174.17905,-14.94502],[-174.18596,-12.48057],[-178.60852,-12.49232]]],[[[-156.4957,-12.32002],[-156.46451,-23.21255],[-156.44843,-28.52556],[-133.59543,-28.4709],[-133.61511,-21.93325],[-133.65593,-7.46952],[-149.6249,-7.51261],[-149.61166,-12.30171],[-156.4957,-12.32002]]],[[[-109.6462,9.84394],[-108.755,9.84085],[-108.75183,10.72712],[-109.64303,10.7302],[-109.6462,9.84394]]],[[[-63.35989,18.06012],[-63.33064,17.9615],[-63.13584,18.0541],[-63.11096,18.05368],[-63.09686,18.04608],[-63.07759,18.04943],[-63.0579,18.06614],[-63.04039,18.05619],[-63.02323,18.05757],[-62.93924,18.02904],[-63.07669,17.79659],[-62.76692,17.64353],[-62.54836,17.8636],[-62.75637,18.13489],[-62.86666,18.19278],[-63.35989,18.06012]]],[[[-62.17275,16.35721],[-61.81728,15.58058],[-61.44899,15.79571],[-60.95725,15.70997],[-60.71337,16.48911],[-61.44461,16.81958],[-61.83929,16.66647],[-62.17275,16.35721]]],[[[-61.51867,14.96709],[-61.26561,14.25664],[-60.5958,14.23076],[-60.69955,15.22234],[-61.51867,14.96709]]],[[[-56.70773,46.51478],[-55.8643,46.64935],[-56.25228,47.31192],[-56.67989,47.3339],[-56.70773,46.51478]]],[[[-54.6084,2.32856],[-54.16286,2.10779],[-53.78743,2.34412],[-52.96539,2.1881],[-52.6906,2.37298],[-52.31787,3.17896],[-51.85573,3.83427],[-51.82312,3.85825],[-51.79599,3.89336],[-51.61983,4.14596],[-51.63798,4.51394],[-51.35485,4.8383],[-53.7094,6.2264],[-54.01074,5.68785],[-54.01877,5.52789],[-54.26916,5.26909],[-54.4717,4.91964],[-54.38444,4.13222],[-54.19367,3.84387],[-54.05128,3.63557],[-53.98914,3.627],[-53.9849,3.58697],[-54.28534,2.67798],[-54.42864,2.42442],[-54.6084,2.32856]]],[[[-5.81385,48.52441],[-1.81005,43.59738],[-1.77289,43.38957],[-1.79319,43.37497],[-1.78332,43.36399],[-1.78714,43.35476],[-1.77068,43.34396],[-1.75334,43.34107],[-1.75079,43.3317],[-1.7397,43.32979],[-1.73074,43.29481],[-1.69407,43.31378],[-1.62481,43.30726],[-1.63052,43.28591],[-1.61341,43.25269],[-1.57674,43.25269],[-1.55963,43.28828],[-1.50992,43.29481],[-1.45289,43.27049],[-1.40942,43.27272],[-1.3758,43.24511],[-1.41562,43.12815],[-1.47555,43.08372],[-1.44067,43.047],[-1.35272,43.02658],[-1.34419,43.09665],[-1.32209,43.1127],[-1.27118,43.11961],[-1.30052,43.09581],[-1.30531,43.06859],[-1.25244,43.04164],[-1.22881,43.05534],[-1.10333,43.0059],[-1.00963,42.99279],[-0.97133,42.96239],[-0.81652,42.95166],[-0.75478,42.96916],[-0.72037,42.92541],[-0.73422,42.91228],[-0.72608,42.89318],[-0.69837,42.87945],[-0.67637,42.88303],[-0.55497,42.77846],[-0.50863,42.82713],[-0.44334,42.79939],[-0.41319,42.80776],[-0.38833,42.80132],[-0.3122,42.84788],[-0.17939,42.78974],[-0.16141,42.79535],[-0.10519,42.72761],[-0.02468,42.68513],[0.17569,42.73424],[0.25336,42.7174],[0.29407,42.67431],[0.36251,42.72282],[0.40214,42.69779],[0.67873,42.69458],[0.65421,42.75872],[0.66121,42.84021],[0.711,42.86372],[0.93089,42.79154],[0.96166,42.80629],[0.98292,42.78754],[1.0804,42.78569],[1.15928,42.71407],[1.35562,42.71944],[1.44197,42.60217],[1.47986,42.61346],[1.46718,42.63296],[1.48043,42.65203],[1.50867,42.64483],[1.55418,42.65669],[1.60085,42.62703],[1.63485,42.62957],[1.6625,42.61982],[1.68267,42.62533],[1.73452,42.61515],[1.72588,42.59098],[1.7858,42.57698],[1.73683,42.55492],[1.72515,42.50338],[1.76335,42.48863],[1.83037,42.48395],[1.88853,42.4501],[1.93663,42.45439],[1.94292,42.44316],[1.94061,42.43333],[1.94084,42.43039],[1.9574,42.42401],[1.96482,42.37787],[2.00488,42.35399],[2.06241,42.35906],[2.11621,42.38393],[2.12789,42.41291],[2.16599,42.42314],[2.20578,42.41633],[2.25551,42.43757],[2.38504,42.39977],[2.43299,42.39423],[2.43508,42.37568],[2.48457,42.33933],[2.54382,42.33406],[2.55516,42.35351],[2.57934,42.35808],[2.6747,42.33974],[2.65311,42.38771],[2.72056,42.42298],[2.75497,42.42578],[2.77464,42.41046],[2.84335,42.45724],[2.85675,42.45444],[2.86983,42.46843],[2.88413,42.45938],[2.92107,42.4573],[2.94283,42.48174],[2.96518,42.46692],[3.03734,42.47363],[3.08167,42.42748],[3.10027,42.42621],[3.11379,42.43646],[3.17156,42.43545],[3.4481,42.4358],[7.52234,41.54558],[8.80584,41.26173],[9.28609,41.32097],[9.62656,41.44198],[9.86526,42.21008],[9.56115,43.20816],[7.50102,43.51859],[7.41855,43.72479],[7.40903,43.7296],[7.41113,43.73156],[7.41291,43.73168],[7.41298,43.73311],[7.41233,43.73439],[7.42062,43.73977],[7.42299,43.74176],[7.42443,43.74087],[7.42809,43.74396],[7.43013,43.74895],[7.43624,43.75014],[7.43708,43.75197],[7.4389,43.75151],[7.4379,43.74963],[7.45448,43.7432],[7.53358,43.53609],[7.63035,43.57419],[7.5289,43.78887],[7.50423,43.84345],[7.49355,43.86551],[7.51162,43.88301],[7.56075,43.89932],[7.56858,43.94506],[7.60771,43.95772],[7.65266,43.9763],[7.66848,43.99943],[7.6597,44.03009],[7.72508,44.07578],[7.66878,44.12795],[7.68694,44.17487],[7.63245,44.17877],[7.62155,44.14881],[7.36364,44.11882],[7.34547,44.14359],[7.27827,44.1462],[7.16929,44.20352],[7.00764,44.23736],[6.98221,44.28289],[6.89171,44.36637],[6.88784,44.42043],[6.94504,44.43112],[6.86233,44.49834],[6.85507,44.53072],[6.96042,44.62129],[6.95133,44.66264],[7.00582,44.69364],[7.07484,44.68073],[7.00401,44.78782],[7.02217,44.82519],[6.93499,44.8664],[6.90774,44.84322],[6.75518,44.89915],[6.74519,44.93661],[6.74791,45.01939],[6.66981,45.02324],[6.62803,45.11175],[6.7697,45.16044],[6.85144,45.13226],[6.96706,45.20841],[7.07074,45.21228],[7.13115,45.25386],[7.10572,45.32924],[7.18019,45.40071],[7.00037,45.509],[6.98948,45.63869],[6.80785,45.71864],[6.80785,45.83265],[6.95315,45.85163],[7.04151,45.92435],[7.00946,45.9944],[6.93862,46.06502],[6.87868,46.03855],[6.89321,46.12548],[6.78968,46.14058],[6.86052,46.28512],[6.77152,46.34784],[6.8024,46.39171],[6.82312,46.42661],[6.53358,46.45431],[6.25432,46.3632],[6.21981,46.31304],[6.24826,46.30175],[6.25137,46.29014],[6.23775,46.27822],[6.24952,46.26255],[6.26749,46.24745],[6.29474,46.26221],[6.31041,46.24417],[6.29663,46.22688],[6.27694,46.21566],[6.26007,46.21165],[6.24821,46.20531],[6.23913,46.20511],[6.23544,46.20714],[6.22175,46.20045],[6.22222,46.19888],[6.21844,46.19837],[6.21603,46.19507],[6.21273,46.19409],[6.21114,46.1927],[6.20539,46.19163],[6.19807,46.18369],[6.19552,46.18401],[6.18707,46.17999],[6.18871,46.16644],[6.18116,46.16187],[6.15305,46.15194],[6.13397,46.1406],[6.09926,46.14373],[6.09199,46.15191],[6.07491,46.14879],[6.05203,46.15191],[6.04564,46.14031],[6.03614,46.13712],[6.01791,46.14228],[5.9871,46.14499],[5.97893,46.13303],[5.95781,46.12925],[5.9641,46.14412],[5.97508,46.15863],[5.98188,46.17392],[5.98846,46.17046],[5.99573,46.18587],[5.96515,46.19638],[5.97542,46.21525],[6.02461,46.23313],[6.03342,46.2383],[6.04602,46.23127],[6.05029,46.23518],[6.0633,46.24583],[6.07072,46.24085],[6.08563,46.24651],[6.10071,46.23772],[6.12446,46.25059],[6.11926,46.2634],[6.1013,46.28512],[6.11697,46.29547],[6.1198,46.31157],[6.13876,46.33844],[6.15738,46.3491],[6.16987,46.36759],[6.15985,46.37721],[6.15016,46.3778],[6.09926,46.40768],[6.06407,46.41676],[6.08427,46.44305],[6.07269,46.46244],[6.1567,46.54402],[6.11084,46.57649],[6.27135,46.68251],[6.38351,46.73171],[6.45209,46.77502],[6.43216,46.80336],[6.46456,46.88865],[6.43341,46.92703],[6.71531,47.0494],[6.68823,47.06616],[6.76788,47.1208],[6.8489,47.15933],[6.9508,47.24338],[6.95108,47.26428],[6.94316,47.28747],[7.05305,47.33304],[7.0564,47.35134],[7.03125,47.36996],[6.87959,47.35335],[6.88542,47.37262],[6.93744,47.40714],[6.93953,47.43388],[7.0024,47.45264],[6.98425,47.49432],[7.0231,47.50522],[7.07425,47.48863],[7.12781,47.50371],[7.16249,47.49025],[7.19583,47.49455],[7.17026,47.44312],[7.24669,47.4205],[7.33526,47.44186],[7.35603,47.43432],[7.40308,47.43638],[7.43088,47.45846],[7.4462,47.46264],[7.4583,47.47216],[7.42923,47.48628],[7.43356,47.49712],[7.47534,47.47932],[7.51076,47.49651],[7.49804,47.51798],[7.5229,47.51644],[7.53199,47.5284],[7.51904,47.53515],[7.50588,47.52856],[7.49691,47.53821],[7.50873,47.54546],[7.51723,47.54578],[7.52831,47.55347],[7.53634,47.55553],[7.55652,47.56779],[7.55689,47.57232],[7.56548,47.57617],[7.56684,47.57785],[7.58386,47.57536],[7.58945,47.59017],[7.59301,47.60058],[7.58851,47.60794],[7.57423,47.61628],[7.5591,47.63849],[7.53384,47.65115],[7.52067,47.66437],[7.51915,47.68335],[7.51266,47.70197],[7.53722,47.71635],[7.54761,47.72912],[7.52921,47.77747],[7.55673,47.87371],[7.62302,47.97898],[7.56966,48.03265],[7.57137,48.12292],[7.6648,48.22219],[7.69022,48.30018],[7.74562,48.32736],[7.73109,48.39192],[7.76833,48.48945],[7.80647,48.51239],[7.80167,48.54758],[7.80057,48.5857],[7.84098,48.64217],[7.89002,48.66317],[7.96812,48.72491],[7.96994,48.75606],[8.01534,48.76085],[8.0326,48.79017],[8.06802,48.78957],[8.10253,48.81829],[8.12813,48.87985],[8.19989,48.95825],[8.20031,48.95856],[8.22604,48.97352],[8.14189,48.97833],[7.97783,49.03161],[7.93641,49.05544],[7.86386,49.03499],[7.79557,49.06583],[7.75948,49.04562],[7.63618,49.05428],[7.62575,49.07654],[7.56416,49.08136],[7.53012,49.09818],[7.49172,49.13915],[7.49473,49.17],[7.44455,49.16765],[7.44052,49.18354],[7.3662,49.17308],[7.35995,49.14399],[7.3195,49.14231],[7.29514,49.11426],[7.23473,49.12971],[7.1593,49.1204],[7.1358,49.1282],[7.12504,49.14253],[7.10384,49.13787],[7.10715,49.15631],[7.07859,49.15031],[7.09007,49.13094],[7.07162,49.1255],[7.06642,49.11415],[7.05548,49.11185],[7.04843,49.11422],[7.04409,49.12123],[7.04662,49.13724],[7.03178,49.15734],[7.0274,49.17042],[7.03459,49.19096],[7.01318,49.19018],[6.97273,49.2099],[6.95963,49.203],[6.94028,49.21641],[6.93831,49.2223],[6.91875,49.22261],[6.89298,49.20863],[6.85939,49.22376],[6.83555,49.21249],[6.85119,49.20038],[6.85016,49.19354],[6.86225,49.18185],[6.84703,49.15734],[6.83385,49.15162],[6.78265,49.16793],[6.73765,49.16375],[6.71137,49.18808],[6.73256,49.20486],[6.71843,49.2208],[6.69274,49.21661],[6.66583,49.28065],[6.60186,49.31055],[6.572,49.35027],[6.58807,49.35358],[6.60091,49.36864],[6.533,49.40748],[6.55404,49.42464],[6.42432,49.47683],[6.40274,49.46546],[6.39168,49.4667],[6.38352,49.46463],[6.36778,49.46937],[6.3687,49.4593],[6.28818,49.48465],[6.27875,49.503],[6.25029,49.50609],[6.2409,49.51408],[6.19543,49.50536],[6.17386,49.50934],[6.15366,49.50226],[6.16115,49.49297],[6.14321,49.48796],[6.12814,49.49365],[6.12346,49.4735],[6.10325,49.4707],[6.09845,49.46351],[6.10072,49.45268],[6.08373,49.45594],[6.07887,49.46399],[6.05553,49.46663],[6.04176,49.44801],[6.02743,49.44845],[6.02648,49.45451],[5.97693,49.45513],[5.96876,49.49053],[5.94224,49.49608],[5.94128,49.50034],[5.86571,49.50015],[5.83389,49.52152],[5.83467,49.52717],[5.84466,49.53027],[5.83648,49.5425],[5.81664,49.53775],[5.80871,49.5425],[5.81838,49.54777],[5.79195,49.55228],[5.77435,49.56298],[5.7577,49.55915],[5.75649,49.54321],[5.64505,49.55146],[5.60909,49.51228],[5.55001,49.52729],[5.46541,49.49825],[5.46734,49.52648],[5.43713,49.5707],[5.3974,49.61596],[5.34837,49.62889],[5.33851,49.61599],[5.3137,49.61225],[5.30214,49.63055],[5.33039,49.6555],[5.31465,49.66846],[5.26232,49.69456],[5.14545,49.70287],[5.09249,49.76193],[4.96714,49.79872],[4.85464,49.78995],[4.86965,49.82271],[4.85134,49.86457],[4.88529,49.9236],[4.78827,49.95609],[4.8382,50.06738],[4.88602,50.15182],[4.83279,50.15331],[4.82438,50.16878],[4.75237,50.11314],[4.70064,50.09384],[4.68695,49.99685],[4.5414,49.96911],[4.51098,49.94659],[4.43488,49.94122],[4.35051,49.95315],[4.31963,49.97043],[4.20532,49.95803],[4.14239,49.98034],[4.13508,50.01976],[4.16294,50.04719],[4.23101,50.06945],[4.20147,50.13535],[4.13561,50.13078],[4.16014,50.19239],[4.15524,50.21103],[4.21945,50.25539],[4.20651,50.27333],[4.17861,50.27443],[4.17347,50.28838],[4.15524,50.2833],[4.16808,50.25786],[4.13665,50.25609],[4.11954,50.30425],[4.10957,50.30234],[4.10237,50.31247],[4.0689,50.3254],[4.0268,50.35793],[3.96771,50.34989],[3.90781,50.32814],[3.84314,50.35219],[3.73911,50.34809],[3.70987,50.3191],[3.71009,50.30305],[3.66976,50.34563],[3.65709,50.36873],[3.67262,50.38663],[3.67494,50.40239],[3.66153,50.45165],[3.64426,50.46275],[3.61014,50.49568],[3.58361,50.49049],[3.5683,50.50192],[3.49509,50.48885],[3.51564,50.5256],[3.47385,50.53397],[3.44629,50.51009],[3.37693,50.49538],[3.28575,50.52724],[3.2729,50.60718],[3.23951,50.6585],[3.264,50.67668],[3.2536,50.68977],[3.26141,50.69151],[3.26063,50.70086],[3.24593,50.71389],[3.22042,50.71019],[3.20845,50.71662],[3.19017,50.72569],[3.20064,50.73547],[3.18811,50.74025],[3.18339,50.74981],[3.16476,50.76843],[3.15017,50.79031],[3.1257,50.78603],[3.11987,50.79188],[3.11206,50.79416],[3.10614,50.78303],[3.09163,50.77717],[3.04314,50.77674],[3.00537,50.76588],[2.96778,50.75242],[2.95019,50.75138],[2.90873,50.702],[2.91036,50.6939],[2.90069,50.69263],[2.88504,50.70656],[2.87937,50.70298],[2.86985,50.7033],[2.8483,50.72276],[2.81056,50.71773],[2.71165,50.81295],[2.63331,50.81457],[2.59093,50.91751],[2.63074,50.94746],[2.57551,51.00326],[2.55904,51.07014],[2.18458,51.52087],[1.17405,50.74239],[-2.02963,49.91866],[-2.09454,49.46288],[-1.83944,49.23037],[-2.00491,48.86706],[-2.5234,48.91595],[-2.56423,49.22209],[-2.9511,49.31141],[-5.81385,48.52441]],[[1.95606,42.45785],[1.96215,42.47854],[1.97003,42.48081],[1.97227,42.48487],[1.97697,42.48568],[1.98022,42.49569],[1.98916,42.49351],[1.99766,42.4858],[1.98579,42.47486],[1.99216,42.46208],[2.01564,42.45171],[1.99838,42.44682],[1.98378,42.44697],[1.96125,42.45364],[1.95606,42.45785]]],[[[39.10324,-21.48967],[40.40841,-23.17181],[43.72277,-16.09877],[41.06663,-17.08802],[39.10324,-21.48967]]],[[[44.75722,-12.58368],[44.82644,-13.30845],[45.54824,-13.22353],[45.45962,-12.30345],[44.75722,-12.58368]]],[[[46.31615,-46.28749],[70.67507,-51.14192],[80.15867,-36.04977],[46.31615,-46.28749]]],[[[46.52682,-10.83678],[47.29063,-12.45583],[48.86266,-10.8109],[46.52682,-10.83678]]],[[[54.08717,-15.5001],[54.13761,-16.33002],[54.96649,-16.28353],[54.91606,-15.45342],[54.08717,-15.5001]]],[[[54.32269,-20.37973],[54.43368,-22.02482],[56.73473,-21.9174],[56.62373,-20.2711],[54.32269,-20.37973]]],[[[157.46481,-18.93777],[158.4748,-21.86428],[166.93331,-23.49588],[173.07304,-22.54607],[162.93363,-17.28904],[157.46481,-18.93777]]]]}},{"type":"Feature","properties":{"id":"GB"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-133.61511,-21.93325],[-133.59543,-28.4709],[-122.0366,-24.55017],[-133.61511,-21.93325]]],[[[-81.81969,19.51758],[-81.52417,18.7521],[-79.33932,19.50496],[-79.63484,20.26689],[-81.81969,19.51758]]],[[[-72.94479,20.79216],[-71.13087,20.98822],[-70.63262,21.53631],[-72.41726,22.40371],[-72.94479,20.79216]]],[[[-65.23529,32.66274],[-65.22652,31.98296],[-64.37503,31.99084],[-64.3838,32.67056],[-65.23529,32.66274]]],[[[-65.02435,18.73231],[-64.86049,18.39954],[-64.64067,18.36478],[-64.646,18.10286],[-63.95092,18.07976],[-63.35989,18.06012],[-62.86666,18.19278],[-62.75637,18.13489],[-62.64209,18.3662],[-63.3414,18.89029],[-63.90607,18.93262],[-64.62855,18.98678],[-65.02435,18.73231]]],[[[-62.78369,-53.1401],[-58.84651,-53.93403],[-55.76919,-51.15168],[-62.3754,-50.36819],[-62.78369,-53.1401]]],[[[-62.52079,16.69392],[-62.17275,16.35721],[-61.83929,16.66647],[-62.14123,17.02632],[-62.52079,16.69392]]],[[[-43.57991,-52.56305],[-40.68557,-57.40649],[-26.52505,-59.90465],[-23.50385,-54.792],[-43.57991,-52.56305]]],[[[-14.91926,-6.63386],[-14.82771,-8.70814],[-13.48367,-36.6746],[-13.41694,-37.88844],[-13.29655,-40.02846],[-9.34669,-41.00353],[-4.97086,-15.55882],[-13.33271,-8.07391],[-14.91926,-6.63386]]],[[[-14.78497,57.60709],[-7.93366,55.84142],[-6.79943,55.54107],[-6.71944,55.27952],[-6.9734,55.19878],[-7.2471,55.06933],[-7.34464,55.04688],[-7.4033,55.00391],[-7.40004,54.94498],[-7.44404,54.9403],[-7.4473,54.87003],[-7.47626,54.83084],[-7.54508,54.79401],[-7.54671,54.74606],[-7.64449,54.75265],[-7.75041,54.7103],[-7.83352,54.73854],[-7.93293,54.66603],[-7.70315,54.62077],[-7.8596,54.53671],[-7.99812,54.54427],[-8.04538,54.48941],[-8.179,54.46763],[-8.04555,54.36292],[-7.87101,54.29299],[-7.8596,54.21779],[-7.81397,54.20159],[-7.69501,54.20731],[-7.55812,54.12239],[-7.4799,54.12239],[-7.44567,54.1539],[-7.32834,54.11475],[-7.30553,54.11869],[-7.34005,54.14698],[-7.29157,54.17191],[-7.28017,54.16714],[-7.29687,54.1354],[-7.29493,54.12013],[-7.26316,54.13863],[-7.25012,54.20063],[-7.14908,54.22732],[-7.19145,54.31296],[-7.02034,54.4212],[-6.87775,54.34682],[-6.85179,54.29176],[-6.81583,54.22791],[-6.74575,54.18788],[-6.70175,54.20218],[-6.6382,54.17071],[-6.66264,54.0666],[-6.62842,54.03503],[-6.47849,54.06947],[-6.36605,54.07234],[-6.36279,54.11248],[-6.32694,54.09337],[-6.29003,54.11278],[-6.26218,54.09785],[-5.83481,53.87749],[-5.37267,53.63269],[-5.79914,52.03902],[-6.81839,49.7273],[-3.06097,49.47231],[-2.9511,49.31141],[-2.56423,49.22209],[-2.5234,48.91595],[-2.00491,48.86706],[-1.83944,49.23037],[-2.09454,49.46288],[-2.02963,49.91866],[1.17405,50.74239],[2.18458,51.52087],[2.56575,51.85301],[-0.3751,61.32236],[-14.78497,57.60709]]],[[[-5.40134,36.14896],[-5.39074,36.10278],[-5.36503,36.06205],[-5.32837,36.05935],[-5.3004,36.07439],[-5.28217,36.09907],[-5.27801,36.14942],[-5.33822,36.15272],[-5.34536,36.15501],[-5.36494,36.15496],[-5.38545,36.15481],[-5.40134,36.14896]]],[[[32.74412,34.43926],[33.26744,34.49942],[33.0138,34.64424],[32.96968,34.64046],[32.96718,34.63446],[32.95891,34.62919],[32.95323,34.64075],[32.95471,34.64528],[32.94976,34.65204],[32.94796,34.6587],[32.95325,34.66462],[32.97079,34.66112],[32.97736,34.65277],[32.99014,34.65518],[32.98668,34.67268],[32.99135,34.68061],[32.95539,34.68471],[32.94683,34.67907],[32.94379,34.67111],[32.93693,34.67027],[32.93449,34.66241],[32.92807,34.66736],[32.93043,34.67091],[32.91398,34.67343],[32.9068,34.66102],[32.86167,34.68734],[32.86014,34.70585],[32.82717,34.70622],[32.79433,34.67883],[32.76136,34.68318],[32.75515,34.64985],[32.74412,34.43926]]],[[[33.67678,35.03866],[33.69938,35.03123],[33.69731,35.01754],[33.71514,35.00294],[33.70639,34.99303],[33.70575,34.97947],[33.83531,34.73974],[33.98684,34.76642],[33.90075,34.96623],[33.86432,34.97592],[33.84811,34.97075],[33.83505,34.98108],[33.85621,34.98956],[33.85891,35.001],[33.85216,35.00579],[33.84045,35.00616],[33.82875,35.01685],[33.83055,35.02865],[33.81524,35.04192],[33.8012,35.04786],[33.82051,35.0667],[33.8355,35.05777],[33.85261,35.0574],[33.88367,35.07877],[33.89485,35.06873],[33.90247,35.07686],[33.91299,35.07579],[33.91789,35.08688],[33.89853,35.11377],[33.88737,35.11408],[33.88943,35.12007],[33.88561,35.12449],[33.87224,35.12293],[33.87622,35.10457],[33.87097,35.09389],[33.87479,35.08881],[33.8541,35.07201],[33.84168,35.06823],[33.82067,35.07826],[33.78581,35.05104],[33.76106,35.04253],[33.73824,35.05321],[33.71482,35.03722],[33.70209,35.04882],[33.7161,35.07279],[33.70861,35.07644],[33.69095,35.06237],[33.68474,35.06602],[33.67742,35.05963],[33.67678,35.03866]],[[33.7343,35.01178],[33.73781,35.02181],[33.74265,35.02329],[33.74983,35.02274],[33.7492,35.01319],[33.74144,35.01053],[33.7343,35.01178]],[[33.75682,34.99916],[33.75994,35.00113],[33.77312,34.9976],[33.77553,34.99518],[33.78516,34.99582],[33.79191,34.98914],[33.78917,34.98854],[33.78571,34.98951],[33.78318,34.98699],[33.78149,34.98854],[33.77843,34.988],[33.7778,34.98981],[33.76738,34.99188],[33.76605,34.99543],[33.75682,34.99916]]],[[[70.64391,-7.71751],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[73.19979,-4.96078],[70.64754,-4.95745],[70.64391,-7.71751]]]]}},{"type":"Feature","properties":{"id":"IT"},"geometry":{"type":"MultiPolygon","coordinates":[[[[6.62803,45.11175],[6.66981,45.02324],[6.74791,45.01939],[6.74519,44.93661],[6.75518,44.89915],[6.90774,44.84322],[6.93499,44.8664],[7.02217,44.82519],[7.00401,44.78782],[7.07484,44.68073],[7.00582,44.69364],[6.95133,44.66264],[6.96042,44.62129],[6.85507,44.53072],[6.86233,44.49834],[6.94504,44.43112],[6.88784,44.42043],[6.89171,44.36637],[6.98221,44.28289],[7.00764,44.23736],[7.16929,44.20352],[7.27827,44.1462],[7.34547,44.14359],[7.36364,44.11882],[7.62155,44.14881],[7.63245,44.17877],[7.68694,44.17487],[7.66878,44.12795],[7.72508,44.07578],[7.6597,44.03009],[7.66848,43.99943],[7.65266,43.9763],[7.60771,43.95772],[7.56858,43.94506],[7.56075,43.89932],[7.51162,43.88301],[7.49355,43.86551],[7.50423,43.84345],[7.5289,43.78887],[7.63035,43.57419],[9.56115,43.20816],[9.86526,42.21008],[9.62656,41.44198],[9.28609,41.32097],[8.80584,41.26173],[7.52234,41.54558],[7.89009,38.19924],[11.2718,37.6713],[12.02012,35.25036],[12.80065,35.1178],[13.4634,35.88474],[14.02721,36.53141],[15.10171,36.26215],[18.75365,39.82496],[18.83516,40.36999],[16.15283,42.18525],[13.12821,44.48877],[13.05142,45.33128],[13.45644,45.59464],[13.6076,45.64761],[13.7198,45.59352],[13.74587,45.59811],[13.78445,45.5825],[13.84106,45.58185],[13.86771,45.59898],[13.8695,45.60835],[13.9191,45.6322],[13.87933,45.65207],[13.83422,45.68703],[13.83332,45.70855],[13.8235,45.7176],[13.66986,45.79955],[13.59784,45.8072],[13.58858,45.83503],[13.57563,45.8425],[13.58644,45.88173],[13.59565,45.89446],[13.60857,45.89907],[13.61931,45.91782],[13.63815,45.93607],[13.6329,45.94894],[13.64307,45.98326],[13.63458,45.98947],[13.62074,45.98388],[13.58903,45.99009],[13.56759,45.96991],[13.52963,45.96588],[13.50104,45.98078],[13.47474,46.00546],[13.49702,46.01832],[13.50998,46.04498],[13.49568,46.04839],[13.50104,46.05986],[13.57072,46.09022],[13.64053,46.13587],[13.66472,46.17392],[13.64451,46.18966],[13.56682,46.18703],[13.56114,46.2054],[13.47587,46.22725],[13.42218,46.20758],[13.37671,46.29668],[13.44808,46.33507],[13.43418,46.35992],[13.47019,46.3621],[13.5763,46.40915],[13.5763,46.42613],[13.59777,46.44137],[13.68684,46.43881],[13.7148,46.5222],[13.64088,46.53438],[13.27627,46.56059],[12.94445,46.60401],[12.59992,46.6595],[12.38708,46.71529],[12.27591,46.88651],[12.2006,46.88854],[12.11675,47.01241],[12.21781,47.03996],[12.19254,47.09331],[11.74789,46.98484],[11.50739,47.00644],[11.33355,46.99862],[11.10618,46.92966],[11.00764,46.76896],[10.72974,46.78972],[10.75753,46.82258],[10.66405,46.87614],[10.54783,46.84505],[10.47197,46.85698],[10.38659,46.67847],[10.40475,46.63671],[10.44686,46.64162],[10.49375,46.62049],[10.46136,46.53164],[10.25309,46.57432],[10.23674,46.63484],[10.10307,46.61003],[10.03715,46.44479],[10.165,46.41051],[10.10506,46.3372],[10.17862,46.25626],[10.14439,46.22992],[10.07055,46.21668],[9.95249,46.38045],[9.73086,46.35071],[9.71273,46.29266],[9.57015,46.2958],[9.46117,46.37481],[9.45936,46.50873],[9.40487,46.46621],[9.36128,46.5081],[9.28136,46.49685],[9.25502,46.43743],[9.29226,46.32717],[9.24503,46.23616],[9.01618,46.04928],[8.99257,45.9698],[9.09065,45.89906],[9.06642,45.8761],[9.04546,45.84968],[9.04059,45.8464],[9.03505,45.83976],[9.03793,45.83548],[9.03279,45.82865],[9.0298,45.82127],[9.00324,45.82055],[8.99663,45.83466],[8.9621,45.83707],[8.94737,45.84285],[8.91129,45.8388],[8.93504,45.86245],[8.94372,45.86587],[8.93649,45.86775],[8.88904,45.95465],[8.86688,45.96135],[8.85121,45.97239],[8.8319,45.9879],[8.79362,45.99207],[8.78585,45.98973],[8.79414,46.00913],[8.85617,46.0748],[8.80778,46.10085],[8.75697,46.10395],[8.62242,46.12112],[8.45032,46.26869],[8.46317,46.43712],[8.42464,46.46367],[8.30648,46.41587],[8.31162,46.38044],[8.08814,46.26692],[8.16866,46.17817],[8.11383,46.11577],[8.02906,46.10331],[7.98881,45.99867],[7.9049,45.99945],[7.85949,45.91485],[7.56343,45.97421],[7.10685,45.85653],[7.04151,45.92435],[6.95315,45.85163],[6.80785,45.83265],[6.80785,45.71864],[6.98948,45.63869],[7.00037,45.509],[7.18019,45.40071],[7.10572,45.32924],[7.13115,45.25386],[7.07074,45.21228],[6.96706,45.20841],[6.85144,45.13226],[6.7697,45.16044],[6.62803,45.11175]],[[12.40415,43.95485],[12.41414,43.95273],[12.42005,43.9578],[12.43662,43.95698],[12.44684,43.96597],[12.46205,43.97463],[12.47853,43.98052],[12.49406,43.98492],[12.50678,43.99113],[12.51463,43.99122],[12.5154,43.98508],[12.51064,43.98165],[12.51109,43.97201],[12.50622,43.97131],[12.50875,43.96198],[12.50655,43.95796],[12.51427,43.94897],[12.51553,43.94096],[12.50496,43.93017],[12.50269,43.92363],[12.49724,43.92248],[12.49247,43.91774],[12.49429,43.90973],[12.48771,43.89706],[12.45648,43.89369],[12.44184,43.90498],[12.41641,43.89991],[12.40935,43.9024],[12.41233,43.90956],[12.40733,43.92379],[12.41551,43.92984],[12.41165,43.93769],[12.40506,43.94325],[12.40415,43.95485]],[[12.44582,41.90194],[12.44815,41.90326],[12.44984,41.90545],[12.45091,41.90625],[12.45543,41.90738],[12.45561,41.90629],[12.45762,41.9058],[12.45755,41.9033],[12.45826,41.90281],[12.45834,41.90174],[12.4577,41.90115],[12.45691,41.90125],[12.45626,41.90172],[12.45435,41.90143],[12.45446,41.90028],[12.45181,41.90056],[12.44834,41.90095],[12.44582,41.90194]]],[[[8.95861,45.96485],[8.97604,45.96151],[8.97741,45.98317],[8.96668,45.98436],[8.95861,45.96485]]]]}},{"type":"Feature","properties":{"id":"KG"},"geometry":{"type":"Polygon","coordinates":[[[69.26938,39.8127],[69.3594,39.52516],[69.68677,39.59281],[69.87491,39.53882],[70.11111,39.58223],[70.2869,39.53141],[70.44757,39.60128],[70.64087,39.58792],[70.7854,39.38933],[71.06418,39.41586],[71.08752,39.50704],[71.49814,39.61397],[71.55856,39.57588],[71.5517,39.45722],[71.62688,39.44056],[71.76816,39.45456],[71.80164,39.40631],[71.7522,39.32031],[71.79202,39.27355],[71.90601,39.27674],[72.04059,39.36704],[72.09689,39.26823],[72.17242,39.2661],[72.23834,39.17248],[72.33173,39.33093],[72.62027,39.39696],[72.85934,39.35116],[73.18454,39.35536],[73.31912,39.38615],[73.45096,39.46677],[73.59831,39.46425],[73.87018,39.47879],[73.94683,39.60733],[73.92354,39.69565],[73.9051,39.75073],[73.83006,39.76136],[73.97049,40.04378],[74.25533,40.13191],[74.35063,40.09742],[74.69875,40.34668],[74.85996,40.32857],[74.78168,40.44886],[74.82013,40.52197],[75.08243,40.43945],[75.22834,40.45382],[75.5854,40.66874],[75.69663,40.28642],[75.91361,40.2948],[75.96168,40.38064],[76.33659,40.3482],[76.5261,40.46114],[76.75681,40.95354],[76.99302,41.0696],[77.28004,41.0033],[77.3693,41.0375],[77.52723,41.00227],[77.76206,41.01574],[77.81287,41.14307],[78.12873,41.23091],[78.15757,41.38565],[78.3732,41.39603],[79.92977,42.04113],[80.17842,42.03211],[80.17807,42.21166],[79.97364,42.42816],[79.52921,42.44778],[79.19763,42.804],[78.91502,42.76839],[78.48469,42.89649],[75.82823,42.94848],[75.72174,42.79672],[75.29966,42.86183],[75.22619,42.85528],[74.88756,42.98612],[74.75,42.99029],[74.70331,43.02519],[74.64615,43.05881],[74.57491,43.13702],[74.22489,43.24657],[73.55634,43.03071],[73.50992,42.82356],[73.44393,42.43098],[71.88792,42.83578],[71.62405,42.76613],[71.53272,42.8014],[71.2724,42.77853],[71.22785,42.69248],[71.17807,42.67381],[71.15232,42.60486],[70.97717,42.50147],[70.85973,42.30188],[70.94483,42.26238],[71.13263,42.28356],[71.28719,42.18033],[70.69777,41.92554],[70.17682,41.5455],[70.48909,41.40335],[70.67586,41.47953],[70.78572,41.36419],[70.77885,41.24813],[70.86263,41.23833],[70.9615,41.16393],[71.02193,41.19494],[71.11806,41.15359],[71.25813,41.18796],[71.27187,41.11015],[71.34877,41.16807],[71.40198,41.09436],[71.46148,41.13958],[71.43814,41.19644],[71.46688,41.31883],[71.57227,41.29175],[71.6787,41.42111],[71.65914,41.49599],[71.73054,41.54713],[71.71132,41.43012],[71.76625,41.4466],[71.83914,41.3546],[71.91457,41.2982],[71.85964,41.19081],[72.07249,41.11739],[72.10745,41.15483],[72.16433,41.16483],[72.17594,41.15522],[72.14864,41.13363],[72.1792,41.10621],[72.21061,41.05607],[72.17594,41.02377],[72.18339,40.99571],[72.324,41.03381],[72.34026,41.04539],[72.34757,41.06104],[72.36138,41.04384],[72.38511,41.02785],[72.45206,41.03018],[72.48742,40.97136],[72.55109,40.96046],[72.59136,40.86947],[72.68157,40.84942],[72.84291,40.85512],[72.94454,40.8094],[73.01869,40.84681],[73.13267,40.83512],[73.13412,40.79122],[73.0612,40.76678],[72.99133,40.76457],[72.93296,40.73089],[72.8722,40.71111],[72.85372,40.7116],[72.84754,40.67229],[72.80137,40.67856],[72.74866,40.60873],[72.74894,40.59592],[72.75982,40.57273],[72.74862,40.57131],[72.74768,40.58051],[72.73995,40.58409],[72.69579,40.59778],[72.66713,40.59076],[72.66713,40.5219],[72.47795,40.5532],[72.40517,40.61917],[72.34406,40.60144],[72.41714,40.55736],[72.38384,40.51535],[72.41513,40.50856],[72.44191,40.48222],[72.40346,40.4007],[72.24368,40.46091],[72.18648,40.49893],[71.96401,40.31907],[72.05464,40.27586],[71.85002,40.25647],[71.82646,40.21872],[71.73054,40.14818],[71.71719,40.17886],[71.69621,40.18492],[71.70569,40.20391],[71.68386,40.26984],[71.61931,40.26775],[71.61725,40.20615],[71.51549,40.22986],[71.51215,40.26943],[71.4246,40.28619],[71.36663,40.31593],[71.13042,40.34106],[71.05901,40.28765],[70.95789,40.28761],[70.9818,40.22392],[70.80495,40.16813],[70.7928,40.12797],[70.65827,40.0981],[70.65946,39.9878],[70.58912,39.95211],[70.55033,39.96619],[70.47557,39.93216],[70.57384,39.99394],[70.58297,40.00891],[70.01283,40.23288],[69.67001,40.10639],[69.64704,40.12165],[69.57615,40.10524],[69.55555,40.12296],[69.53794,40.11833],[69.53855,40.0887],[69.5057,40.03277],[69.53615,39.93991],[69.43557,39.92877],[69.43134,39.98431],[69.35649,40.01994],[69.26938,39.8127]],[[70.52631,39.86989],[70.53651,39.89155],[70.74189,39.86319],[70.63105,39.77923],[70.59667,39.83542],[70.54998,39.85137],[70.52631,39.86989]],[[71.00236,40.18154],[71.06305,40.1771],[71.12218,40.03052],[71.21139,40.03369],[71.1427,39.95026],[71.23067,39.93581],[71.16101,39.88423],[71.10531,39.91354],[71.04979,39.89808],[71.10501,39.95568],[71.09063,39.99],[71.11668,39.99291],[71.11037,40.01984],[71.01035,40.05481],[71.00236,40.18154]],[[71.71511,39.96348],[71.78838,40.01404],[71.86463,39.98598],[71.84316,39.95582],[71.7504,39.93701],[71.71511,39.96348]]]}},{"type":"Feature","properties":{"id":"KI"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-175.33482,-1.40631],[-175.31804,-7.54825],[-174.18707,-7.54408],[-167.75329,-7.52784],[-156.50903,-7.4975],[-156.4957,-12.32002],[-149.61166,-12.30171],[-149.6249,-7.51261],[-149.65979,5.27712],[-161.06795,5.2462],[-161.05669,1.11722],[-158.62734,1.1296],[-158.62058,-1.35506],[-161.04969,-1.36251],[-175.33482,-1.40631]]],[[[169,-3.5],[178,-3.5],[178,3.9],[169,3.9],[169,-3.5]]]]}},{"type":"Feature","properties":{"id":"NL"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-70.34259,12.92535],[-70.24399,12.38063],[-69.4514,12.18025],[-68.99639,11.79035],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309],[-69.5195,12.75292],[-70.34259,12.92535]]],[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-62.93924,18.02904],[-63.02323,18.05757],[-63.04039,18.05619],[-63.0579,18.06614],[-63.07759,18.04943],[-63.09686,18.04608],[-63.11096,18.05368],[-63.13584,18.0541],[-63.33064,17.9615],[-63.29212,17.90532],[-63.58819,17.61311]]],[[[2.56575,51.85301],[3.36263,51.37112],[3.38696,51.33436],[3.35847,51.31572],[3.38289,51.27331],[3.41704,51.25933],[3.43488,51.24135],[3.52698,51.2458],[3.51502,51.28697],[3.58939,51.30064],[3.78999,51.25766],[3.78783,51.2151],[3.90125,51.20371],[3.97889,51.22537],[4.01957,51.24504],[4.05165,51.24171],[4.16721,51.29348],[4.24024,51.35371],[4.21923,51.37443],[4.33265,51.37687],[4.34086,51.35738],[4.39292,51.35547],[4.43777,51.36989],[4.38064,51.41965],[4.39747,51.43316],[4.38122,51.44905],[4.47736,51.4778],[4.5388,51.48184],[4.54675,51.47265],[4.52846,51.45002],[4.53521,51.4243],[4.57489,51.4324],[4.65442,51.42352],[4.72935,51.48424],[4.74578,51.48937],[4.77321,51.50529],[4.78803,51.50284],[4.84139,51.4799],[4.82409,51.44736],[4.82946,51.4213],[4.78314,51.43319],[4.76577,51.43046],[4.77229,51.41337],[4.78941,51.41102],[4.84988,51.41502],[4.90016,51.41404],[4.92152,51.39487],[5.00393,51.44406],[5.0106,51.47167],[5.03281,51.48679],[5.04774,51.47022],[5.07891,51.4715],[5.10456,51.43163],[5.07102,51.39469],[5.13105,51.34791],[5.13377,51.31592],[5.16222,51.31035],[5.2002,51.32243],[5.24244,51.30495],[5.22542,51.26888],[5.23814,51.26064],[5.26461,51.26693],[5.29716,51.26104],[5.33886,51.26314],[5.347,51.27502],[5.41672,51.26248],[5.4407,51.28169],[5.46519,51.2849],[5.48476,51.30053],[5.515,51.29462],[5.5569,51.26544],[5.5603,51.22249],[5.65145,51.19788],[5.65528,51.18736],[5.70344,51.1829],[5.74617,51.18928],[5.77735,51.17845],[5.77697,51.1522],[5.82564,51.16753],[5.85508,51.14445],[5.80798,51.11661],[5.8109,51.10861],[5.83226,51.10585],[5.82921,51.09328],[5.79903,51.09371],[5.79835,51.05834],[5.77258,51.06196],[5.75961,51.03113],[5.77688,51.02483],[5.76242,50.99703],[5.71864,50.96092],[5.72875,50.95428],[5.74752,50.96202],[5.75927,50.95601],[5.74644,50.94723],[5.72545,50.92312],[5.72644,50.91167],[5.71626,50.90796],[5.69858,50.91046],[5.67886,50.88142],[5.64504,50.87107],[5.64009,50.84742],[5.65259,50.82309],[5.70118,50.80764],[5.68995,50.79641],[5.70107,50.7827],[5.68091,50.75804],[5.69469,50.75529],[5.72216,50.76398],[5.73904,50.75674],[5.74356,50.7691],[5.76533,50.78159],[5.77513,50.78308],[5.80673,50.7558],[5.84548,50.76542],[5.84888,50.75448],[5.88734,50.77092],[5.89129,50.75125],[5.89132,50.75124],[5.95942,50.7622],[5.97545,50.75441],[6.01976,50.75398],[6.02624,50.77453],[5.97497,50.79992],[5.98404,50.80988],[6.00462,50.80065],[6.02328,50.81694],[6.01921,50.84435],[6.05623,50.8572],[6.05702,50.85179],[6.07431,50.84674],[6.07693,50.86025],[6.08805,50.87223],[6.07486,50.89307],[6.09297,50.92066],[6.01615,50.93367],[6.02697,50.98303],[5.95282,50.98728],[5.90296,50.97356],[5.90493,51.00198],[5.87849,51.01969],[5.86735,51.05182],[5.9134,51.06736],[5.9541,51.03496],[5.98292,51.07469],[6.16706,51.15677],[6.17384,51.19589],[6.07889,51.17038],[6.07889,51.24432],[6.16977,51.33169],[6.22674,51.36135],[6.22641,51.39948],[6.20654,51.40049],[6.21724,51.48568],[6.18017,51.54096],[6.09055,51.60564],[6.11759,51.65609],[6.02767,51.6742],[6.04091,51.71821],[5.95003,51.7493],[5.98665,51.76944],[5.94568,51.82786],[5.99848,51.83195],[6.06705,51.86136],[6.10337,51.84829],[6.16902,51.84094],[6.11551,51.89769],[6.15349,51.90439],[6.21443,51.86801],[6.29872,51.86801],[6.30593,51.84998],[6.40704,51.82771],[6.38815,51.87257],[6.47179,51.85395],[6.50231,51.86313],[6.58556,51.89386],[6.68386,51.91861],[6.72319,51.89518],[6.82357,51.96711],[6.83035,51.9905],[6.68128,52.05052],[6.76117,52.11895],[6.83984,52.11728],[6.97189,52.20329],[6.9897,52.2271],[7.03729,52.22695],[7.06365,52.23789],[7.02703,52.27941],[7.07044,52.37805],[7.03417,52.40237],[6.99041,52.47235],[6.94293,52.43597],[6.69507,52.488],[6.71641,52.62905],[6.77307,52.65375],[7.04557,52.63318],[7.07253,52.81083],[7.21694,53.00742],[7.17898,53.13817],[7.22681,53.18165],[7.21679,53.20058],[7.19052,53.31866],[7.00198,53.32672],[6.91025,53.44221],[5.45168,54.20039],[2.56575,51.85301]],[[4.91493,51.4353],[4.91935,51.43634],[4.92227,51.44252],[4.91811,51.44621],[4.92287,51.44741],[4.92811,51.4437],[4.92566,51.44273],[4.92815,51.43856],[4.92879,51.44161],[4.93544,51.44634],[4.94025,51.44193],[4.93416,51.44185],[4.93471,51.43861],[4.94265,51.44003],[4.93986,51.43064],[4.92952,51.42984],[4.92652,51.43329],[4.91493,51.4353]],[[4.93295,51.44945],[4.95244,51.45207],[4.9524,51.45014],[4.93909,51.44632],[4.93295,51.44945]]]]}},{"type":"Feature","properties":{"id":"NO"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-10.71459,70.09565],[-5.93364,70.76368],[-9.68082,72.73731],[-10.71459,70.09565]]],[[[-3.52068,82.6752],[16.4353,73.61229],[33.12005,75.46568],[35.22046,80.57056],[-3.52068,82.6752]]],[[[-0.3751,61.32236],[7.28637,57.35913],[10.40861,58.38489],[10.64958,58.89391],[11.08911,58.98745],[11.15367,59.07862],[11.34459,59.11672],[11.4601,58.99022],[11.45199,58.89604],[11.65732,58.90177],[11.8213,59.24985],[11.69297,59.59442],[11.92112,59.69531],[11.87121,59.86039],[12.15641,59.8926],[12.36317,59.99259],[12.52003,60.13846],[12.59133,60.50559],[12.2277,61.02442],[12.69115,61.06584],[12.86939,61.35427],[12.57707,61.56547],[12.40595,61.57226],[12.14746,61.7147],[12.29187,62.25699],[12.07085,62.6297],[12.19919,63.00104],[11.98529,63.27487],[12.19919,63.47935],[12.14928,63.59373],[12.74105,64.02171],[13.23411,64.09087],[13.98222,64.00953],[14.16051,64.18725],[14.11117,64.46674],[13.64276,64.58402],[14.50926,65.31786],[14.53778,66.12399],[15.05113,66.15572],[15.49318,66.28509],[15.37197,66.48217],[16.35589,67.06419],[16.39154,67.21653],[16.09922,67.4364],[16.12774,67.52106],[16.38441,67.52923],[16.7409,67.91037],[17.30416,68.11591],[17.90787,67.96537],[18.13836,68.20874],[18.1241,68.53721],[18.39503,68.58672],[18.63032,68.50849],[18.97255,68.52416],[19.93508,68.35911],[20.22027,68.48759],[19.95647,68.55546],[20.22027,68.67246],[20.33435,68.80174],[20.28444,68.93283],[20.0695,69.04469],[20.55258,69.06069],[20.72171,69.11874],[21.05775,69.0356],[21.11099,69.10291],[20.98641,69.18809],[21.00732,69.22755],[21.27827,69.31281],[21.63833,69.27485],[22.27276,68.89514],[22.38367,68.71561],[22.53321,68.74393],[23.13064,68.64684],[23.68017,68.70276],[23.781,68.84514],[24.02299,68.81601],[24.18432,68.73936],[24.74898,68.65143],[24.90023,68.55579],[24.93048,68.61102],[25.10189,68.63307],[25.12206,68.78684],[25.42455,68.90328],[25.61613,68.89602],[25.75729,68.99383],[25.69679,69.27039],[25.96904,69.68397],[26.40261,69.91377],[26.64461,69.96565],[27.05802,69.92069],[27.57226,70.06215],[27.95542,70.0965],[27.97558,69.99671],[28.32849,69.88605],[28.36883,69.81658],[29.12697,69.69193],[29.31664,69.47994],[28.8629,69.22395],[28.81248,69.11997],[28.91738,69.04774],[29.0444,69.0119],[29.26623,69.13794],[29.27631,69.2811],[29.97205,69.41623],[30.16363,69.65244],[30.52662,69.54699],[30.95011,69.54699],[30.84095,69.80584],[31.59909,70.16571],[32.07813,72.01005],[18.46509,71.28681],[-0.3751,61.32236]]],[[[2.85578,-54.70531],[3.87126,-54.71036],[3.87947,-54.15611],[2.86398,-54.15099],[2.85578,-54.70531]]]]}},{"type":"Feature","properties":{"id":"NZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-180,-24.21376],[-179.93224,-45.18423],[-173.00283,-45.20102],[-173.10761,-24.19665],[-180,-24.21376]]],[[[-174.18707,-7.54408],[-174.17993,-10.13616],[-167.75195,-10.12005],[-167.73854,-14.92809],[-171.14262,-14.93704],[-173.13438,-14.94228],[-173.11048,-23.23027],[-167.73129,-23.22266],[-156.46451,-23.21255],[-156.4957,-12.32002],[-156.50903,-7.4975],[-167.75329,-7.52784],[-174.18707,-7.54408]]],[[[164.49803,-50.68404],[169.00308,-53.19756],[179.49541,-50.04657],[179.49541,-36.79303],[169.6687,-29.09191],[169.35326,-30.60259],[164.49803,-50.68404]]]]}},{"type":"Feature","properties":{"id":"OM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[52.00311,19.00083],[52.78009,17.35124],[52.74267,17.29519],[52.81185,17.28568],[53.09917,16.67084],[53.32998,16.16312],[56.66759,17.24021],[61.45114,22.55394],[56.86325,25.03856],[56.3227,24.97284],[56.34873,24.93205],[56.30269,24.88334],[56.20568,24.85063],[56.20062,24.78565],[56.13684,24.73699],[56.06128,24.74457],[56.03535,24.81161],[55.97836,24.87673],[55.97467,24.89639],[56.05106,24.87461],[56.05715,24.95727],[55.96316,25.00857],[55.90849,24.96771],[55.85094,24.96858],[55.81116,24.9116],[55.81348,24.80102],[55.83408,24.77858],[55.83271,24.68567],[55.76461,24.5287],[55.83271,24.41521],[55.83395,24.32776],[55.80747,24.31069],[55.79145,24.27914],[55.76781,24.26209],[55.75939,24.26114],[55.75382,24.2466],[55.75257,24.23466],[55.76558,24.23227],[55.77658,24.23476],[55.83367,24.20193],[55.95472,24.2172],[56.01799,24.07426],[55.8308,24.01633],[55.73301,24.05994],[55.48677,23.94946],[55.57358,23.669],[55.22634,23.10378],[55.2137,22.71065],[55.66469,21.99658],[54.99756,20.00083],[52.00311,19.00083]]],[[[55.81777,26.18798],[56.08666,26.05038],[56.15498,26.06828],[56.19334,25.9795],[56.13963,25.82765],[56.17416,25.77239],[56.13579,25.73524],[56.14826,25.66351],[56.18363,25.65508],[56.20473,25.61119],[56.25365,25.60211],[56.26636,25.60643],[56.25341,25.61443],[56.26534,25.62825],[56.82555,25.7713],[56.79239,26.41236],[56.68954,26.76645],[56.2644,26.58649],[55.81777,26.18798]]],[[[56.20838,25.25668],[56.20872,25.24104],[56.24341,25.22867],[56.27628,25.23404],[56.34438,25.26653],[56.35172,25.30681],[56.3111,25.30107],[56.3005,25.31815],[56.26062,25.33108],[56.23362,25.31253],[56.25008,25.28843],[56.24465,25.27505],[56.20838,25.25668]],[[56.27086,25.26128],[56.2716,25.27916],[56.28102,25.28486],[56.29379,25.2754],[56.28423,25.26344],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"RU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-179.99933,64.74703],[-172.76104,63.77445],[-169.03888,65.48473],[-168.95635,65.98512],[-168.25765,71.99091],[-179.9843,71.90735],[-179.99933,64.74703]]],[[[18.57853,55.25302],[19.64312,54.45423],[19.8038,54.44203],[20.63871,54.3706],[21.41123,54.32395],[22.79705,54.36264],[22.7253,54.41732],[22.70208,54.45312],[22.67788,54.532],[22.71293,54.56454],[22.68021,54.58486],[22.7522,54.63525],[22.74225,54.64339],[22.75467,54.6483],[22.73397,54.66604],[22.73631,54.72952],[22.87317,54.79492],[22.85083,54.88711],[22.76422,54.92521],[22.68723,54.9811],[22.65451,54.97037],[22.60075,55.01863],[22.58907,55.07085],[22.47688,55.04408],[22.31562,55.0655],[22.14267,55.05345],[22.11697,55.02131],[22.06087,55.02935],[22.02582,55.05078],[22.03984,55.07888],[21.99543,55.08691],[21.96505,55.07353],[21.85521,55.09493],[21.64954,55.1791],[21.55605,55.20311],[21.51095,55.18507],[21.46766,55.21115],[21.38446,55.29348],[21.35465,55.28427],[21.26425,55.24456],[20.95181,55.27994],[20.60454,55.40986],[18.57853,55.25302]]],[[[26.32936,60.00121],[26.90044,59.63819],[27.85643,59.58538],[28.04187,59.47017],[28.19061,59.39962],[28.21137,59.38058],[28.20537,59.36491],[28.19284,59.35791],[28.14215,59.28934],[28.00689,59.28351],[27.90911,59.24353],[27.87978,59.18097],[27.80482,59.1116],[27.74429,58.98351],[27.36366,58.78381],[27.55489,58.39525],[27.48541,58.22615],[27.62393,58.09462],[27.67282,57.92627],[27.81841,57.89244],[27.78526,57.83963],[27.56689,57.83356],[27.50171,57.78842],[27.52615,57.72843],[27.3746,57.66834],[27.40393,57.62125],[27.31919,57.57672],[27.34698,57.52242],[27.56832,57.53728],[27.52453,57.42826],[27.86101,57.29402],[27.66511,56.83921],[27.86101,56.88204],[28.04768,56.59004],[28.13526,56.57989],[28.10069,56.524],[28.19057,56.44637],[28.16599,56.37806],[28.23716,56.27588],[28.15217,56.16964],[28.30571,56.06035],[28.36888,56.05805],[28.37987,56.11399],[28.43068,56.09407],[28.5529,56.11705],[28.68337,56.10173],[28.63668,56.07262],[28.73418,55.97131],[29.08299,56.03427],[29.21717,55.98971],[29.44692,55.95978],[29.3604,55.75862],[29.51283,55.70294],[29.61446,55.77716],[29.80672,55.79569],[29.97975,55.87281],[30.12136,55.8358],[30.27776,55.86819],[30.30987,55.83592],[30.48257,55.81066],[30.51346,55.78982],[30.51037,55.76568],[30.63344,55.73079],[30.67464,55.64176],[30.72957,55.66268],[30.7845,55.58514],[30.86003,55.63169],[30.93419,55.6185],[30.95204,55.50667],[30.90123,55.46621],[30.93144,55.3914],[30.8257,55.3313],[30.81946,55.27931],[30.87944,55.28223],[30.97369,55.17134],[31.02071,55.06167],[31.00972,55.02783],[30.94243,55.03964],[30.9081,55.02232],[30.95754,54.98609],[30.93144,54.9585],[30.81759,54.94064],[30.8264,54.90062],[30.75165,54.80699],[30.95479,54.74346],[30.97127,54.71967],[31.0262,54.70698],[30.98226,54.68872],[30.99187,54.67046],[31.19339,54.66947],[31.21399,54.63113],[31.08543,54.50361],[31.22945,54.46585],[31.3177,54.34067],[31.30791,54.25315],[31.57002,54.14535],[31.89599,54.0837],[31.88744,54.03653],[31.85019,53.91801],[31.77028,53.80015],[31.89137,53.78099],[32.12621,53.81586],[32.36663,53.7166],[32.45717,53.74039],[32.50112,53.68594],[32.40499,53.6656],[32.47777,53.5548],[32.74968,53.45597],[32.73257,53.33494],[32.51725,53.28431],[32.40773,53.18856],[32.15368,53.07594],[31.82373,53.10042],[31.787,53.18033],[31.62496,53.22886],[31.56316,53.19432],[31.40523,53.21406],[31.36403,53.13504],[31.3915,53.09712],[31.33519,53.08805],[31.32283,53.04101],[31.24147,53.031],[31.35667,52.97854],[31.592,52.79011],[31.57277,52.71613],[31.50406,52.69707],[31.63869,52.55361],[31.56316,52.51518],[31.61397,52.48843],[31.62084,52.33849],[31.57971,52.32146],[31.70735,52.26711],[31.6895,52.1973],[31.77877,52.18636],[31.7822,52.11406],[31.81722,52.09955],[31.85018,52.11305],[31.96141,52.08015],[31.92159,52.05144],[32.08813,52.03319],[32.23331,52.08085],[32.2777,52.10266],[32.34044,52.1434],[32.33083,52.23685],[32.38988,52.24946],[32.3528,52.32842],[32.54781,52.32423],[32.69475,52.25535],[32.85405,52.27888],[32.89937,52.2461],[33.18913,52.3754],[33.51323,52.35779],[33.48027,52.31499],[33.55718,52.30324],[33.78789,52.37204],[34.05239,52.20132],[34.11199,52.14087],[34.09413,52.00835],[34.41136,51.82793],[34.42922,51.72852],[34.07765,51.67065],[34.17599,51.63253],[34.30562,51.5205],[34.22048,51.4187],[34.33446,51.363],[34.23009,51.26429],[34.31661,51.23936],[34.38802,51.2746],[34.6613,51.25053],[34.6874,51.18],[34.82472,51.17483],[34.97304,51.2342],[35.14058,51.23162],[35.12685,51.16191],[35.20375,51.04723],[35.31774,51.08434],[35.40837,51.04119],[35.32598,50.94524],[35.39307,50.92145],[35.41367,50.80227],[35.47704,50.77274],[35.48116,50.66405],[35.39464,50.64751],[35.47463,50.49247],[35.58003,50.45117],[35.61711,50.35707],[35.73659,50.35489],[35.80388,50.41356],[35.8926,50.43829],[36.06893,50.45205],[36.20763,50.3943],[36.30101,50.29088],[36.47817,50.31457],[36.58371,50.28563],[36.56655,50.2413],[36.64571,50.218],[36.69377,50.26982],[36.91762,50.34963],[37.08468,50.34935],[37.48204,50.46079],[37.47243,50.36277],[37.62486,50.29966],[37.62879,50.24481],[37.61113,50.21976],[37.75807,50.07896],[37.79515,50.08425],[37.90776,50.04194],[38.02999,49.94482],[38.02999,49.90592],[38.21675,49.98104],[38.18517,50.08161],[38.32524,50.08866],[38.35408,50.00664],[38.65688,49.97176],[38.68677,50.00904],[38.73311,49.90238],[38.90477,49.86787],[38.9391,49.79524],[39.1808,49.88911],[39.27968,49.75976],[39.44496,49.76067],[39.59142,49.73758],[39.65047,49.61761],[39.84548,49.56064],[40.13249,49.61672],[40.16683,49.56865],[40.03636,49.52321],[40.03087,49.45452],[40.1141,49.38798],[40.14912,49.37681],[40.18331,49.34996],[40.22176,49.25683],[40.01988,49.1761],[39.93437,49.05709],[39.6836,49.05121],[39.6683,48.99454],[39.71353,48.98959],[39.72649,48.9754],[39.74874,48.98675],[39.78368,48.91596],[39.98967,48.86901],[40.03636,48.91957],[40.08168,48.87443],[39.97182,48.79398],[39.79466,48.83739],[39.73104,48.7325],[39.71765,48.68673],[39.67226,48.59368],[39.79764,48.58668],[39.84548,48.57821],[39.86196,48.46633],[39.88794,48.44226],[39.94847,48.35055],[39.84136,48.33321],[39.84273,48.30947],[39.90041,48.3049],[39.91465,48.26743],[39.95248,48.29972],[39.9693,48.29904],[39.97325,48.31399],[39.99241,48.31768],[40.00752,48.22445],[39.94847,48.22811],[39.83724,48.06501],[39.88256,48.04482],[39.77544,48.04206],[39.82213,47.96396],[39.73935,47.82876],[38.87979,47.87719],[38.79628,47.81109],[38.76379,47.69346],[38.35062,47.61631],[38.28679,47.53552],[38.28954,47.39255],[38.22225,47.30788],[38.33074,47.30508],[38.32112,47.2585],[38.23049,47.2324],[38.22955,47.12069],[38.3384,46.98085],[38.12112,46.86078],[37.62608,46.82615],[35.23066,45.79231],[34.96015,45.75634],[34.79905,45.81009],[34.80153,45.90047],[34.75479,45.90705],[34.66679,45.97136],[34.60861,45.99347],[34.55889,45.99347],[34.52011,45.95097],[34.48729,45.94267],[34.44155,45.95995],[34.41221,46.00245],[34.33912,46.06114],[34.25111,46.0532],[34.181,46.06804],[34.12929,46.10494],[34.07311,46.11769],[34.05272,46.10838],[33.91549,46.15938],[33.85234,46.19863],[33.79715,46.20482],[33.74047,46.18555],[33.646,46.23028],[33.61517,46.22615],[33.63854,46.14147],[33.61467,46.13561],[33.57318,46.10317],[33.59087,46.06013],[33.54017,46.0123],[31.62627,45.50633],[32.99857,44.48323],[33.66142,43.9825],[36.61884,44.89556],[39.81147,43.06294],[40.0078,43.38551],[40.00853,43.40578],[40.01552,43.42025],[40.01007,43.42411],[40.03312,43.44262],[40.04445,43.47776],[40.10657,43.57344],[40.65957,43.56212],[41.64935,43.22331],[42.40563,43.23226],[42.66667,43.13917],[42.75889,43.19651],[43.03322,43.08883],[43.0419,43.02413],[43.81453,42.74297],[43.73119,42.62043],[43.95517,42.55396],[44.54202,42.75699],[44.70002,42.74679],[44.80941,42.61277],[44.88754,42.74934],[45.15318,42.70598],[45.36501,42.55268],[45.78692,42.48358],[45.61676,42.20768],[46.42738,41.91323],[46.5332,41.87389],[46.58924,41.80547],[46.75269,41.8623],[46.8134,41.76252],[47.00955,41.63583],[46.99554,41.59743],[47.03757,41.55434],[47.10762,41.59044],[47.34579,41.27884],[47.49004,41.26366],[47.54504,41.20275],[47.62288,41.22969],[47.75831,41.19455],[47.87973,41.21798],[48.07587,41.49957],[48.22064,41.51472],[48.2878,41.56221],[48.40277,41.60441],[48.42301,41.65444],[48.55078,41.77917],[48.5867,41.84306],[48.80971,41.95365],[49.2134,44.84989],[49.88945,46.04554],[49.32259,46.26944],[49.16518,46.38542],[48.54988,46.56267],[48.51142,46.69268],[49.01136,46.72716],[48.52326,47.4102],[48.45173,47.40818],[48.15348,47.74545],[47.64973,47.76559],[47.41689,47.83687],[47.38731,47.68176],[47.12107,47.83687],[47.11516,48.27188],[46.49011,48.43019],[46.78392,48.95352],[46.91104,48.99715],[47.01458,49.07085],[47.04416,49.17152],[46.98795,49.23531],[46.78398,49.34026],[46.9078,49.86707],[47.18319,49.93721],[47.34589,50.09308],[47.30448,50.30894],[47.58551,50.47867],[48.10044,50.09242],[48.24519,49.86099],[48.42564,49.82283],[48.68352,49.89546],[48.90782,50.02281],[48.57946,50.63278],[48.86936,50.61589],[49.12673,50.78639],[49.41959,50.85927],[49.39001,51.09396],[49.76866,51.11067],[49.97277,51.2405],[50.26859,51.28677],[50.59695,51.61859],[51.26254,51.68466],[51.301,51.48799],[51.77431,51.49536],[51.8246,51.67916],[52.36119,51.74161],[52.54329,51.48444],[53.46165,51.49445],[53.69299,51.23466],[54.12248,51.11542],[54.46331,50.85554],[54.41894,50.61214],[54.55797,50.52006],[54.71476,50.61214],[54.56685,51.01958],[54.72067,51.03261],[55.67774,50.54508],[56.11398,50.7471],[56.17906,50.93204],[57.17302,51.11253],[57.44221,50.88354],[57.74986,50.93017],[57.75578,51.13852],[58.3208,51.15151],[58.87974,50.70852],[59.48928,50.64216],[59.51886,50.49937],[59.81172,50.54451],[60.01288,50.8163],[60.17262,50.83312],[60.31914,50.67705],[60.81833,50.6629],[61.4431,50.80679],[61.56889,51.23679],[61.6813,51.25716],[61.55114,51.32746],[61.50677,51.40687],[60.95655,51.48615],[60.92401,51.61124],[60.5424,51.61675],[60.36787,51.66815],[60.50986,51.7964],[60.09867,51.87135],[59.99809,51.98263],[60.19925,51.99173],[60.48915,52.15175],[60.72581,52.15538],[60.78201,52.22067],[61.05417,52.35096],[60.98021,52.50068],[60.84709,52.52228],[60.84118,52.63912],[60.71693,52.66245],[60.71989,52.75923],[61.05842,52.92217],[61.23462,53.03227],[62.0422,52.96105],[62.12799,52.99133],[62.14574,53.09626],[61.19024,53.30536],[61.14291,53.41481],[61.29082,53.50992],[61.37957,53.45887],[61.57185,53.50112],[61.55706,53.57144],[60.90626,53.62937],[61.22574,53.80268],[61.14283,53.90063],[60.99796,53.93699],[61.26863,53.92797],[61.3706,54.08464],[61.47603,54.08048],[61.56941,53.95703],[61.65318,54.02445],[62.03913,53.94768],[62.00966,54.04134],[62.38535,54.03961],[62.45931,53.90737],[62.56876,53.94047],[62.58651,54.05871],[63.80604,54.27079],[63.91224,54.20013],[64.02715,54.22679],[63.97686,54.29763],[64.97216,54.4212],[65.11033,54.33028],[65.24663,54.35721],[65.20174,54.55216],[68.21308,54.98645],[68.26661,55.09226],[68.19206,55.18823],[68.90865,55.38148],[69.34224,55.36344],[69.74917,55.35545],[70.19179,55.1476],[70.76493,55.3027],[70.96009,55.10558],[71.08288,54.71253],[71.24185,54.64965],[71.08706,54.33376],[71.10379,54.13326],[71.96141,54.17736],[72.17477,54.36303],[72.43415,53.92685],[72.71026,54.1161],[73.37963,53.96132],[73.74778,54.07194],[73.68921,53.86522],[73.25412,53.61532],[73.39218,53.44623],[75.07405,53.80831],[75.43398,53.98652],[75.3668,54.07439],[76.91052,54.4677],[76.82266,54.1798],[76.44076,54.16017],[76.54243,53.99329],[77.90383,53.29807],[79.11255,52.01171],[80.08138,50.77658],[80.4127,50.95581],[80.44819,51.20855],[80.80318,51.28262],[81.16999,51.15662],[81.06091,50.94833],[81.41248,50.97524],[81.46581,50.77658],[81.94999,50.79307],[82.55443,50.75412],[83.14607,51.00796],[83.8442,50.87375],[84.29385,50.27257],[84.99198,50.06793],[85.24047,49.60239],[86.18709,49.50259],[86.63674,49.80136],[86.79056,49.74787],[86.61307,49.60239],[86.82606,49.51796],[87.03071,49.25142],[87.31465,49.23603],[87.28386,49.11626],[87.478,49.07403],[87.48983,49.13794],[87.81333,49.17354],[87.98977,49.18147],[88.15543,49.30314],[88.17223,49.46934],[88.42449,49.48821],[88.82499,49.44808],[89.70687,49.72535],[89.59711,49.90851],[91.86048,50.73734],[92.07173,50.69585],[92.44714,50.78762],[93.01109,50.79001],[92.99595,50.63183],[94.30823,50.57498],[94.39258,50.22193],[94.49477,50.17832],[94.6121,50.04239],[94.97166,50.04725],[95.02465,49.96941],[95.74757,49.97915],[95.80056,50.04239],[96.97388,49.88413],[97.24639,49.74737],[97.56811,49.84265],[97.56432,49.92801],[97.76871,49.99861],[97.85197,49.91339],[98.29481,50.33561],[98.31373,50.4996],[98.06393,50.61262],[97.9693,50.78044],[98.01472,50.86652],[97.83305,51.00248],[98.05257,51.46696],[98.22053,51.46579],[98.33222,51.71832],[98.74142,51.8637],[98.87768,52.14563],[99.27888,51.96876],[99.75578,51.90108],[99.89203,51.74903],[100.61116,51.73028],[101.39085,51.45753],[101.5044,51.50467],[102.14032,51.35566],[102.32194,50.67982],[102.71178,50.38873],[103.70343,50.13952],[105.32528,50.4648],[106.05562,50.40582],[106.07865,50.33474],[106.47156,50.31909],[106.49628,50.32436],[106.51122,50.34408],[106.58373,50.34044],[106.80326,50.30177],[107.00007,50.1977],[107.1174,50.04239],[107.36407,49.97612],[107.96116,49.93191],[107.95387,49.66659],[108.27937,49.53167],[108.53969,49.32325],[109.18017,49.34709],[109.51325,49.22859],[110.24373,49.16676],[110.39891,49.25083],[110.64493,49.1816],[113.02647,49.60772],[113.20216,49.83356],[114.325,50.28098],[114.9703,50.19254],[115.26068,49.97367],[115.73602,49.87688],[116.22402,50.04477],[116.62502,49.92919],[116.71193,49.83813],[117.07142,49.68482],[117.27597,49.62544],[117.48208,49.62324],[117.82343,49.52696],[118.61623,49.93809],[119.11003,50.00276],[119.27996,50.13348],[119.38598,50.35162],[119.13553,50.37412],[120.10963,51.671],[120.65907,51.93544],[120.77337,52.20805],[120.61346,52.32447],[120.71673,52.54099],[120.46454,52.63811],[120.04049,52.58773],[120.0451,52.7359],[120.85633,53.28499],[121.39213,53.31888],[122.35063,53.49565],[122.85966,53.47395],[123.26989,53.54843],[123.86158,53.49391],[124.46078,53.21881],[125.17522,53.20225],[125.6131,53.07229],[126.558,52.13738],[126.44606,51.98254],[126.68349,51.70607],[126.90369,51.3238],[126.93135,51.0841],[127.14586,50.91152],[127.28165,50.72075],[127.36335,50.58306],[127.28765,50.46585],[127.36009,50.43787],[127.37384,50.28393],[127.60515,50.23503],[127.49299,50.01251],[127.53516,49.84306],[127.83476,49.5748],[128.72896,49.58676],[129.11153,49.36813],[129.23232,49.40353],[129.35317,49.3481],[129.40398,49.44194],[129.50685,49.42398],[129.67598,49.29596],[129.85416,49.11067],[130.2355,48.86741],[130.43232,48.90844],[130.66946,48.88251],[130.52147,48.61745],[130.84462,48.30942],[130.65103,48.10052],[130.90915,47.90623],[130.95985,47.6957],[131.09871,47.6852],[131.2635,47.73325],[131.90448,47.68011],[132.57309,47.71741],[132.66989,47.96491],[134.49516,48.42884],[134.75328,48.36763],[134.67098,48.1564],[134.55508,47.98651],[134.7671,47.72051],[134.50898,47.4812],[134.20016,47.33458],[134.03538,46.75668],[133.84104,46.46681],[133.91496,46.4274],[133.88097,46.25066],[133.68047,46.14697],[133.72695,46.05576],[133.67569,45.9759],[133.60442,45.90053],[133.48457,45.86203],[133.41083,45.57723],[133.19419,45.51913],[133.09279,45.25693],[133.12293,45.1332],[132.96373,45.0212],[132.83978,45.05916],[131.99417,45.2567],[131.86903,45.33636],[131.76532,45.22609],[131.66852,45.2196],[131.68466,45.12374],[131.48415,44.99513],[130.95639,44.85154],[131.1108,44.70266],[131.30365,44.04262],[131.25484,44.03131],[131.23583,43.96085],[131.26176,43.94011],[131.21105,43.82383],[131.19492,43.53047],[131.29402,43.46695],[131.30324,43.39498],[131.19031,43.21385],[131.20414,43.13654],[131.10274,43.04734],[131.135,42.94114],[131.02668,42.91246],[131.02438,42.86518],[130.66524,42.84753],[130.44361,42.76205],[130.40213,42.70788],[130.56576,42.68925],[130.62107,42.58413],[130.55143,42.52158],[130.56835,42.43281],[130.60805,42.4317],[130.64181,42.41422],[130.66367,42.38024],[130.65022,42.32281],[131.95041,41.5445],[140.9182,45.92937],[145.82343,44.571],[145.23667,43.76813],[145.76215,43.50342],[145.97944,43.07828],[169.19658,53.9242],[180,62.52334],[180,71.53642],[157.93051,77.7025],[94.09128,81.82849],[56.59649,82.16972],[35.22046,80.57056],[33.12005,75.46568],[32.07813,72.01005],[31.59909,70.16571],[30.84095,69.80584],[30.95011,69.54699],[30.52662,69.54699],[30.16363,69.65244],[29.97205,69.41623],[29.27631,69.2811],[29.26623,69.13794],[29.0444,69.0119],[28.91738,69.04774],[28.45957,68.91417],[28.78224,68.86696],[28.43941,68.53366],[28.62982,68.19816],[29.34179,68.06655],[29.66955,67.79872],[30.02041,67.67523],[29.91155,67.51507],[28.9839,66.94139],[29.91155,66.13863],[30.16363,65.66935],[29.97205,65.70256],[29.74013,65.64025],[29.84096,65.56945],[29.68972,65.31803],[29.61914,65.23791],[29.8813,65.22101],[29.84096,65.1109],[29.61914,65.05993],[29.68972,64.80789],[30.05271,64.79072],[30.12329,64.64862],[30.01238,64.57513],[30.06279,64.35782],[30.4762,64.25728],[30.55687,64.09036],[30.25437,63.83364],[29.98213,63.75795],[30.49637,63.46666],[31.23244,63.22239],[31.29294,63.09035],[31.58535,62.91642],[31.38369,62.66284],[31.10136,62.43042],[29.01829,61.17448],[28.82816,61.1233],[28.47974,60.93365],[27.77352,60.52722],[27.71177,60.3893],[27.44953,60.22766],[26.32936,60.00121]]]]}}]} \ No newline at end of file +{"type":"FeatureCollection","features":[{"type":"Feature","properties":{"id":"TT"},"geometry":{"type":"Polygon","coordinates":[[[-62.08693,10.04435],[-60.993,9.88751],[-60.54419,10.87433],[-60.21728,11.59108],[-61.08554,11.23803],[-61.70744,10.98489],[-62.08693,10.04435]]]}},{"type":"Feature","properties":{"id":"AI"},"geometry":{"type":"Polygon","coordinates":[[[-63.95092,18.07976],[-63.35989,18.06012],[-62.86666,18.19278],[-62.75637,18.13489],[-62.64209,18.3662],[-63.3414,18.89029],[-63.90607,18.93262],[-63.95092,18.07976]]]}},{"type":"Feature","properties":{"id":"SX"},"geometry":{"type":"Polygon","coordinates":[[[-63.33064,17.9615],[-63.29212,17.90532],[-63.07669,17.79659],[-62.93924,18.02904],[-63.02323,18.05757],[-63.02886,18.05482],[-63.03669,18.05786],[-63.03998,18.05596],[-63.0591,18.06744],[-63.07759,18.04943],[-63.09686,18.04608],[-63.11096,18.05368],[-63.13584,18.0541],[-63.33064,17.9615]]]}},{"type":"Feature","properties":{"id":"VC"},"geometry":{"type":"Polygon","coordinates":[[[-61.65863,12.64273],[-61.18064,12.44553],[-60.70539,13.41452],[-61.43129,13.68336],[-61.65863,12.64273]]]}},{"type":"Feature","properties":{"id":"CW"},"geometry":{"type":"Polygon","coordinates":[[[-69.5195,12.75292],[-69.4514,12.18025],[-68.99639,11.79035],[-68.33524,11.78151],[-68.90012,12.62309],[-69.5195,12.75292]]]}},{"type":"Feature","properties":{"id":"HT"},"geometry":{"type":"Polygon","coordinates":[[[-74.76465,18.06252],[-72.29523,17.48026],[-71.75671,18.03456],[-71.73827,18.06949],[-71.7384,18.09747],[-71.74608,18.1009],[-71.74994,18.11115],[-71.75465,18.14405],[-71.78526,18.18528],[-71.69566,18.3414],[-71.90875,18.45821],[-71.88102,18.50125],[-72.00201,18.62312],[-71.95412,18.64939],[-71.82556,18.62551],[-71.71885,18.78423],[-71.72624,18.87802],[-71.77766,18.95007],[-71.88766,18.95175],[-71.74088,19.0437],[-71.71088,19.08353],[-71.69938,19.10916],[-71.65337,19.11759],[-71.62642,19.21212],[-71.73229,19.26686],[-71.77766,19.33823],[-71.69448,19.37866],[-71.6802,19.45008],[-71.71268,19.53374],[-71.71261,19.55073],[-71.7429,19.58445],[-71.74432,19.66513],[-71.75539,19.67863],[-71.75865,19.70231],[-72.17094,20.08703],[-72.94479,20.79216],[-73.62304,20.6935],[-73.98196,19.51903],[-74.7289,18.71009],[-74.76465,18.06252]]]}},{"type":"Feature","properties":{"id":"KY"},"geometry":{"type":"Polygon","coordinates":[[[-81.81969,19.51758],[-81.52417,18.7521],[-79.33932,19.50496],[-79.63484,20.26689],[-81.81969,19.51758]]]}},{"type":"Feature","properties":{"id":"CU"},"geometry":{"type":"Polygon","coordinates":[[[-85.9092,21.8218],[-85.29304,20.76169],[-80.28428,20.93037],[-78.19353,19.33462],[-73.98196,19.51903],[-73.62304,20.6935],[-80.16442,23.44484],[-82.02215,24.23074],[-85.9092,21.8218]]]}},{"type":"Feature","properties":{"id":"TC"},"geometry":{"type":"Polygon","coordinates":[[[-72.94479,20.79216],[-71.13087,20.98822],[-70.63262,21.53631],[-72.41726,22.40371],[-72.94479,20.79216]]]}},{"type":"Feature","properties":{"id":"MT"},"geometry":{"type":"Polygon","coordinates":[[[13.4634,35.88474],[14.74801,35.36688],[15.31045,36.18238],[14.12059,36.67252],[13.4634,35.88474]]]}},{"type":"Feature","properties":{"id":"GR"},"geometry":{"type":"Polygon","coordinates":[[[19.0384,40.35325],[22.5213,33.45682],[29.79413,35.94445],[29.59233,36.17753],[29.12174,36.12466],[28.23708,36.56812],[27.95037,36.46155],[27.89482,36.69898],[27.46117,36.53789],[27.24613,36.71622],[27.45627,36.9008],[27.18011,36.95503],[27.14757,37.32],[26.95583,37.64989],[26.99377,37.69034],[27.16428,37.72343],[27.05537,37.9131],[26.21136,38.17558],[26.24183,38.44695],[26.32173,38.48731],[26.21136,38.65436],[26.61814,38.81372],[26.70773,39.0312],[26.43357,39.43096],[25.94257,39.39358],[25.61285,40.17161],[26.04292,40.3958],[25.94795,40.72797],[26.03489,40.73051],[26.0754,40.72772],[26.08638,40.73214],[26.12495,40.74283],[26.12854,40.77339],[26.15685,40.80709],[26.21351,40.83298],[26.20856,40.86048],[26.25526,40.91286],[26.29441,40.89119],[26.28623,40.93005],[26.32259,40.94042],[26.35894,40.94292],[26.33297,40.98388],[26.36615,41.0128],[26.32049,41.06382],[26.32259,41.24929],[26.39861,41.25053],[26.5209,41.33993],[26.5837,41.32131],[26.62997,41.34613],[26.62811,41.41531],[26.59742,41.48058],[26.59566,41.60754],[26.52957,41.62179],[26.47958,41.67037],[26.35957,41.71149],[26.30255,41.70925],[26.2654,41.71544],[26.22888,41.74139],[26.21325,41.73223],[26.16841,41.74858],[26.06148,41.70345],[26.07083,41.64584],[26.15146,41.60828],[26.1478,41.55432],[26.17951,41.55409],[26.18711,41.51731],[26.15055,41.4772],[26.20288,41.43943],[26.16548,41.42278],[26.15072,41.3616],[25.88636,41.30431],[25.8266,41.34563],[25.70507,41.29209],[25.67178,41.30954],[25.54824,41.31331],[25.53256,41.28103],[25.49377,41.28657],[25.28322,41.23411],[25.11611,41.34212],[24.942,41.38685],[24.90928,41.40876],[24.86136,41.39298],[24.82514,41.4035],[24.8041,41.34913],[24.71529,41.41928],[24.61129,41.42278],[24.53676,41.56049],[24.30513,41.51297],[24.27124,41.57682],[24.18126,41.51735],[24.10063,41.54796],[24.06323,41.53222],[24.07396,41.46665],[23.96907,41.43918],[23.91551,41.48279],[23.90298,41.45437],[23.80148,41.43943],[23.77106,41.40044],[23.67644,41.41139],[23.63203,41.37632],[23.52453,41.40262],[23.40416,41.39999],[23.34088,41.36366],[23.31727,41.40687],[23.22153,41.36874],[23.19969,41.31739],[22.93334,41.34104],[22.81199,41.3398],[22.76882,41.32384],[22.7477,41.16392],[22.71266,41.13945],[22.65306,41.18168],[22.62852,41.14385],[22.58295,41.11568],[22.55965,41.13128],[22.4224,41.11809],[22.25452,41.1656],[22.17629,41.15969],[22.1424,41.12449],[22.06527,41.15617],[21.90869,41.09191],[21.91085,41.05683],[21.76237,40.92596],[21.69601,40.9429],[21.66778,40.90066],[21.57079,40.86445],[21.53007,40.90759],[21.41555,40.9173],[21.35595,40.87578],[21.25779,40.86165],[21.21105,40.8855],[21.15262,40.85546],[20.97887,40.85475],[20.98396,40.79109],[20.95752,40.76982],[20.98134,40.76046],[21.05833,40.66586],[21.03932,40.56299],[20.96908,40.51526],[20.94925,40.46625],[20.83688,40.47882],[20.7906,40.42726],[20.78234,40.35803],[20.71789,40.27739],[20.67162,40.09433],[20.62566,40.0897],[20.60357,40.07367],[20.55593,40.06524],[20.51297,40.08168],[20.48487,40.06271],[20.42373,40.06777],[20.37911,39.99058],[20.31135,39.99438],[20.41546,39.82832],[20.41475,39.81437],[20.38572,39.78516],[20.30804,39.81563],[20.29152,39.80421],[20.31961,39.72799],[20.27412,39.69884],[20.22707,39.67459],[20.22376,39.64532],[20.15988,39.652],[20.12956,39.65805],[20.05189,39.69112],[20.00957,39.69227],[19.96394,39.64841],[19.96042,39.81638],[19.0384,40.35325]]]}},{"type":"Feature","properties":{"id":"PN"},"geometry":{"type":"Polygon","coordinates":[[[-133.61511,-21.93325],[-133.59543,-28.4709],[-122.0366,-24.55017],[-133.61511,-21.93325]]]}},{"type":"Feature","properties":{"id":"TO"},"geometry":{"type":"Polygon","coordinates":[[[-180,-24.21376],[-173.10761,-24.19665],[-173.11048,-23.23027],[-173.13438,-14.94228],[-174.17905,-14.94502],[-176.76826,-14.95183],[-176.74538,-22.89767],[-180,-22.90585],[-180,-24.21376]]]}},{"type":"Feature","properties":{"id":"WS"},"geometry":{"type":"Polygon","coordinates":[[[-174.18596,-12.48057],[-174.17905,-14.94502],[-173.13438,-14.94228],[-171.14262,-14.93704],[-171.14953,-12.4725],[-174.18596,-12.48057]]]}},{"type":"Feature","properties":{"id":"WF"},"geometry":{"type":"Polygon","coordinates":[[[-178.60852,-12.49232],[-178.60161,-14.95666],[-176.76826,-14.95183],[-174.17905,-14.94502],[-174.18596,-12.48057],[-178.60852,-12.49232]]]}},{"type":"Feature","properties":{"id":"PF"},"geometry":{"type":"Polygon","coordinates":[[[-156.4957,-12.32002],[-156.46451,-23.21255],[-156.44843,-28.52556],[-133.59543,-28.4709],[-133.61511,-21.93325],[-133.65593,-7.46952],[-149.6249,-7.51261],[-149.61166,-12.30171],[-156.4957,-12.32002]]]}},{"type":"Feature","properties":{"id":"AS"},"geometry":{"type":"Polygon","coordinates":[[[-174.18596,-12.48057],[-171.14953,-12.4725],[-171.14262,-14.93704],[-167.73854,-14.92809],[-167.75195,-10.12005],[-174.17993,-10.13616],[-174.18596,-12.48057]]]}},{"type":"Feature","properties":{"id":"US-HI"},"geometry":{"type":"Polygon","coordinates":[[[-179.2458,29.18869],[-179.23433,18.15359],[-154.13058,18.17333],[-154.14205,29.20682],[-176.83456,29.19028],[-176.81808,27.68129],[-177.84531,27.68616],[-177.8563,29.18961],[-179.2458,29.18869]]]}},{"type":"Feature","properties":{"id":"CV"},"geometry":{"type":"Polygon","coordinates":[[[-25.86,17.60587],[-25.82546,14.43014],[-22.19117,14.46693],[-22.22571,17.64208],[-25.86,17.60587]]]}},{"type":"Feature","properties":{"id":"SH"},"geometry":{"type":"Polygon","coordinates":[[[-14.60614,-7.38624],[-14.56953,-8.25719],[-13.48367,-36.6746],[-13.41694,-37.88844],[-9.58838,-40.85412],[-5.42129,-15.86551],[-13.99188,-7.95424],[-14.60614,-7.38624]]]}},{"type":"Feature","properties":{"id":"CP"},"geometry":{"type":"Polygon","coordinates":[[[-109.6462,9.84394],[-108.755,9.84085],[-108.75183,10.72712],[-109.64303,10.7302],[-109.6462,9.84394]]]}},{"type":"Feature","properties":{"id":"MX"},"geometry":{"type":"Polygon","coordinates":[[[-118.94251,18.80654],[-92.37213,14.39277],[-92.22756,14.53116],[-92.21554,14.55293],[-92.18833,14.57079],[-92.1625,14.64936],[-92.14696,14.66115],[-92.14756,14.67925],[-92.14273,14.69003],[-92.16851,14.74931],[-92.17783,14.84644],[-92.1423,14.88647],[-92.14788,14.98341],[-92.04836,15.06278],[-92.20983,15.26077],[-91.73182,16.07371],[-90.44567,16.07573],[-90.40499,16.40524],[-90.61212,16.49832],[-90.69064,16.70697],[-91.04436,16.92175],[-91.43809,17.25373],[-90.99199,17.25192],[-90.98678,17.81655],[-89.14985,17.81563],[-89.15105,17.95104],[-89.03839,18.0067],[-88.87451,17.89044],[-88.71505,18.0707],[-88.71322,18.11387],[-88.48242,18.49164],[-88.1031,18.48551],[-87.90671,18.15213],[-87.87604,18.18313],[-87.86657,18.19971],[-87.85693,18.18266],[-87.84815,18.18511],[-87.24084,17.80373],[-85.9092,21.8218],[-96.92418,25.97377],[-97.13927,25.96583],[-97.27535,25.94592],[-97.35946,25.92189],[-97.37246,25.84373],[-97.4304,25.84516],[-97.45941,25.87841],[-97.49743,25.8866],[-97.49765,25.89934],[-97.50821,25.88911],[-97.51773,25.88671],[-97.64528,26.01544],[-97.86337,26.05948],[-97.95155,26.0625],[-97.97017,26.05232],[-98.24603,26.07191],[-98.28429,26.1055],[-98.31167,26.10781],[-98.34154,26.15058],[-98.44505,26.20627],[-98.49002,26.21335],[-98.60298,26.25462],[-98.66477,26.23984],[-98.82116,26.35465],[-99.00546,26.3925],[-99.03053,26.41249],[-99.08477,26.39849],[-99.26044,26.80936],[-99.44515,27.04032],[-99.43313,27.2096],[-99.53573,27.30926],[-99.49744,27.43746],[-99.482,27.47128],[-99.48045,27.49016],[-99.50208,27.50021],[-99.52955,27.49747],[-99.51478,27.55836],[-99.55409,27.61314],[-99.73972,27.69568],[-100.29247,28.27883],[-100.35255,28.48679],[-100.50029,28.66117],[-100.51222,28.70679],[-100.5075,28.74066],[-100.52313,28.75598],[-100.59809,28.88197],[-100.63689,28.90812],[-100.67294,29.09744],[-100.79696,29.24688],[-100.87982,29.296],[-100.94053,29.33399],[-100.94579,29.34523],[-100.96725,29.3477],[-101.01128,29.36947],[-101.05686,29.44738],[-101.47277,29.7744],[-102.60596,29.8192],[-103.15787,28.93865],[-104.37752,29.54255],[-104.39363,29.55396],[-104.3969,29.57105],[-104.5171,29.64671],[-104.67567,30.1469],[-104.87514,30.53003],[-105.78426,31.19518],[-106.00363,31.39181],[-106.08158,31.39907],[-106.21204,31.46981],[-106.23711,31.51262],[-106.24612,31.54193],[-106.27924,31.56061],[-106.30122,31.60989],[-106.30319,31.62214],[-106.33419,31.66303],[-106.34864,31.69663],[-106.3718,31.71165],[-106.38003,31.73151],[-106.41773,31.75196],[-106.43419,31.75478],[-106.45244,31.76523],[-106.46726,31.75998],[-106.47206,31.7509],[-106.48815,31.74769],[-106.50111,31.75714],[-106.50962,31.76155],[-106.51251,31.76922],[-106.52266,31.77509],[-106.529,31.784],[-108.20899,31.78534],[-108.20979,31.33316],[-109.05235,31.3333],[-109.56227,31.33402],[-111.07523,31.33232],[-112.34627,31.73488],[-114.63109,32.43959],[-114.82011,32.49609],[-114.79524,32.55731],[-114.81141,32.55543],[-114.80584,32.62028],[-114.76736,32.64094],[-114.71871,32.71894],[-115.49377,32.66517],[-115.88053,32.63624],[-116.58627,32.57969],[-116.87852,32.55531],[-117.12426,32.53431],[-118.48109,32.5991],[-118.94251,18.80654]]]}},{"type":"Feature","properties":{"id":"IE"},"geometry":{"type":"Polygon","coordinates":[[[-13.3292,54.24486],[-10.17712,50.85267],[-5.79914,52.03902],[-5.37267,53.63269],[-5.81146,53.88396],[-6.26218,54.09785],[-6.29003,54.11278],[-6.32694,54.09337],[-6.36279,54.11248],[-6.36605,54.07234],[-6.47849,54.06947],[-6.62842,54.03503],[-6.66264,54.0666],[-6.6382,54.17071],[-6.70175,54.20218],[-6.74575,54.18788],[-6.81583,54.22791],[-6.83383,54.26291],[-6.87692,54.28036],[-6.85179,54.29176],[-6.87775,54.34682],[-7.02034,54.4212],[-7.19145,54.31296],[-7.14908,54.22732],[-7.25012,54.20063],[-7.26316,54.13863],[-7.29493,54.12013],[-7.29687,54.1354],[-7.28017,54.16714],[-7.29157,54.17191],[-7.34005,54.14698],[-7.30553,54.11869],[-7.32834,54.11475],[-7.44567,54.1539],[-7.4799,54.12239],[-7.55812,54.12239],[-7.69501,54.20731],[-7.81397,54.20159],[-7.8596,54.21779],[-7.86552,54.29318],[-7.95324,54.30721],[-7.99118,54.34675],[-8.04555,54.36292],[-8.16206,54.44204],[-8.14172,54.45063],[-8.1782,54.46614],[-8.09297,54.47697],[-8.09924,54.48405],[-8.04216,54.49297],[-8.00714,54.54528],[-7.85084,54.53358],[-7.82827,54.55539],[-7.77445,54.5839],[-7.68965,54.61736],[-7.93293,54.66603],[-7.83352,54.73854],[-7.75041,54.7103],[-7.64449,54.75265],[-7.54671,54.74606],[-7.54508,54.79401],[-7.47626,54.83084],[-7.4473,54.87003],[-7.44404,54.9403],[-7.40004,54.94498],[-7.4033,55.00391],[-7.34367,55.04808],[-7.2471,55.06933],[-6.9734,55.19878],[-6.71944,55.27952],[-6.79943,55.54107],[-7.93366,55.84142],[-13.3292,54.24486]]]}},{"type":"Feature","properties":{"id":"ST"},"geometry":{"type":"Polygon","coordinates":[[[5.9107,-0.09539],[6.69416,-0.53945],[7.47763,0.84469],[8.0168,1.79377],[7.23334,2.23756],[6.69947,1.30108],[5.9107,-0.09539]]]}},{"type":"Feature","properties":{"id":"GD"},"geometry":{"type":"Polygon","coordinates":[[[-62.09312,11.91803],[-61.57127,11.71308],[-61.18064,12.44553],[-61.65863,12.64273],[-62.09312,11.91803]]]}},{"type":"Feature","properties":{"id":"AG"},"geometry":{"type":"Polygon","coordinates":[[[-62.62949,16.82364],[-62.52079,16.69392],[-62.14123,17.02632],[-61.83929,16.66647],[-61.44461,16.81958],[-61.45192,17.43725],[-61.45764,17.9187],[-62.12601,17.9235],[-62.27053,17.22145],[-62.62949,16.82364]]]}},{"type":"Feature","properties":{"id":"AF"},"geometry":{"type":"Polygon","coordinates":[[[60.51097,34.10356],[60.5838,33.80793],[60.5485,33.73422],[60.64147,33.57712],[60.94802,33.51535],[60.87706,33.49274],[60.58547,33.1341],[60.87558,32.20873],[60.85498,31.48782],[61.70929,31.37391],[61.7962,31.17755],[61.83257,31.0452],[61.82985,30.97731],[61.77732,30.92593],[61.80829,30.84224],[60.87249,29.8597],[62.47751,29.40782],[63.5717,29.48652],[63.8891,29.43705],[63.99793,29.39563],[64.03449,29.41343],[64.14539,29.38726],[64.19796,29.50407],[64.62116,29.58903],[65.06584,29.53045],[66.25305,29.85017],[66.37458,29.9698],[66.23609,30.06321],[66.35948,30.41818],[66.28413,30.57001],[66.38823,30.92947],[66.42668,30.95251],[66.45054,30.95361],[66.45423,30.95777],[66.57852,30.97657],[66.68166,31.07597],[66.72561,31.20526],[66.78588,31.20942],[66.86571,31.2828],[67.04147,31.31561],[67.02724,31.2379],[67.15547,31.24531],[67.31357,31.19371],[67.68487,31.30202],[67.72384,31.32534],[67.80229,31.31756],[67.79079,31.41826],[67.62374,31.40473],[67.58323,31.52772],[67.72056,31.52304],[67.86066,31.6259],[68.00071,31.6564],[68.1655,31.82691],[68.25614,31.80357],[68.27605,31.75863],[68.4649,31.76553],[68.57475,31.83158],[68.6956,31.75687],[68.79997,31.61665],[68.91078,31.59687],[68.95995,31.64822],[69.00939,31.62249],[69.11514,31.70782],[69.20577,31.85957],[69.3225,31.93186],[69.27032,32.14141],[69.28785,32.29235],[69.23599,32.45946],[69.2712,32.53046],[69.38155,32.56601],[69.44747,32.6678],[69.43649,32.7302],[69.38018,32.76601],[69.46758,32.85399],[69.54731,32.87504],[69.49813,32.88629],[69.49341,33.02025],[69.57656,33.09911],[69.71526,33.09911],[69.79766,33.13247],[69.84601,33.09111],[70.02563,33.14282],[70.07114,33.21951],[70.13686,33.21064],[70.32775,33.34496],[70.17062,33.53535],[70.20141,33.64387],[70.14785,33.6553],[70.14236,33.71701],[69.99801,33.73576],[69.94986,33.83056],[69.91699,33.85331],[69.90034,33.90333],[69.85794,33.92562],[69.87307,33.9689],[69.90203,34.04194],[70.54336,33.9463],[70.88119,33.97933],[71.08068,34.06538],[71.06933,34.10564],[71.09307,34.11961],[71.09453,34.13524],[71.13078,34.16503],[71.12815,34.26619],[71.17758,34.36306],[71.023,34.4508],[71.0089,34.54568],[71.11536,34.62911],[71.0842,34.68975],[71.28356,34.80882],[71.29472,34.87728],[71.51275,34.97473],[71.50194,35.00398],[71.55996,35.02627],[71.52938,35.09023],[71.68647,35.21091],[71.5541,35.28776],[71.54294,35.31037],[71.65781,35.44284],[71.49917,35.6267],[71.55273,35.71483],[71.37969,35.95865],[71.19505,36.04134],[71.60491,36.39429],[71.80267,36.49924],[72.18135,36.71838],[72.6323,36.84601],[73.82685,36.91421],[74.04856,36.82648],[74.43389,37.00977],[74.53739,36.96224],[74.56453,37.03023],[74.49981,37.24518],[74.80605,37.21565],[74.88887,37.23275],[74.8294,37.3435],[74.68383,37.3948],[74.56161,37.37734],[74.23339,37.41116],[74.20308,37.34208],[73.8564,37.26158],[73.82552,37.22659],[73.64974,37.23643],[73.61129,37.27469],[73.76647,37.33913],[73.77197,37.4417],[73.29633,37.46495],[73.06884,37.31729],[72.80862,37.22513],[72.66381,37.02014],[72.54095,37.00007],[72.33673,36.98596],[72.04215,36.82],[71.83977,36.67888],[71.67034,36.67268],[71.57195,36.74943],[71.51502,36.89128],[71.46683,36.95222],[71.46923,36.99925],[71.45578,37.03094],[71.43097,37.05855],[71.44127,37.11856],[71.4494,37.18137],[71.4555,37.21418],[71.47386,37.2269],[71.48339,37.23937],[71.48454,37.26017],[71.50725,37.31563],[71.49821,37.31975],[71.48701,37.33312],[71.4949,37.36961],[71.48906,37.38055],[71.47451,37.38625],[71.47945,37.40664],[71.4973,37.40715],[71.49612,37.4279],[71.52648,37.47983],[71.50616,37.50733],[71.50434,37.52701],[71.4952,37.53926],[71.51168,37.61484],[71.51957,37.62035],[71.5267,37.63313],[71.53138,37.67835],[71.54236,37.69407],[71.55241,37.73515],[71.53052,37.76521],[71.54324,37.77104],[71.55481,37.78509],[71.59255,37.79956],[71.59369,37.81432],[71.58468,37.84774],[71.59832,37.87404],[71.58614,37.89551],[71.59257,37.92294],[71.51565,37.95349],[71.32871,37.88564],[71.296,37.93403],[71.2809,37.91995],[71.2505,37.92724],[71.27457,37.96653],[71.26848,37.99156],[71.27642,38.00603],[71.29427,38.0178],[71.28354,38.0417],[71.30354,38.04359],[71.32942,38.11146],[71.34997,38.1494],[71.37564,38.1602],[71.36461,38.1963],[71.37362,38.25691],[71.33272,38.27427],[71.33216,38.30549],[71.26041,38.31135],[71.21415,38.32872],[71.1451,38.40106],[71.10957,38.40671],[71.10592,38.42077],[71.09484,38.42414],[71.05609,38.39959],[71.0315,38.45231],[70.98936,38.49011],[70.92451,38.43039],[70.88719,38.46826],[70.84376,38.44688],[70.82538,38.45394],[70.81697,38.44507],[70.80521,38.44447],[70.79766,38.44944],[70.78702,38.45031],[70.78581,38.45502],[70.77132,38.45548],[70.75455,38.4252],[70.72485,38.4131],[70.69367,38.41832],[70.67438,38.40597],[70.67603,38.38852],[70.69461,38.37056],[70.64908,38.34885],[70.61239,38.34862],[70.60389,38.28202],[70.54673,38.24541],[70.52973,38.20277],[70.48999,38.12004],[70.36683,38.04231],[70.32889,37.99853],[70.29653,37.98689],[70.26958,37.93885],[70.17465,37.93478],[70.18478,37.84639],[70.27694,37.81258],[70.28243,37.66706],[70.15015,37.52519],[69.9545,37.56662],[69.9487,37.59148],[69.93362,37.61378],[69.85476,37.60668],[69.82618,37.57233],[69.51888,37.5844],[69.45367,37.48957],[69.37007,37.40548],[69.45522,37.23497],[69.39874,37.16756],[69.37145,37.16606],[69.31789,37.11693],[69.25249,37.09393],[69.03274,37.25174],[68.99156,37.31406],[68.88168,37.33368],[68.92719,37.28074],[68.91189,37.26704],[68.82093,37.32839],[68.8278,37.24221],[68.69699,37.30396],[68.6194,37.19915],[68.41888,37.13906],[68.41201,37.10402],[68.29253,37.10621],[68.27605,37.00977],[68.18542,37.02074],[68.02194,36.91923],[67.87917,37.0591],[67.79113,37.08311],[67.78329,37.1834],[67.51868,37.26102],[67.2581,37.17216],[67.2224,37.24545],[67.13039,37.27168],[67.08232,37.35469],[66.95598,37.40162],[66.64699,37.32958],[66.55743,37.35409],[66.33888,37.31133],[65.7154,37.5541],[65.64485,37.44515],[65.64263,37.34388],[65.55198,37.24481],[64.97945,37.21913],[64.61141,36.6351],[64.62514,36.44311],[64.57295,36.34362],[64.43288,36.24401],[64.05385,36.10433],[64.05879,36.04708],[63.53693,35.94764],[63.50089,35.89934],[63.29944,35.85684],[63.12276,35.86208],[63.10834,35.81976],[63.19554,35.71369],[63.25288,35.68264],[63.10079,35.63024],[63.12276,35.53196],[63.10031,35.43256],[62.90853,35.37086],[62.74098,35.25432],[62.62288,35.22067],[62.47813,35.28507],[62.3017,35.12815],[62.29076,35.25728],[62.15871,35.33278],[62.06365,35.43423],[61.96718,35.45458],[61.77693,35.41341],[61.58742,35.43803],[61.33924,35.62353],[61.27349,35.60734],[61.19041,35.29355],[61.0991,35.27845],[61.12831,35.09938],[61.06926,34.82139],[61.00197,34.70631],[61.00192,34.62558],[60.74684,34.5173],[60.91321,34.30411],[60.70529,34.30912],[60.51097,34.10356]]]}},{"type":"Feature","properties":{"id":"AO"},"geometry":{"type":"Polygon","coordinates":[[[11.26455,-17.25284],[11.75063,-17.25013],[11.79313,-17.27066],[12.06573,-17.14275],[12.54878,-17.25148],[12.90653,-17.03233],[13.3695,-16.97635],[13.97735,-17.42959],[14.21493,-17.3911],[18.42063,-17.38963],[18.61118,-17.61751],[18.84226,-17.80375],[19.76165,-17.89903],[20.27046,-17.87028],[20.75385,-18.00746],[21.18747,-17.93235],[21.42831,-18.02509],[23.47474,-17.62877],[23.20038,-17.47563],[22.17217,-16.50269],[22.00323,-16.18028],[21.97988,-13.00148],[24.03339,-12.99091],[23.90937,-12.844],[24.06672,-12.29058],[23.98804,-12.13149],[24.02603,-11.15368],[24.00027,-10.89356],[23.86868,-11.02856],[23.45631,-10.946],[23.16602,-11.10577],[22.54205,-11.05784],[22.25951,-11.24911],[22.17954,-10.85884],[22.32604,-10.76291],[22.19039,-9.94628],[21.84856,-9.59871],[21.78726,-7.28414],[20.56263,-7.28566],[20.61689,-6.90876],[20.31846,-6.91953],[20.30218,-6.98955],[19.5469,-7.00195],[19.33698,-7.99743],[18.33635,-8.00126],[17.5828,-8.13784],[16.96282,-7.21787],[16.55507,-5.85631],[13.04371,-5.87078],[12.42245,-6.07585],[11.95767,-5.94705],[12.20376,-5.76338],[12.26557,-5.74031],[12.52318,-5.74353],[12.52301,-5.17481],[12.53599,-5.1618],[12.53586,-5.14658],[12.51589,-5.1332],[12.49815,-5.14058],[12.45557,-5.08427],[12.60251,-5.01715],[12.62723,-4.94969],[12.71238,-4.95474],[12.73941,-4.89325],[12.8013,-4.84861],[12.85606,-4.74734],[13.09656,-4.69037],[13.10153,-4.68319],[12.84387,-4.4111],[12.76844,-4.38709],[12.64835,-4.55937],[12.40964,-4.60609],[12.32957,-4.79011],[12.19808,-4.79456],[12.16634,-4.89507],[12.08805,-4.95978],[12.01335,-5.03191],[11.50888,-5.33417],[11.26455,-17.25284]]]}},{"type":"Feature","properties":{"id":"AL"},"geometry":{"type":"Polygon","coordinates":[[[19.0384,40.35325],[19.96042,39.81638],[19.96394,39.64841],[20.00957,39.69227],[20.05189,39.69112],[20.12956,39.65805],[20.15988,39.652],[20.22376,39.64532],[20.22707,39.67459],[20.27412,39.69884],[20.31961,39.72799],[20.29152,39.80421],[20.30804,39.81563],[20.38572,39.78516],[20.41475,39.81437],[20.41546,39.82832],[20.31135,39.99438],[20.37911,39.99058],[20.42373,40.06777],[20.48487,40.06271],[20.51297,40.08168],[20.55593,40.06524],[20.60357,40.07367],[20.62566,40.0897],[20.67162,40.09433],[20.71789,40.27739],[20.78234,40.35803],[20.7906,40.42726],[20.83688,40.47882],[20.94925,40.46625],[20.96908,40.51526],[21.03932,40.56299],[21.05833,40.66586],[20.98134,40.76046],[20.95752,40.76982],[20.98396,40.79109],[20.97887,40.85475],[20.97693,40.90103],[20.94989,40.92025],[20.84587,40.9375],[20.81567,40.89662],[20.7316,40.91069],[20.67394,41.08015],[20.59832,41.09066],[20.58546,41.11179],[20.59715,41.13644],[20.51731,41.23147],[20.49756,41.33789],[20.52503,41.34279],[20.56237,41.40546],[20.51301,41.442],[20.49456,41.49173],[20.45331,41.51436],[20.45809,41.5549],[20.52103,41.56473],[20.55508,41.58113],[20.51769,41.65975],[20.52937,41.69292],[20.51301,41.72433],[20.53405,41.78099],[20.57144,41.7897],[20.55976,41.87068],[20.59524,41.8818],[20.57946,41.91593],[20.63069,41.94913],[20.59434,42.03879],[20.55633,42.08173],[20.56955,42.12097],[20.48857,42.25444],[20.3819,42.3029],[20.34479,42.32656],[20.24399,42.32168],[20.22591,42.41572],[20.16283,42.50627],[20.07761,42.55582],[20.01834,42.54622],[20.00842,42.5109],[19.9324,42.51699],[19.82333,42.46581],[19.76549,42.50237],[19.74731,42.57422],[19.77375,42.58517],[19.73244,42.66299],[19.65972,42.62774],[19.4836,42.40831],[19.42537,42.37287],[19.42142,42.32745],[19.2834,42.18096],[19.40185,42.1028],[19.37996,42.07357],[19.36867,42.02564],[19.38571,41.96383],[19.34924,41.95751],[19.36949,41.9229],[19.34203,41.90591],[19.37842,41.88598],[19.37284,41.84597],[19.26406,41.74971],[19.0384,40.35325]]]}},{"type":"Feature","properties":{"id":"AD"},"geometry":{"type":"Polygon","coordinates":[[[1.41245,42.53539],[1.44759,42.54431],[1.46661,42.50949],[1.41648,42.48315],[1.43838,42.47848],[1.44529,42.43724],[1.5127,42.42959],[1.55073,42.43299],[1.55937,42.45808],[1.57953,42.44957],[1.58933,42.46275],[1.65674,42.47125],[1.66826,42.50779],[1.70571,42.48867],[1.72334,42.4973],[1.73927,42.55523],[1.7858,42.57698],[1.72588,42.59098],[1.73452,42.61515],[1.68267,42.62533],[1.6625,42.61982],[1.63485,42.62957],[1.60085,42.62703],[1.55418,42.65669],[1.50867,42.64483],[1.48043,42.65203],[1.46718,42.63296],[1.47986,42.61346],[1.44197,42.60217],[1.42512,42.58292],[1.44529,42.56722],[1.4234,42.55959],[1.41245,42.53539]]]}},{"type":"Feature","properties":{"id":"AR"},"geometry":{"type":"Polygon","coordinates":[[[-73.55259,-49.92488],[-73.15765,-50.78337],[-72.31343,-50.58411],[-72.26257,-51.24515],[-72.43492,-51.584],[-72.33329,-51.58698],[-72.29733,-51.69804],[-71.91204,-51.88475],[-72.02791,-51.97673],[-71.9165,-52.00094],[-69.97824,-52.00845],[-68.80153,-52.23831],[-68.56979,-52.32411],[-68.4165,-52.33208],[-68.60702,-52.65781],[-68.60733,-54.9125],[-67.99952,-54.88833],[-67.10428,-54.93922],[-66.07313,-55.19618],[-63.58514,-55.05744],[-62.38818,-51.70652],[-62.10074,-50.6407],[-55.71154,-35.78518],[-57.83001,-34.69099],[-58.34425,-34.15035],[-58.44442,-33.84033],[-58.40475,-33.11777],[-58.1224,-32.98842],[-58.22362,-32.52416],[-58.10036,-32.25338],[-58.20252,-31.86966],[-58.00076,-31.65016],[-58.0023,-31.53084],[-58.07569,-31.44916],[-57.98127,-31.3872],[-57.9908,-31.34924],[-57.86729,-31.06352],[-57.89476,-30.95994],[-57.8024,-30.77193],[-57.89115,-30.49572],[-57.64859,-30.35095],[-57.61478,-30.25165],[-57.65384,-30.18549],[-57.09386,-29.74211],[-56.81251,-29.48154],[-56.62789,-29.18073],[-56.57295,-29.11357],[-56.54171,-29.11447],[-56.05265,-28.62651],[-56.00458,-28.60421],[-56.01729,-28.51223],[-55.65418,-28.18304],[-55.6262,-28.17124],[-55.33303,-27.94661],[-55.16872,-27.86224],[-55.1349,-27.89759],[-54.90805,-27.73149],[-54.90159,-27.63132],[-54.67657,-27.57214],[-54.50416,-27.48232],[-54.41888,-27.40882],[-54.19268,-27.30751],[-54.19062,-27.27639],[-54.15978,-27.2889],[-53.80144,-27.09844],[-53.73372,-26.6131],[-53.68269,-26.33359],[-53.64505,-26.28089],[-53.64186,-26.25976],[-53.64632,-26.24798],[-53.63881,-26.25075],[-53.63739,-26.2496],[-53.65237,-26.23289],[-53.65018,-26.19501],[-53.73968,-26.10012],[-53.73391,-26.07006],[-53.7264,-26.0664],[-53.73086,-26.05842],[-53.73511,-26.04211],[-53.83691,-25.94849],[-53.90831,-25.55513],[-54.52926,-25.62846],[-54.5502,-25.58915],[-54.59398,-25.59224],[-54.65904,-25.6802],[-54.589,-25.8277],[-54.62234,-25.92888],[-54.60681,-25.97411],[-54.68431,-25.99054],[-54.64084,-26.1018],[-54.70732,-26.45099],[-54.79941,-26.52551],[-54.8022,-26.67031],[-54.9337,-26.67829],[-54.95747,-26.78906],[-55.00584,-26.78754],[-55.04991,-26.79549],[-55.15342,-26.88226],[-55.12836,-26.94563],[-55.20183,-26.97011],[-55.25243,-26.93808],[-55.31375,-26.96751],[-55.37864,-26.96614],[-55.44542,-27.02105],[-55.45932,-27.1104],[-55.56301,-27.10085],[-55.5661,-27.16249],[-55.63236,-27.17509],[-55.57262,-27.24501],[-55.60043,-27.27934],[-55.58927,-27.32144],[-55.72986,-27.44278],[-55.79269,-27.44704],[-55.89195,-27.3467],[-56.05018,-27.30528],[-56.73614,-27.46289],[-56.68018,-27.56428],[-56.74129,-27.60506],[-57.32013,-27.41566],[-57.66002,-27.36018],[-58.07373,-27.23509],[-58.60038,-27.30177],[-58.65321,-27.14028],[-58.25088,-26.7548],[-58.21243,-26.48962],[-58.14067,-26.25585],[-58.10377,-26.21967],[-58.15938,-26.18424],[-58.1188,-26.16704],[-57.87176,-25.93604],[-57.70105,-25.65514],[-57.60904,-25.60701],[-57.5517,-25.45303],[-57.66307,-25.35445],[-57.70186,-25.32005],[-57.6989,-25.31175],[-57.69972,-25.30418],[-57.70667,-25.30038],[-57.70564,-25.29567],[-57.69867,-25.29452],[-57.69459,-25.28653],[-57.69953,-25.28346],[-57.70397,-25.29144],[-57.70547,-25.29146],[-57.70285,-25.28288],[-57.70867,-25.286],[-57.71322,-25.28461],[-57.71064,-25.28123],[-57.71208,-25.27302],[-57.72012,-25.27747],[-57.72264,-25.27762],[-57.72304,-25.26689],[-57.72929,-25.24644],[-57.80821,-25.13863],[-58.25492,-24.92528],[-58.32624,-24.9999],[-58.49936,-24.857],[-58.81668,-24.76522],[-58.86234,-24.73155],[-59.34333,-24.48839],[-59.48444,-24.33802],[-60.03719,-24.01071],[-60.28163,-24.04436],[-60.99815,-23.79225],[-61.1111,-23.60111],[-61.9749,-23.02665],[-62.19909,-22.69955],[-62.2257,-22.55346],[-62.50937,-22.38119],[-62.53962,-22.36912],[-62.64455,-22.25091],[-62.8078,-22.12534],[-62.81124,-21.9987],[-63.66092,-21.99916],[-63.67911,-22.05107],[-63.68272,-22.05473],[-63.68654,-22.0397],[-63.68233,-22.0311],[-63.69212,-22.013],[-63.70963,-21.99934],[-63.93425,-22.00003],[-64.0357,-22.24096],[-64.155,-22.45133],[-64.23414,-22.55631],[-64.30186,-22.8768],[-64.33138,-22.87261],[-64.34417,-22.73985],[-64.36005,-22.72441],[-64.39009,-22.72251],[-64.4176,-22.67692],[-64.44065,-22.63864],[-64.42399,-22.53523],[-64.52459,-22.44189],[-64.56853,-22.35991],[-64.53832,-22.29131],[-64.58888,-22.25035],[-64.67174,-22.18957],[-64.74569,-22.18295],[-64.90014,-22.12136],[-64.99524,-22.08255],[-65.47435,-22.08908],[-65.48426,-22.09534],[-65.57743,-22.07675],[-65.5855,-22.09725],[-65.61299,-22.09671],[-65.7467,-22.10105],[-65.9261,-21.93335],[-66.04832,-21.9187],[-66.03836,-21.84829],[-66.24077,-21.77837],[-66.23502,-21.84572],[-66.27279,-21.95228],[-66.29714,-22.08741],[-66.7298,-22.23644],[-67.18382,-22.81525],[-66.99632,-22.99839],[-67.32284,-24.03517],[-68.24569,-24.39838],[-68.56909,-24.69831],[-68.38372,-25.08636],[-68.57622,-25.32505],[-68.38096,-26.177],[-68.55949,-26.28479],[-68.59048,-26.49861],[-68.27677,-26.90626],[-68.43363,-27.08414],[-68.77586,-27.16029],[-69.22504,-27.95042],[-69.66709,-28.44055],[-69.80969,-29.07185],[-70.01174,-29.31903],[-69.81262,-30.22822],[-70.14479,-30.36595],[-70.55832,-31.51559],[-69.88099,-33.34489],[-69.87386,-34.13344],[-70.51025,-35.26243],[-70.37155,-36.04243],[-71.03141,-36.47209],[-71.11312,-37.10447],[-71.19484,-37.64631],[-70.89532,-38.6923],[-71.41319,-38.89076],[-71.46125,-39.58134],[-71.67755,-40.09908],[-71.94293,-40.71109],[-71.72699,-42.12929],[-72.15541,-42.15941],[-72.16026,-42.90111],[-71.74896,-43.17714],[-71.89144,-43.47235],[-71.62433,-43.63905],[-71.8238,-44.38767],[-71.13853,-44.46931],[-71.22333,-44.77403],[-72.06985,-44.81756],[-71.30298,-45.25676],[-71.78192,-45.63132],[-71.61163,-45.98742],[-71.87461,-46.16984],[-71.72733,-46.27863],[-71.67154,-46.52591],[-71.64802,-46.67064],[-71.97177,-46.9036],[-71.86225,-47.1568],[-72.50478,-47.80586],[-72.27662,-48.28727],[-72.56126,-48.53934],[-72.53242,-48.79736],[-73.09655,-49.14342],[-73.45156,-49.79461],[-73.55259,-49.92488]]]}},{"type":"Feature","properties":{"id":"AT"},"geometry":{"type":"Polygon","coordinates":[[[9.53116,47.27029],[9.56766,47.24281],[9.55214,47.22395],[9.56826,47.22016],[9.58264,47.20673],[9.56539,47.17124],[9.62623,47.14685],[9.63395,47.08443],[9.61216,47.07732],[9.60717,47.06091],[9.87935,47.01337],[9.88266,46.93343],[9.98058,46.91434],[10.10715,46.84296],[10.22675,46.86942],[10.24128,46.93147],[10.30031,46.92093],[10.36933,47.00212],[10.48376,46.93891],[10.46999,46.85498],[10.54783,46.84505],[10.66405,46.87614],[10.75753,46.82258],[10.72974,46.78972],[11.01811,46.76214],[11.12094,46.93552],[11.33355,46.99862],[11.40724,46.96689],[11.50726,47.00642],[11.74789,46.98484],[12.20764,47.09821],[12.23291,47.04487],[12.11675,47.01241],[12.2006,46.88854],[12.27591,46.88651],[12.31378,46.79718],[12.37952,46.72452],[12.47875,46.67888],[12.59992,46.6595],[12.73361,46.63797],[12.94445,46.60401],[13.09123,46.59661],[13.27627,46.56059],[13.49599,46.55634],[13.7148,46.5222],[13.89837,46.52331],[14.00422,46.48474],[14.04002,46.49117],[14.12097,46.47724],[14.15989,46.43327],[14.28326,46.44315],[14.314,46.43327],[14.42608,46.44614],[14.45877,46.41717],[14.52176,46.42617],[14.56463,46.37208],[14.5942,46.43434],[14.66892,46.44936],[14.72185,46.49974],[14.81836,46.51046],[14.83549,46.56614],[14.86419,46.59411],[14.87129,46.61],[14.92283,46.60848],[14.9595,46.63293],[14.98024,46.6009],[15.01451,46.641],[15.14215,46.66131],[15.23711,46.63994],[15.41235,46.65556],[15.45514,46.63697],[15.46906,46.61321],[15.54431,46.6312],[15.55333,46.64988],[15.54533,46.66985],[15.59826,46.68908],[15.62317,46.67947],[15.63255,46.68069],[15.6365,46.6894],[15.6543,46.69228],[15.6543,46.70616],[15.67411,46.70735],[15.69523,46.69823],[15.72279,46.69548],[15.73823,46.70011],[15.76771,46.69863],[15.78518,46.70712],[15.8162,46.71897],[15.87691,46.7211],[15.94864,46.68769],[15.98512,46.68463],[15.99988,46.67947],[16.04036,46.6549],[16.04347,46.68694],[16.02808,46.71094],[15.99769,46.7266],[15.98432,46.74991],[15.99126,46.78199],[15.99054,46.82772],[16.05786,46.83927],[16.11282,46.86858],[16.19904,46.94134],[16.22403,46.939],[16.25989,46.96016],[16.27688,46.96312],[16.28202,47.00159],[16.51369,47.00084],[16.43936,47.03548],[16.52176,47.05747],[16.46134,47.09395],[16.52863,47.13974],[16.44932,47.14418],[16.46442,47.16845],[16.4523,47.18812],[16.42801,47.18422],[16.41739,47.20649],[16.43663,47.21127],[16.44142,47.25079],[16.47782,47.25918],[16.45104,47.41181],[16.49908,47.39416],[16.52414,47.41007],[16.57152,47.40868],[16.6718,47.46139],[16.64821,47.50155],[16.71059,47.52692],[16.64193,47.63114],[16.58699,47.61772],[16.4222,47.66537],[16.55129,47.72268],[16.53514,47.73837],[16.54779,47.75074],[16.61183,47.76171],[16.65679,47.74197],[16.71179,47.73676],[16.74886,47.68155],[16.82663,47.6831],[16.86757,47.72279],[16.87271,47.68802],[17.08953,47.70841],[17.05048,47.79377],[17.07347,47.80944],[17.00696,47.86411],[17.08275,47.87719],[17.11227,47.92685],[17.09786,47.97336],[17.16001,48.00636],[17.07039,48.0317],[17.09168,48.09366],[17.05735,48.14179],[17.02919,48.13996],[16.97701,48.17385],[16.89461,48.31332],[16.90903,48.32519],[16.84243,48.35258],[16.83317,48.38138],[16.83588,48.3844],[16.8497,48.38321],[16.85204,48.44968],[16.94611,48.53614],[16.93955,48.60371],[16.90354,48.71541],[16.79779,48.70998],[16.71883,48.73806],[16.68518,48.7281],[16.67008,48.77699],[16.46134,48.80865],[16.40915,48.74576],[16.37345,48.729],[16.06295,48.75477],[15.9851,48.78645],[15.83842,48.86815],[15.78048,48.87487],[15.75341,48.8516],[15.6921,48.85973],[15.61622,48.89541],[15.51357,48.91549],[15.48027,48.94481],[15.34823,48.98444],[15.28305,48.98831],[15.26177,48.95766],[15.16018,48.94229],[15.16937,48.96342],[15.15418,48.99424],[15.04088,49.01237],[14.99878,49.01444],[14.97612,48.96983],[14.98917,48.90082],[14.95072,48.79101],[14.98032,48.77959],[14.9782,48.7766],[14.98112,48.77524],[14.9758,48.76857],[14.95641,48.75915],[14.94773,48.76268],[14.81545,48.7874],[14.80999,48.77949],[14.80124,48.74719],[14.80584,48.73489],[14.72756,48.69502],[14.72099,48.60062],[14.66691,48.58029],[14.60808,48.62881],[14.56237,48.60374],[14.54675,48.61438],[14.4668,48.64646],[14.44483,48.64337],[14.44324,48.59244],[14.33732,48.55678],[14.27102,48.58097],[14.20691,48.5898],[14.09348,48.59466],[14.04516,48.62411],[14.00997,48.63937],[14.06151,48.66873],[13.98267,48.71022],[13.84023,48.76988],[13.82266,48.75544],[13.81863,48.73257],[13.79337,48.71375],[13.81791,48.69832],[13.81283,48.68426],[13.81901,48.6761],[13.82609,48.62345],[13.80038,48.59487],[13.80519,48.58026],[13.76921,48.55324],[13.7513,48.5624],[13.74816,48.53058],[13.72802,48.51208],[13.66113,48.53558],[13.65186,48.55092],[13.62508,48.55501],[13.59705,48.57013],[13.57535,48.55912],[13.51291,48.59023],[13.50131,48.58091],[13.50663,48.57506],[13.46967,48.55157],[13.45214,48.56472],[13.43695,48.55776],[13.45727,48.51092],[13.42527,48.45711],[13.43929,48.43386],[13.40709,48.37292],[13.30897,48.31575],[13.26039,48.29422],[13.18093,48.29577],[13.126,48.27867],[13.0851,48.27711],[13.02083,48.25689],[12.95306,48.20629],[12.87126,48.20318],[12.84475,48.16556],[12.836,48.1647],[12.8362,48.15876],[12.82673,48.15245],[12.80676,48.14979],[12.78595,48.12445],[12.7617,48.12796],[12.74973,48.10885],[12.76141,48.07373],[12.8549,48.01122],[12.87476,47.96195],[12.91683,47.95647],[12.9211,47.95135],[12.91985,47.94069],[12.92668,47.93879],[12.93419,47.94063],[12.93642,47.94436],[12.93886,47.94046],[12.94163,47.92927],[13.00588,47.84374],[12.98543,47.82896],[12.96311,47.79957],[12.93202,47.77302],[12.94371,47.76281],[12.9353,47.74788],[12.91711,47.74026],[12.90274,47.72513],[12.91333,47.7178],[12.92969,47.71094],[12.98578,47.7078],[13.01382,47.72116],[13.07692,47.68814],[13.09562,47.63304],[13.06407,47.60075],[13.06641,47.58577],[13.04537,47.58183],[13.05355,47.56291],[13.03252,47.53373],[13.04537,47.49426],[12.9998,47.46267],[12.98344,47.48716],[12.9624,47.47452],[12.85256,47.52741],[12.84672,47.54556],[12.80699,47.54477],[12.77427,47.58025],[12.82101,47.61493],[12.76492,47.64485],[12.77777,47.66689],[12.7357,47.6787],[12.6071,47.6741],[12.57438,47.63238],[12.53816,47.63553],[12.50076,47.62293],[12.44117,47.6741],[12.43883,47.6977],[12.37222,47.68433],[12.336,47.69534],[12.27991,47.68827],[12.26004,47.67725],[12.24017,47.69534],[12.26238,47.73544],[12.2542,47.7433],[12.22571,47.71776],[12.18303,47.70065],[12.16217,47.70105],[12.16769,47.68167],[12.18347,47.66663],[12.18507,47.65984],[12.19895,47.64085],[12.20801,47.61082],[12.20398,47.60667],[12.18568,47.6049],[12.17737,47.60121],[12.18145,47.61019],[12.17824,47.61506],[12.13734,47.60639],[12.05788,47.61742],[12.02282,47.61033],[12.0088,47.62451],[11.85572,47.60166],[11.84052,47.58354],[11.63934,47.59202],[11.60681,47.57881],[11.58811,47.55515],[11.58578,47.52281],[11.52618,47.50939],[11.4362,47.51413],[11.38128,47.47465],[11.4175,47.44621],[11.33804,47.44937],[11.29597,47.42566],[11.27844,47.39956],[11.22002,47.3964],[11.25157,47.43277],[11.20482,47.43198],[11.12536,47.41222],[11.11835,47.39719],[10.97111,47.39561],[10.97111,47.41617],[10.98513,47.42882],[10.92437,47.46991],[10.93839,47.48018],[10.90918,47.48571],[10.87061,47.4786],[10.86945,47.5015],[10.91268,47.51334],[10.88814,47.53701],[10.77596,47.51729],[10.7596,47.53228],[10.6965,47.54253],[10.68832,47.55752],[10.63456,47.5591],[10.60337,47.56755],[10.56912,47.53584],[10.48849,47.54057],[10.47329,47.58552],[10.43473,47.58394],[10.44992,47.5524],[10.4324,47.50111],[10.44291,47.48453],[10.46278,47.47901],[10.47446,47.43318],[10.4359,47.41183],[10.4324,47.38494],[10.39851,47.37623],[10.33424,47.30813],[10.23257,47.27088],[10.17531,47.27167],[10.17648,47.29149],[10.2147,47.31014],[10.19998,47.32832],[10.23757,47.37609],[10.22774,47.38904],[10.2127,47.38019],[10.17648,47.38889],[10.16362,47.36674],[10.11805,47.37228],[10.09819,47.35724],[10.06897,47.40709],[10.1052,47.4316],[10.09001,47.46005],[10.07131,47.45531],[10.03859,47.48927],[10.00003,47.48216],[9.96029,47.53899],[9.92407,47.53111],[9.87733,47.54688],[9.87499,47.52953],[9.8189,47.54688],[9.82591,47.58158],[9.80254,47.59419],[9.76748,47.5934],[9.72736,47.53457],[9.55125,47.53629],[9.56312,47.49495],[9.58208,47.48344],[9.59482,47.46305],[9.60205,47.46165],[9.60484,47.46358],[9.60841,47.47178],[9.62158,47.45858],[9.62475,47.45685],[9.6423,47.45599],[9.65728,47.45383],[9.65863,47.44847],[9.64483,47.43842],[9.6446,47.43233],[9.65043,47.41937],[9.65136,47.40504],[9.6629,47.39591],[9.67334,47.39191],[9.67445,47.38429],[9.6711,47.37824],[9.66243,47.37136],[9.65427,47.36824],[9.62476,47.36639],[9.59978,47.34671],[9.58513,47.31334],[9.55659,47.29822],[9.54773,47.2809],[9.53116,47.27029]]]}},{"type":"Feature","properties":{"id":"AZ-NX"},"geometry":{"type":"Polygon","coordinates":[[[44.75779,39.7148],[44.80977,39.65768],[44.81043,39.62677],[44.88916,39.59653],[44.96746,39.42998],[45.05932,39.36435],[45.08751,39.35052],[45.16168,39.21952],[45.30489,39.18333],[45.40148,39.09007],[45.40452,39.07224],[45.44811,39.04927],[45.44966,38.99243],[45.6131,38.964],[45.6155,38.94304],[45.65172,38.95199],[45.83883,38.90768],[45.90266,38.87739],[45.94624,38.89072],[46.00228,38.87376],[46.06766,38.87861],[46.14785,38.84206],[46.06973,39.0744],[46.02303,39.09978],[45.99774,39.28931],[45.79225,39.3695],[45.83,39.46487],[45.80804,39.56716],[45.70547,39.60174],[45.46992,39.49888],[45.29606,39.57654],[45.30385,39.61373],[45.23535,39.61373],[45.21784,39.58074],[45.17464,39.58614],[45.18554,39.67846],[45.06604,39.79277],[44.92869,39.72157],[44.88354,39.74432],[44.75779,39.7148]]]}},{"type":"Feature","properties":{"id":"BI"},"geometry":{"type":"Polygon","coordinates":[[[29.00167,-2.78523],[29.00404,-2.81978],[29.0505,-2.81774],[29.09119,-2.87871],[29.09797,-2.91935],[29.16037,-2.95457],[29.17258,-2.99385],[29.25633,-3.05471],[29.21463,-3.3514],[29.43398,-4.41764],[29.7641,-4.46348],[29.76616,-4.42514],[29.82885,-4.36153],[29.8853,-4.3607],[30.04417,-4.27451],[30.08399,-4.16289],[30.21532,-4.04338],[30.21703,-3.98995],[30.33514,-3.78067],[30.405,-3.7906],[30.39676,-3.70769],[30.47435,-3.54511],[30.68172,-3.40958],[30.63949,-3.35577],[30.84165,-3.25152],[30.83823,-2.97837],[30.6675,-2.98987],[30.57926,-2.89791],[30.4987,-2.9573],[30.40662,-2.86151],[30.52747,-2.65841],[30.41789,-2.66266],[30.54501,-2.41404],[30.42933,-2.31064],[30.14034,-2.43626],[29.95911,-2.33348],[29.8901,-2.7511],[29.77655,-2.76096],[29.72351,-2.81668],[29.65544,-2.78762],[29.57313,-2.80931],[29.54429,-2.82851],[29.45434,-2.79971],[29.36645,-2.82611],[29.32234,-2.6483],[29.2141,-2.6303],[29.15024,-2.59609],[29.13282,-2.60612],[29.08973,-2.5918],[29.05694,-2.61058],[29.0542,-2.7078],[29.04064,-2.74317],[29.00167,-2.78523]]]}},{"type":"Feature","properties":{"id":"BJ"},"geometry":{"type":"Polygon","coordinates":[[[0.77666,10.37665],[1.35507,9.99525],[1.36624,9.5951],[1.33675,9.54765],[1.38977,9.48665],[1.41746,9.3226],[1.5649,9.16941],[1.61838,9.0527],[1.64249,6.99562],[1.55877,6.99737],[1.61812,6.74843],[1.58105,6.68619],[1.61241,6.64977],[1.60675,6.61601],[1.69549,6.54438],[1.76776,6.42823],[1.79826,6.28221],[1.62992,6.24226],[1.67336,6.02702],[2.74181,6.13349],[2.70566,6.38038],[2.70641,6.45126],[2.72031,6.48827],[2.70464,6.50831],[2.74795,6.57082],[2.7307,6.63749],[2.76254,6.6843],[2.78211,6.69495],[2.78941,6.75982],[2.73405,6.78508],[2.74194,6.9271],[2.71104,6.95147],[2.75516,7.04527],[2.74057,7.106],[2.77473,7.13496],[2.74572,7.27784],[2.74435,7.42485],[2.79559,7.43004],[2.78668,7.5116],[2.73405,7.5423],[2.73095,7.7755],[2.67523,7.87825],[2.75413,8.19925],[2.76151,8.87201],[2.78829,8.97138],[2.77907,9.06924],[3.08017,9.10006],[3.14147,9.28375],[3.13928,9.47167],[3.34726,9.70696],[3.32099,9.78032],[3.35383,9.83641],[3.54429,9.87739],[3.66668,10.18417],[3.57275,10.27185],[3.66943,10.4689],[3.78292,10.40538],[3.84243,10.59316],[3.72264,11.13444],[3.6938,11.12989],[3.47682,11.4388],[3.57604,11.66871],[3.60978,11.69375],[3.55124,11.72872],[3.5636,11.77611],[3.53656,11.7825],[3.48187,11.86092],[3.39975,11.88053],[3.3098,11.88675],[3.29752,11.9342],[3.2662,11.98601],[3.27014,12.01312],[3.12543,12.15748],[3.06158,12.1949],[3.00536,12.27249],[2.96733,12.28574],[2.94656,12.31451],[2.90382,12.35383],[2.88459,12.37571],[2.84039,12.40707],[2.6593,12.30631],[2.37783,12.24804],[2.39657,12.10952],[2.45824,11.98672],[2.39723,11.89473],[2.29983,11.68254],[2.00988,11.42227],[1.42823,11.46822],[1.03409,11.04719],[0.9813,11.08876],[0.91245,10.99597],[0.8804,10.803],[0.80358,10.71459],[0.77666,10.37665]]]}},{"type":"Feature","properties":{"id":"BF"},"geometry":{"type":"Polygon","coordinates":[[[-5.51058,10.43177],[-5.39602,10.2929],[-5.12465,10.29788],[-4.96453,9.99923],[-4.96621,9.89132],[-4.6426,9.70696],[-4.31392,9.60062],[-4.24951,9.75893],[-3.69703,9.94279],[-3.31779,9.91125],[-3.27228,9.84981],[-3.19306,9.93781],[-3.16609,9.85147],[-3.00765,9.74019],[-2.93012,9.57403],[-2.76494,9.40778],[-2.68802,9.49343],[-2.76534,9.56589],[-2.78915,9.74103],[-2.74174,9.83172],[-2.80048,10.196],[-2.75825,10.24296],[-2.83241,10.33619],[-2.94232,10.64281],[-2.94055,10.71273],[-2.84357,10.89096],[-2.83373,11.0067],[-1.73,10.97725],[-1.47731,11.01702],[-1.11476,10.99747],[-0.67143,10.99811],[-0.61937,10.91305],[-0.56991,10.98787],[-0.44298,11.04292],[-0.42391,11.11661],[-0.38219,11.12596],[-0.36907,11.08753],[-0.27277,11.12509],[-0.27374,11.17157],[-0.13493,11.14075],[0.50388,11.01011],[0.49987,10.99073],[0.51893,10.97641],[0.50159,10.93293],[0.66104,10.99964],[0.91245,10.99597],[0.9813,11.08876],[1.03409,11.04719],[1.42823,11.46822],[2.00988,11.42227],[2.29983,11.68254],[2.39723,11.89473],[2.05785,12.35539],[2.26349,12.41915],[2.17769,12.68857],[2.04654,12.72842],[1.86561,12.60683],[0.99167,13.10727],[0.99253,13.37515],[1.18873,13.31771],[1.21217,13.37853],[1.24516,13.33968],[1.29329,13.35638],[1.24429,13.39373],[1.20088,13.38951],[1.02813,13.46635],[0.99514,13.5668],[0.90156,13.62346],[0.77637,13.64442],[0.77377,13.6866],[0.61924,13.68491],[0.596,13.71253],[0.6063,13.77306],[0.46949,13.94739],[0.38051,14.05575],[0.16936,14.51654],[0.23963,14.74433],[0.23859,15.00135],[0.06588,14.96961],[-0.24673,15.07805],[-0.72004,15.08655],[-1.05875,14.7921],[-1.32166,14.72774],[-1.68083,14.50023],[-1.97945,14.47709],[-1.9992,14.19011],[-2.10223,14.14878],[-2.29185,14.24741],[-2.47398,14.29965],[-2.6707,14.13957],[-2.84667,14.05532],[-2.90831,13.81174],[-2.88189,13.64921],[-3.25641,13.71337],[-3.24783,13.58792],[-3.27804,13.55522],[-3.23599,13.29035],[-3.43507,13.27272],[-3.4313,13.1588],[-3.54454,13.1781],[-3.78582,13.36139],[-3.95095,13.38394],[-3.95988,13.40765],[-3.89362,13.43837],[-3.95524,13.50114],[-3.96039,13.46892],[-4.1645,13.27837],[-4.25617,13.23794],[-4.23969,13.18613],[-4.34477,13.12927],[-4.21819,12.95722],[-4.23385,12.72591],[-4.47356,12.71252],[-4.38577,12.54333],[-4.43985,12.4064],[-4.39676,12.30864],[-4.44637,12.33564],[-4.47109,12.28717],[-4.57703,12.19875],[-4.54387,12.14087],[-4.62747,12.12207],[-4.62987,12.06531],[-4.70692,12.06746],[-4.72893,12.01579],[-5.07897,11.97918],[-5.26389,11.84778],[-5.40258,11.8327],[-5.26389,11.75728],[-5.29251,11.61715],[-5.22867,11.60421],[-5.20665,11.43811],[-5.25509,11.36905],[-5.25949,11.24816],[-5.32553,11.21578],[-5.32905,11.12636],[-5.49284,11.07538],[-5.41579,10.84628],[-5.47083,10.75329],[-5.46643,10.56074],[-5.51058,10.43177]]]}},{"type":"Feature","properties":{"id":"BD"},"geometry":{"type":"Polygon","coordinates":[[[88.00683,24.66477],[88.08786,24.63232],[88.12296,24.51301],[88.50934,24.32474],[88.69434,24.31737],[88.74841,24.1959],[88.70429,24.16241],[88.73828,23.9191],[88.7,23.90482],[88.67048,23.86857],[88.58413,23.87076],[88.56525,23.64075],[88.7412,23.48576],[88.80043,23.5089],[88.78892,23.4445],[88.7642,23.44781],[88.71683,23.2538],[88.80437,23.21579],[88.81141,23.25506],[88.84729,23.23298],[88.90737,23.23487],[88.94205,23.20821],[89.0035,23.21815],[88.93672,23.1735],[88.93003,23.14446],[88.86875,23.08636],[88.8748,23.04462],[88.88411,23.04044],[88.87368,23.0186],[88.87776,23.00422],[88.84334,23.00849],[88.86248,23.00153],[88.87115,22.97854],[88.8557,22.96708],[88.85836,22.94574],[88.87063,22.95235],[88.96779,22.84738],[88.96007,22.80814],[88.91492,22.75861],[88.92299,22.72251],[88.96041,22.70113],[88.9472,22.66486],[88.92814,22.65045],[88.93449,22.61892],[88.96436,22.61939],[88.93947,22.5934],[88.93775,22.55837],[88.96059,22.55235],[88.97981,22.48385],[88.99406,22.47243],[88.98977,22.44514],[89.00058,22.42991],[88.96985,22.41198],[88.99011,22.39277],[88.98556,22.33086],[89.02908,22.29918],[88.99423,22.28846],[89.01775,22.27114],[89.01638,22.25462],[89.03234,22.25812],[89.04058,22.22713],[89.07268,22.19455],[89.03553,21.77397],[89.13927,21.60785],[89.13606,21.42955],[92.39837,20.38919],[92.37665,20.72172],[92.27073,20.96176],[92.2582,21.07713],[92.19752,21.13722],[92.20001,21.16052],[92.17529,21.16312],[92.17288,21.17918],[92.19477,21.20165],[92.19771,21.20075],[92.21966,21.23346],[92.19932,21.32951],[92.25777,21.36269],[92.27056,21.43101],[92.38317,21.48709],[92.43158,21.37025],[92.55105,21.3856],[92.60187,21.24615],[92.68152,21.28454],[92.59775,21.6092],[92.62187,21.87037],[92.60949,21.97638],[92.56616,22.13554],[92.60029,22.1522],[92.5181,22.71441],[92.37665,22.9435],[92.34455,23.2344],[92.40119,23.2385],[92.26352,23.72344],[92.15417,23.73409],[92.04706,23.64229],[91.95093,23.73284],[91.95642,23.47361],[91.84209,23.40693],[91.75832,23.28913],[91.79154,23.215],[91.81677,23.08052],[91.74347,23.00651],[91.61571,22.93929],[91.54632,23.0133],[91.46693,23.22825],[91.40659,23.28589],[91.38015,23.18171],[91.41423,23.05501],[91.37028,23.07128],[91.32848,23.15945],[91.32445,23.24615],[91.2969,23.32602],[91.32634,23.36424],[91.29428,23.35226],[91.29741,23.37881],[91.28106,23.37798],[91.27089,23.43127],[91.24746,23.45297],[91.25016,23.48269],[91.22016,23.50103],[91.22437,23.51496],[91.16163,23.59734],[91.16008,23.66276],[91.20583,23.64924],[91.20746,23.68823],[91.14618,23.69294],[91.16249,23.74701],[91.226,23.73962],[91.22763,23.79453],[91.25432,23.84007],[91.22574,23.89486],[91.24892,23.90828],[91.23853,23.92632],[91.27793,23.92177],[91.26634,23.94766],[91.28265,23.97707],[91.29406,23.96727],[91.30462,23.9944],[91.37689,23.97527],[91.39663,24.04834],[91.3732,24.11448],[91.57911,24.07577],[91.64125,24.10993],[91.65292,24.22095],[91.73807,24.14158],[91.75283,24.23835],[91.84553,24.20798],[91.89943,24.12654],[91.96603,24.3799],[92.12173,24.37461],[92.16258,24.53306],[92.21991,24.50307],[92.29511,24.75478],[92.24052,24.85933],[92.24018,24.90792],[92.27588,24.90558],[92.29871,24.91555],[92.34043,24.87833],[92.34592,24.89453],[92.36377,24.87195],[92.37356,24.8753],[92.38652,24.85147],[92.43827,24.85622],[92.44188,24.87693],[92.48308,24.8633],[92.49921,24.90558],[92.48599,24.92567],[92.48805,24.94909],[92.44823,24.93991],[92.47029,24.96186],[92.45887,24.97049],[92.4193,24.96543],[92.42197,24.99189],[92.42832,25.0293],[92.34807,25.05224],[92.34652,25.0737],[92.22773,25.09803],[92.20653,25.13533],[92.1431,25.14396],[92.13027,25.16311],[92.10388,25.1776],[92.03538,25.18863],[92.03186,25.18342],[92.0044,25.18358],[91.97916,25.16866],[91.95565,25.18117],[91.7875,25.16532],[91.75334,25.17496],[91.74322,25.14722],[91.71275,25.16431],[91.69438,25.13432],[91.62692,25.12306],[91.62013,25.14489],[91.5979,25.1459],[91.61361,25.17643],[91.57748,25.17224],[91.55422,25.15126],[91.47156,25.13557],[91.46985,25.15251],[91.29261,25.18428],[91.27106,25.20789],[91.24746,25.19764],[91.1939,25.20214],[91.07082,25.1936],[90.91598,25.16144],[90.82071,25.16299],[90.78088,25.18102],[90.7378,25.15818],[90.68819,25.16424],[90.65926,25.18979],[90.63463,25.17418],[90.50794,25.172],[90.44151,25.14233],[90.31568,25.19049],[90.11724,25.22419],[89.90567,25.30864],[89.87837,25.2835],[89.83288,25.29553],[89.84086,25.31854],[89.81185,25.37078],[89.83958,25.44908],[89.85297,25.47078],[89.85074,25.49248],[89.86138,25.51541],[89.85219,25.53942],[89.8655,25.54453],[89.88292,25.61807],[89.87151,25.66086],[89.86515,25.66357],[89.84704,25.69529],[89.86232,25.73465],[89.81683,25.81441],[89.83503,25.87111],[89.88996,25.9443],[89.82241,25.94507],[89.8752,25.97895],[89.83503,26.01197],[89.82722,25.97532],[89.811,26.04336],[89.77272,26.03704],[89.79555,26.06788],[89.79537,26.08916],[89.78198,26.08461],[89.78456,26.10519],[89.75555,26.10858],[89.76465,26.1334],[89.74499,26.15959],[89.72117,26.15674],[89.70201,26.15138],[89.68826,26.16067],[89.68294,26.22675],[89.63058,26.2286],[89.65719,26.17161],[89.6135,26.17716],[89.60517,26.1468],[89.65187,26.06156],[89.61393,26.05169],[89.57951,26.02493],[89.58346,25.96761],[89.53908,25.97],[89.54612,26.0058],[89.49205,26.0058],[89.46544,25.99832],[89.43008,26.0122],[89.4275,26.04614],[89.39025,26.01158],[89.35953,26.0077],[89.26975,26.05986],[89.22992,26.1203],[89.15869,26.13708],[89.13757,26.18055],[89.141,26.22306],[89.09791,26.31265],[89.12195,26.28802],[89.13722,26.32049],[89.08744,26.32465],[89.09105,26.39279],[89.00659,26.41401],[88.96162,26.45781],[88.92728,26.40878],[88.91132,26.37018],[88.99784,26.33496],[88.98599,26.3028],[89.05998,26.29403],[89.05328,26.2469],[88.97071,26.23922],[88.889,26.29772],[88.87493,26.2363],[88.83579,26.22983],[88.78961,26.31093],[88.6679,26.26078],[88.67837,26.32265],[88.70275,26.31218],[88.75502,26.32549],[88.73459,26.3298],[88.74481,26.33673],[88.74197,26.35011],[88.70352,26.3358],[88.6855,26.38018],[88.68876,26.39571],[88.67374,26.39686],[88.68833,26.40593],[88.63005,26.43499],[88.62353,26.47088],[88.59538,26.47064],[88.59323,26.45059],[88.56147,26.45743],[88.55598,26.48524],[88.5274,26.48186],[88.51959,26.51136],[88.49135,26.51266],[88.47504,26.54492],[88.44826,26.53593],[88.42277,26.56542],[88.41642,26.63549],[88.40955,26.63802],[88.36887,26.57486],[88.3705,26.55951],[88.33093,26.48929],[88.35393,26.4469],[88.36938,26.48683],[88.48414,26.4602],[88.5256,26.35072],[88.49727,26.35965],[88.48131,26.35042],[88.4559,26.37403],[88.43487,26.33542],[88.41144,26.33642],[88.41479,26.3158],[88.37634,26.30803],[88.35102,26.2841],[88.3493,26.25223],[88.35865,26.24292],[88.35067,26.22244],[88.1797,26.14927],[88.17661,26.03426],[88.08804,25.91334],[88.11841,25.8002],[88.14811,25.77639],[88.18691,25.80128],[88.20768,25.78799],[88.26004,25.81627],[88.26948,25.78165],[88.35514,25.72738],[88.41161,25.67154],[88.45436,25.66349],[88.44783,25.5971],[88.4983,25.58363],[88.49435,25.55916],[88.53315,25.53903],[88.54019,25.50913],[88.59701,25.50836],[88.60662,25.5161],[88.62396,25.49829],[88.6461,25.49798],[88.64662,25.48535],[88.66756,25.47163],[88.70944,25.47985],[88.7182,25.50464],[88.76661,25.4955],[88.75974,25.52648],[88.77948,25.51626],[88.80918,25.52338],[88.81381,25.48992],[88.82823,25.48945],[88.83888,25.47024],[88.81896,25.40932],[88.8375,25.40141],[88.84034,25.36535],[88.86334,25.35488],[88.87905,25.3306],[88.90565,25.33813],[88.92814,25.30352],[88.95329,25.30244],[88.97981,25.30577],[88.99449,25.29607],[89.0108,25.30213],[89.00762,25.26518],[88.95544,25.25354],[88.9611,25.20835],[88.93758,25.15864],[88.87853,25.179],[88.84265,25.21239],[88.80317,25.17076],[88.73605,25.18474],[88.72163,25.20664],[88.61537,25.20121],[88.56422,25.17061],[88.55632,25.19251],[88.48491,25.21177],[88.44423,25.19173],[88.46054,25.14652],[88.46277,25.07468],[88.33917,24.86803],[88.27325,24.88796],[88.22278,24.96271],[88.14004,24.93529],[88.16167,24.85996],[88.1052,24.80387],[88.10314,24.78127],[88.00683,24.66477]]]}},{"type":"Feature","properties":{"id":"BG"},"geometry":{"type":"Polygon","coordinates":[[[22.34773,42.31725],[22.38421,42.30296],[22.48678,42.19965],[22.50289,42.19527],[22.51224,42.15457],[22.67701,42.06614],[22.86749,42.02275],[22.89979,41.89103],[22.96682,41.77137],[23.01239,41.76527],[23.03342,41.71034],[22.95275,41.62436],[22.96451,41.35626],[22.93334,41.34104],[23.19969,41.31739],[23.22153,41.36874],[23.31727,41.40687],[23.34088,41.36366],[23.40416,41.39999],[23.52453,41.40262],[23.63203,41.37632],[23.67644,41.41139],[23.77106,41.40044],[23.80148,41.43943],[23.90298,41.45437],[23.91551,41.48279],[23.96907,41.43918],[24.07396,41.46665],[24.06323,41.53222],[24.10063,41.54796],[24.18126,41.51735],[24.27124,41.57682],[24.30513,41.51297],[24.53676,41.56049],[24.61129,41.42278],[24.71529,41.41928],[24.8041,41.34913],[24.82514,41.4035],[24.86136,41.39298],[24.90928,41.40876],[24.942,41.38685],[25.11611,41.34212],[25.28322,41.23411],[25.49377,41.28657],[25.53256,41.28103],[25.54824,41.31331],[25.67178,41.30954],[25.70507,41.29209],[25.8266,41.34563],[25.88636,41.30431],[26.15072,41.3616],[26.16548,41.42278],[26.20288,41.43943],[26.15055,41.4772],[26.18711,41.51731],[26.17951,41.55409],[26.1478,41.55432],[26.15146,41.60828],[26.07083,41.64584],[26.06148,41.70345],[26.16841,41.74858],[26.21325,41.73223],[26.22888,41.74139],[26.2654,41.71544],[26.30255,41.70925],[26.35957,41.71149],[26.32952,41.73637],[26.33589,41.76802],[26.36952,41.82265],[26.5385,41.82403],[26.57961,41.90024],[26.56202,41.92731],[26.62996,41.97644],[26.79143,41.97386],[26.9692,42.00542],[27.03277,42.0809],[27.08486,42.08735],[27.19251,42.06028],[27.22376,42.10152],[27.27411,42.10409],[27.45478,41.96591],[27.52379,41.93756],[27.55191,41.90928],[27.69949,41.97515],[27.81235,41.94803],[27.83492,41.99709],[27.91479,41.97902],[28.32297,41.98371],[29.24336,43.70874],[28.23293,43.76],[27.99558,43.84193],[27.92008,44.00761],[27.73468,43.95326],[27.64542,44.04958],[27.60834,44.01206],[27.41981,44.01627],[27.30514,44.08832],[27.27802,44.12912],[27.13502,44.1407],[26.95141,44.13555],[26.62712,44.05698],[26.361,44.03824],[26.10115,43.96908],[26.05584,43.90925],[25.94911,43.85745],[25.77289,43.70362],[25.38665,43.61917],[25.17144,43.70261],[25.10718,43.6831],[25.01346,43.71255],[24.81399,43.71082],[24.73542,43.68523],[24.62281,43.74082],[24.50264,43.76314],[24.35364,43.70211],[24.18149,43.68218],[23.73978,43.80627],[23.61687,43.79289],[23.4507,43.84936],[23.26772,43.84843],[23.05288,43.79494],[22.86357,43.83873],[22.83753,43.88055],[22.88383,43.98565],[23.01674,44.01946],[23.05412,44.07278],[22.67698,44.21863],[22.61711,44.16938],[22.62016,44.09177],[22.53313,44.02417],[22.41828,44.00664],[22.36095,43.80331],[22.41043,43.69566],[22.47582,43.6558],[22.53397,43.47225],[22.82036,43.33665],[22.89727,43.22417],[23.00806,43.19279],[22.98104,43.11199],[22.84804,43.00458],[22.78916,42.98317],[22.76538,42.90375],[22.67312,42.87608],[22.61981,42.89445],[22.5424,42.87772],[22.43111,42.81912],[22.49515,42.74629],[22.44223,42.57773],[22.55669,42.50144],[22.51961,42.3991],[22.47498,42.3915],[22.45919,42.33822],[22.34773,42.31725]]]}},{"type":"Feature","properties":{"id":"BH"},"geometry":{"type":"Polygon","coordinates":[[[50.26923,26.08243],[50.302,25.87592],[50.57069,25.57887],[50.80824,25.54641],[50.7801,25.595],[50.86149,25.6965],[50.81266,25.88946],[50.93865,26.30758],[50.71771,26.73086],[50.38162,26.53976],[50.26923,26.08243]]]}},{"type":"Feature","properties":{"id":"BA"},"geometry":{"type":"Polygon","coordinates":[[[15.72584,44.82334],[15.8255,44.71501],[15.89348,44.74964],[15.95824,44.69361],[16.06407,44.61142],[16.01729,44.58025],[16.03012,44.55572],[16.10566,44.52586],[16.17144,44.40594],[16.12969,44.38275],[16.22028,44.34883],[16.18766,44.30855],[16.19088,44.27141],[16.22079,44.23572],[16.21727,44.2177],[16.26337,44.17764],[16.37666,44.08129],[16.43662,44.07523],[16.43629,44.02826],[16.50528,44.0244],[16.55472,43.95326],[16.70922,43.84887],[16.74882,43.77394],[16.80736,43.76011],[16.99748,43.58559],[17.02983,43.56391],[17.08313,43.54263],[17.16218,43.49272],[17.22321,43.49956],[17.28475,43.47154],[17.28612,43.43266],[17.25579,43.40353],[17.286,43.33065],[17.3529,43.2527],[17.42826,43.21868],[17.43118,43.18402],[17.64268,43.08595],[17.70879,42.97223],[17.5392,42.92787],[17.6444,42.88641],[17.6873,42.92839],[17.78961,42.89344],[17.82394,42.91796],[17.89775,42.81781],[18.13611,42.68142],[18.15713,42.65359],[18.17816,42.66028],[18.25052,42.60541],[18.3615,42.61867],[18.43735,42.55921],[18.49778,42.58409],[18.53751,42.57376],[18.55504,42.58409],[18.52232,42.62279],[18.57373,42.64429],[18.54841,42.68328],[18.54603,42.69171],[18.55221,42.69045],[18.56789,42.72074],[18.47324,42.74992],[18.45921,42.81682],[18.47633,42.85829],[18.4935,42.86433],[18.49661,42.89306],[18.49076,42.95553],[18.52232,43.01451],[18.66254,43.03928],[18.64735,43.14766],[18.66808,43.20473],[18.71747,43.2286],[18.69409,43.25014],[18.76538,43.29838],[18.85342,43.32426],[18.84794,43.33735],[18.83912,43.34795],[18.90911,43.36383],[18.95999,43.3306],[18.95001,43.29327],[19.01046,43.24911],[19.03724,43.29042],[19.08093,43.29969],[19.08393,43.31949],[19.04071,43.397],[19.01078,43.43854],[18.96053,43.45042],[18.95469,43.49367],[18.91021,43.50087],[19.01078,43.55806],[19.04934,43.50384],[19.13933,43.5282],[19.15685,43.53943],[19.22807,43.5264],[19.24774,43.53061],[19.25611,43.59997],[19.33426,43.58833],[19.36778,43.6114],[19.38589,43.59232],[19.40082,43.58741],[19.41301,43.53946],[19.42829,43.55591],[19.41618,43.57777],[19.51755,43.57958],[19.49172,43.60034],[19.49386,43.637],[19.5094,43.62744],[19.53317,43.70399],[19.4815,43.73284],[19.46373,43.76254],[19.3986,43.79668],[19.33885,43.8625],[19.23465,43.98764],[19.24363,44.01502],[19.38614,43.961],[19.40314,43.96607],[19.42872,43.95816],[19.4488,43.96014],[19.47669,43.95606],[19.52656,43.956],[19.54193,43.97595],[19.5681,43.98558],[19.56364,43.99898],[19.58956,44.00334],[19.59832,44.01007],[19.61806,44.01374],[19.62342,44.01883],[19.61394,44.03522],[19.62467,44.05268],[19.57467,44.04716],[19.55368,44.07186],[19.50965,44.08129],[19.49292,44.11384],[19.46966,44.12129],[19.48386,44.14332],[19.47338,44.15034],[19.43905,44.13088],[19.40927,44.16722],[19.36323,44.18165],[19.35344,44.18955],[19.35889,44.20903],[19.34443,44.21816],[19.34825,44.23124],[19.32791,44.26745],[19.28649,44.27565],[19.25311,44.26397],[19.23229,44.26241],[19.208,44.29243],[19.18328,44.28383],[19.16741,44.28648],[19.1323,44.31604],[19.13423,44.34017],[19.11547,44.34218],[19.1083,44.3558],[19.11925,44.36684],[19.10372,44.36877],[19.10365,44.37795],[19.10704,44.38249],[19.10749,44.39421],[19.11616,44.40141],[19.14693,44.41364],[19.15045,44.45148],[19.12174,44.50091],[19.13277,44.52674],[19.16483,44.52209],[19.19517,44.55053],[19.18307,44.57426],[19.21959,44.59175],[19.24229,44.62224],[19.25886,44.6608],[19.29928,44.68903],[19.33464,44.73758],[19.33224,44.76727],[19.3458,44.78804],[19.37181,44.87922],[19.29903,44.9095],[19.19191,44.92202],[19.01994,44.85493],[18.86678,44.85233],[18.76347,44.90669],[18.76369,44.93707],[18.80661,44.93561],[18.78515,44.96742],[18.78687,44.98142],[18.66482,45.06667],[18.58886,45.08824],[18.53659,45.0583],[18.47926,45.05951],[18.46943,45.06787],[18.41896,45.11083],[18.32176,45.10151],[18.27541,45.13458],[18.20846,45.12804],[18.21344,45.08927],[18.17756,45.07739],[18.10718,45.0877],[18.06366,45.14596],[18.03121,45.12632],[18.01774,45.15111],[17.99479,45.14958],[17.97834,45.13831],[17.97336,45.12245],[17.93706,45.08016],[17.87148,45.04645],[17.84754,45.04478],[17.66571,45.13408],[17.60112,45.10836],[17.51469,45.10791],[17.48748,45.13264],[17.45942,45.12574],[17.4498,45.16119],[17.41727,45.13398],[17.34337,45.14148],[17.32092,45.16246],[17.26982,45.18832],[17.25131,45.14957],[17.2442,45.14581],[17.18004,45.14657],[17.0415,45.20759],[16.9767,45.24292],[16.93954,45.2289],[16.94203,45.26872],[16.92272,45.27694],[16.91001,45.25579],[16.83534,45.21614],[16.83804,45.18951],[16.81137,45.18434],[16.77723,45.19262],[16.7344,45.20719],[16.68754,45.20048],[16.64962,45.20714],[16.59952,45.23156],[16.58484,45.22506],[16.5501,45.2212],[16.53618,45.22702],[16.52403,45.22545],[16.49185,45.20877],[16.47974,45.18524],[16.4703,45.14621],[16.41091,45.12035],[16.38456,45.06424],[16.38219,45.05139],[16.3749,45.05206],[16.35863,45.03529],[16.35572,45.0031],[16.28937,44.99661],[16.12153,45.09616],[16.00965,45.21838],[15.92382,45.22739],[15.89103,45.21626],[15.8337,45.22201],[15.82709,45.20786],[15.81022,45.20979],[15.80108,45.20036],[15.77816,45.18515],[15.77778,45.17653],[15.76371,45.16508],[15.79061,45.11635],[15.74585,45.0638],[15.78374,44.9722],[15.74723,44.96818],[15.76096,44.87045],[15.79472,44.8455],[15.72584,44.82334]]]}},{"type":"Feature","properties":{"id":"BY"},"geometry":{"type":"Polygon","coordinates":[[[23.18196,52.28812],[23.20071,52.22848],[23.47859,52.18215],[23.54314,52.12148],[23.61,52.11264],[23.64066,52.07626],[23.68733,51.9906],[23.61523,51.92066],[23.62691,51.78208],[23.53198,51.74298],[23.57053,51.55938],[23.56236,51.53673],[23.62751,51.50512],[23.67776,51.50233],[23.60906,51.62122],[23.75381,51.65754],[23.91118,51.63316],[23.8741,51.59734],[23.99907,51.58369],[24.13075,51.66979],[24.3163,51.75063],[24.29021,51.80841],[24.37123,51.88222],[24.98784,51.91273],[25.20228,51.97143],[25.43077,51.92193],[25.73673,51.91973],[25.80574,51.94556],[25.83217,51.92587],[26.00408,51.92967],[26.19084,51.86781],[26.39367,51.87315],[26.46962,51.80501],[26.69759,51.82284],[26.80043,51.75777],[26.9489,51.73788],[26.99422,51.76933],[27.20602,51.77291],[27.20948,51.66713],[27.26613,51.65957],[27.25136,51.60651],[27.71932,51.60672],[27.67125,51.50854],[27.76052,51.47604],[27.85253,51.62293],[27.91844,51.61952],[27.95827,51.56065],[28.10658,51.57857],[28.23452,51.66988],[28.37592,51.54505],[28.47051,51.59734],[28.64429,51.5664],[28.67959,51.45219],[28.75958,51.42362],[28.77451,51.48759],[28.81795,51.55552],[28.95528,51.59222],[28.99098,51.56833],[29.1187,51.65872],[29.16402,51.64679],[29.20659,51.56918],[29.25603,51.57089],[29.25191,51.49828],[29.32881,51.37843],[29.42357,51.4187],[29.49773,51.39814],[29.54372,51.48372],[29.7408,51.53417],[29.77376,51.4461],[30.17888,51.51025],[30.34642,51.42555],[30.36153,51.33984],[30.56203,51.25655],[30.64992,51.35014],[30.51946,51.59649],[30.68804,51.82806],[30.76443,51.89739],[30.90897,52.00699],[30.95589,52.07775],[31.13332,52.1004],[31.26022,52.03982],[31.38326,52.12991],[31.7822,52.11406],[31.77877,52.18636],[31.6895,52.1973],[31.70735,52.26711],[31.57971,52.32146],[31.62084,52.33849],[31.61397,52.48843],[31.56316,52.51518],[31.63869,52.55361],[31.50406,52.69707],[31.57277,52.71613],[31.592,52.79011],[31.42982,52.89171],[31.35667,52.97854],[31.24147,53.031],[31.32283,53.04101],[31.33519,53.08805],[31.3915,53.09712],[31.36403,53.13504],[31.40523,53.21406],[31.56316,53.19432],[31.62496,53.22886],[31.787,53.18033],[31.82373,53.10042],[32.15368,53.07594],[32.40773,53.18856],[32.51725,53.28431],[32.73257,53.33494],[32.74968,53.45597],[32.47777,53.5548],[32.40499,53.6656],[32.50112,53.68594],[32.45717,53.74039],[32.36663,53.7166],[32.12621,53.81586],[31.89137,53.78099],[31.77028,53.80015],[31.85019,53.91801],[31.88744,54.03653],[31.89599,54.0837],[31.57002,54.14535],[31.30791,54.25315],[31.32442,54.34044],[31.27017,54.37755],[31.22945,54.46585],[31.08543,54.50361],[31.21399,54.63113],[31.19339,54.66947],[30.99187,54.67046],[30.98226,54.68872],[31.0262,54.70698],[30.97127,54.71967],[30.95479,54.74346],[30.75165,54.80699],[30.8264,54.90062],[30.81759,54.94064],[30.93144,54.9585],[30.95754,54.98609],[30.9081,55.02232],[30.94243,55.03964],[31.00972,55.02783],[31.02071,55.06167],[30.97369,55.17134],[30.87944,55.28223],[30.81946,55.27931],[30.8257,55.3313],[30.93144,55.3914],[30.90763,55.47642],[30.95204,55.50667],[30.93419,55.6185],[30.86003,55.63169],[30.7845,55.58514],[30.72957,55.66268],[30.67464,55.64176],[30.63344,55.73079],[30.48826,55.77502],[30.52448,55.79857],[30.30987,55.83592],[30.25428,55.87319],[30.10923,55.8306],[29.97975,55.87281],[29.80672,55.79569],[29.61446,55.77716],[29.51283,55.70294],[29.3604,55.75862],[29.44692,55.95978],[29.21717,55.98971],[29.08299,56.03427],[28.73418,55.97131],[28.63668,56.07262],[28.68337,56.10173],[28.5529,56.11705],[28.43068,56.09407],[28.37987,56.11399],[28.36888,56.05805],[28.30571,56.06035],[28.15217,56.16964],[27.97865,56.11849],[27.80055,55.98378],[27.63988,55.9265],[27.61683,55.78558],[27.55508,55.784],[27.42977,55.79443],[27.35956,55.81141],[27.27804,55.78299],[27.1559,55.85032],[26.97418,55.81411],[26.87448,55.7172],[26.77677,55.67806],[26.71802,55.70645],[26.64888,55.70515],[26.63231,55.67968],[26.63167,55.57887],[26.53627,55.5052],[26.55601,55.43826],[26.55137,55.38915],[26.44168,55.34613],[26.56631,55.32221],[26.6714,55.33902],[26.80929,55.31642],[26.83266,55.30444],[26.835,55.28182],[26.73017,55.24226],[26.72983,55.21788],[26.68075,55.19787],[26.69243,55.16718],[26.6191,55.14665],[26.59069,55.15391],[26.54211,55.14312],[26.51481,55.16051],[26.46249,55.12814],[26.35121,55.1525],[26.30628,55.12536],[26.22908,55.10656],[26.26941,55.08032],[26.24067,55.06524],[26.24642,55.04685],[26.20397,54.99729],[26.12239,54.9849],[26.0612,54.94168],[25.99129,54.95705],[25.88627,54.93044],[25.85932,54.90587],[25.81675,54.87448],[25.73598,54.79355],[25.75977,54.57252],[25.68045,54.5321],[25.64813,54.48704],[25.62203,54.4656],[25.63371,54.42075],[25.5376,54.33158],[25.55102,54.32738],[25.55256,54.31567],[25.68513,54.31727],[25.78553,54.23327],[25.75864,54.22379],[25.78563,54.15747],[25.71084,54.16704],[25.64875,54.1259],[25.54724,54.14925],[25.51094,54.17615],[25.56372,54.20828],[25.54673,54.22841],[25.59247,54.22796],[25.5039,54.31011],[25.47944,54.29914],[25.43437,54.29528],[25.36331,54.26517],[25.29275,54.26241],[25.22066,54.2595],[25.19662,54.21632],[25.16122,54.19862],[25.07105,54.13408],[24.989,54.14433],[24.96514,54.17499],[24.81519,54.14293],[24.83064,54.13222],[24.77131,54.11091],[24.85311,54.02862],[24.73949,53.96668],[24.68713,53.96446],[24.70636,54.02128],[24.61126,54.001],[24.44411,53.90076],[24.34128,53.90076],[24.19638,53.96405],[23.98837,53.92554],[23.95098,53.9613],[23.81309,53.94205],[23.81818,53.90888],[23.7854,53.90059],[23.71726,53.93379],[23.61677,53.92691],[23.51284,53.95052],[23.62004,53.60942],[23.81995,53.24131],[23.85657,53.22923],[23.91393,53.16469],[23.87548,53.0831],[23.92184,53.02079],[23.94689,52.95919],[23.91805,52.94016],[23.93763,52.71332],[23.73615,52.6149],[23.58296,52.59868],[23.45112,52.53774],[23.34141,52.44845],[23.18196,52.28812]]]}},{"type":"Feature","properties":{"id":"BZ"},"geometry":{"type":"Polygon","coordinates":[[[-89.22683,15.88619],[-89.17418,15.90898],[-89.02415,15.9063],[-88.95358,15.88698],[-88.40779,16.09624],[-87.3359,17.10872],[-87.24084,17.80373],[-87.84815,18.18511],[-87.85693,18.18266],[-87.86657,18.19971],[-87.87604,18.18313],[-87.90671,18.15213],[-88.1031,18.48551],[-88.48242,18.49164],[-88.71322,18.11387],[-88.71505,18.0707],[-88.87451,17.89044],[-89.03839,18.0067],[-89.15105,17.95104],[-89.14985,17.81563],[-89.15025,17.04813],[-89.22683,15.88619]]]}},{"type":"Feature","properties":{"id":"BO"},"geometry":{"type":"Polygon","coordinates":[[[-69.6401,-17.28606],[-69.57641,-17.29164],[-69.46863,-17.37466],[-69.46846,-17.49842],[-69.46784,-17.60463],[-69.34126,-17.72753],[-69.28671,-17.94844],[-69.07496,-18.03715],[-69.14546,-18.16387],[-69.07432,-18.28259],[-68.92822,-18.86015],[-68.94987,-18.93302],[-68.87082,-19.06003],[-68.80602,-19.08355],[-68.61989,-19.27584],[-68.41218,-19.40499],[-68.66761,-19.72118],[-68.54611,-19.84651],[-68.57132,-20.03134],[-68.74273,-20.08817],[-68.73733,-20.45725],[-68.43658,-20.63824],[-68.55383,-20.7355],[-68.53957,-20.91542],[-68.40403,-20.94562],[-68.17943,-21.30184],[-67.85114,-22.87076],[-67.54284,-22.89771],[-67.18382,-22.81525],[-66.7298,-22.23644],[-66.29714,-22.08741],[-66.27279,-21.95228],[-66.23502,-21.84572],[-66.24077,-21.77837],[-66.03836,-21.84829],[-66.04832,-21.9187],[-65.9261,-21.93335],[-65.7467,-22.10105],[-65.61299,-22.09671],[-65.5855,-22.09725],[-65.57743,-22.07675],[-65.48426,-22.09534],[-65.47435,-22.08908],[-64.99524,-22.08255],[-64.90014,-22.12136],[-64.74569,-22.18295],[-64.67174,-22.18957],[-64.58888,-22.25035],[-64.53832,-22.29131],[-64.56853,-22.35991],[-64.52459,-22.44189],[-64.42399,-22.53523],[-64.44065,-22.63864],[-64.4176,-22.67692],[-64.39009,-22.72251],[-64.36005,-22.72441],[-64.34417,-22.73985],[-64.33138,-22.87261],[-64.30186,-22.8768],[-64.23414,-22.55631],[-64.155,-22.45133],[-64.0357,-22.24096],[-63.93425,-22.00003],[-63.70963,-21.99934],[-63.69212,-22.013],[-63.68233,-22.0311],[-63.68654,-22.0397],[-63.68272,-22.05473],[-63.67911,-22.05107],[-63.66092,-21.99916],[-62.81124,-21.9987],[-62.8078,-22.12534],[-62.64455,-22.25091],[-62.2757,-21.06657],[-62.26883,-20.55311],[-61.93912,-20.10053],[-61.73723,-19.63958],[-60.00638,-19.2981],[-59.06965,-19.29148],[-58.21106,-19.79319],[-58.16225,-20.16193],[-57.8496,-19.98346],[-58.12704,-19.75888],[-57.78463,-19.03259],[-57.71113,-19.03161],[-57.69134,-19.00544],[-57.71995,-18.97546],[-57.71924,-18.89784],[-57.76594,-18.89816],[-57.55668,-18.24043],[-57.45231,-18.23065],[-57.71942,-17.8291],[-57.73949,-17.56095],[-57.88318,-17.44826],[-57.99476,-17.51572],[-58.32935,-17.28195],[-58.5058,-16.80958],[-58.30918,-16.3699],[-58.32431,-16.25861],[-58.41506,-16.32636],[-60.16069,-16.26479],[-60.23797,-15.50267],[-60.58224,-15.09887],[-60.23968,-15.09515],[-60.27887,-14.63021],[-60.46037,-14.22496],[-60.48053,-13.77981],[-61.05527,-13.50054],[-61.81151,-13.49564],[-63.76259,-12.42952],[-63.90248,-12.52544],[-64.22539,-12.45267],[-64.30708,-12.46398],[-64.99778,-11.98604],[-65.30027,-11.48749],[-65.28141,-10.86289],[-65.35402,-10.78685],[-65.37923,-10.35141],[-65.29019,-9.86253],[-65.40538,-9.65084],[-65.56244,-9.84266],[-65.68343,-9.75323],[-66.57234,-9.90053],[-67.17784,-10.34016],[-67.65998,-10.55295],[-67.71217,-10.71053],[-68.05446,-10.67106],[-68.29444,-11.02275],[-68.71533,-11.14749],[-68.7651,-11.0496],[-68.75179,-11.03688],[-68.75265,-11.02383],[-68.74729,-11.00931],[-68.75463,-11.00788],[-68.76033,-11.01816],[-68.76488,-11.01656],[-68.75789,-10.99988],[-68.91792,-11.00944],[-69.42792,-10.93451],[-69.47839,-10.95254],[-69.57156,-10.94555],[-68.98115,-11.8979],[-68.65044,-12.50689],[-68.85615,-12.87769],[-68.8864,-13.40792],[-69.05799,-13.67067],[-68.8932,-14.2138],[-69.35291,-14.79845],[-69.36254,-14.94634],[-69.14856,-15.23478],[-69.40336,-15.61358],[-69.20291,-16.16668],[-69.10014,-16.22596],[-68.96135,-16.19452],[-68.89354,-16.25983],[-68.79464,-16.33272],[-68.98358,-16.42165],[-69.04027,-16.57214],[-69.00853,-16.66769],[-69.16734,-16.7286],[-69.34021,-16.98193],[-69.6001,-17.21524],[-69.6401,-17.28606]]]}},{"type":"Feature","properties":{"id":"BR"},"geometry":{"type":"Polygon","coordinates":[[[-73.96938,-7.58465],[-73.65485,-7.77897],[-73.76576,-7.89884],[-72.92886,-9.04074],[-73.21498,-9.40904],[-72.72216,-9.41397],[-72.31883,-9.5184],[-72.14721,-9.99928],[-71.23394,-9.9668],[-70.53373,-9.42628],[-70.58453,-9.58303],[-70.55429,-9.76692],[-70.62487,-9.80666],[-70.61593,-10.99833],[-70.51395,-10.92249],[-70.38791,-11.07096],[-69.90896,-10.92744],[-69.57835,-10.94051],[-69.57156,-10.94555],[-69.47839,-10.95254],[-69.42792,-10.93451],[-68.91792,-11.00944],[-68.75789,-10.99988],[-68.76488,-11.01656],[-68.76033,-11.01816],[-68.75463,-11.00788],[-68.74729,-11.00931],[-68.75265,-11.02383],[-68.75179,-11.03688],[-68.7651,-11.0496],[-68.71533,-11.14749],[-68.29444,-11.02275],[-68.05446,-10.67106],[-67.71217,-10.71053],[-67.65998,-10.55295],[-67.17784,-10.34016],[-66.57234,-9.90053],[-65.68343,-9.75323],[-65.56244,-9.84266],[-65.40538,-9.65084],[-65.29019,-9.86253],[-65.37923,-10.35141],[-65.35402,-10.78685],[-65.28141,-10.86289],[-65.30027,-11.48749],[-64.99778,-11.98604],[-64.30708,-12.46398],[-64.22539,-12.45267],[-63.90248,-12.52544],[-63.76259,-12.42952],[-61.81151,-13.49564],[-61.05527,-13.50054],[-60.48053,-13.77981],[-60.46037,-14.22496],[-60.27887,-14.63021],[-60.23968,-15.09515],[-60.58224,-15.09887],[-60.23797,-15.50267],[-60.16069,-16.26479],[-58.41506,-16.32636],[-58.32431,-16.25861],[-58.30918,-16.3699],[-58.5058,-16.80958],[-58.32935,-17.28195],[-57.99476,-17.51572],[-57.88318,-17.44826],[-57.73949,-17.56095],[-57.71942,-17.8291],[-57.45231,-18.23065],[-57.55668,-18.24043],[-57.76594,-18.89816],[-57.71924,-18.89784],[-57.71995,-18.97546],[-57.69134,-19.00544],[-57.71113,-19.03161],[-57.78463,-19.03259],[-58.12704,-19.75888],[-57.8496,-19.98346],[-58.16225,-20.16193],[-58.04454,-20.39853],[-57.84536,-20.93155],[-57.93492,-21.65505],[-57.88239,-21.6868],[-57.94584,-21.74243],[-57.98625,-22.09157],[-56.80807,-22.27813],[-56.63761,-22.26463],[-56.5212,-22.11556],[-56.45893,-22.08072],[-56.22665,-22.26495],[-55.84899,-22.28433],[-55.79011,-22.38484],[-55.74797,-22.38373],[-55.73493,-22.45918],[-55.74767,-22.46469],[-55.7466,-22.46909],[-55.75273,-22.47477],[-55.75149,-22.48198],[-55.74677,-22.48575],[-55.74728,-22.50538],[-55.74175,-22.51396],[-55.73771,-22.52657],[-55.72407,-22.5508],[-55.71377,-22.55734],[-55.6986,-22.56268],[-55.69471,-22.57094],[-55.69437,-22.57771],[-55.6315,-22.62026],[-55.62532,-22.62779],[-55.61433,-22.70778],[-55.66909,-22.86921],[-55.63849,-22.95122],[-55.61056,-23.04909],[-55.51769,-23.20632],[-55.54575,-23.22044],[-55.52992,-23.25246],[-55.52288,-23.2595],[-55.53236,-23.2691],[-55.54726,-23.27521],[-55.55438,-23.2827],[-55.45143,-23.71401],[-55.43585,-23.87157],[-55.44061,-23.91699],[-55.40491,-23.97778],[-55.12292,-23.99669],[-55.0521,-23.98578],[-55.0512,-23.98064],[-55.04609,-23.98401],[-55.02725,-23.97288],[-54.6238,-23.83078],[-54.32807,-24.01865],[-54.28482,-24.07123],[-54.33734,-24.13704],[-54.25941,-24.35366],[-54.32876,-24.48464],[-54.4423,-25.13381],[-54.62368,-25.46388],[-54.60539,-25.48062],[-54.59509,-25.53696],[-54.59398,-25.59224],[-54.5502,-25.58915],[-54.52926,-25.62846],[-53.90831,-25.55513],[-53.83691,-25.94849],[-53.73511,-26.04211],[-53.73086,-26.05842],[-53.7264,-26.0664],[-53.73391,-26.07006],[-53.73968,-26.10012],[-53.65018,-26.19501],[-53.65237,-26.23289],[-53.63739,-26.2496],[-53.63881,-26.25075],[-53.64632,-26.24798],[-53.64186,-26.25976],[-53.64505,-26.28089],[-53.68269,-26.33359],[-53.73372,-26.6131],[-53.80144,-27.09844],[-54.15978,-27.2889],[-54.19062,-27.27639],[-54.19268,-27.30751],[-54.41888,-27.40882],[-54.50416,-27.48232],[-54.67657,-27.57214],[-54.90159,-27.63132],[-54.90805,-27.73149],[-55.1349,-27.89759],[-55.16872,-27.86224],[-55.33303,-27.94661],[-55.6262,-28.17124],[-55.65418,-28.18304],[-56.01729,-28.51223],[-56.00458,-28.60421],[-56.05265,-28.62651],[-56.54171,-29.11447],[-56.57295,-29.11357],[-56.62789,-29.18073],[-56.81251,-29.48154],[-57.09386,-29.74211],[-57.65384,-30.18549],[-57.5584,-30.21398],[-57.21405,-30.28723],[-57.05715,-30.08246],[-56.82128,-30.09434],[-56.48796,-30.39908],[-56.47543,-30.38846],[-56.4619,-30.38457],[-56.17309,-30.632],[-56.00692,-30.83621],[-56.00658,-31.07704],[-55.8356,-31.04381],[-55.58866,-30.84117],[-55.5634,-30.8686],[-55.55373,-30.8732],[-55.55218,-30.88193],[-55.54572,-30.89051],[-55.53431,-30.89714],[-55.53276,-30.90218],[-55.52712,-30.89997],[-55.51862,-30.89828],[-55.50841,-30.9027],[-55.50821,-30.91349],[-54.15744,-31.87391],[-53.76024,-32.0751],[-53.58856,-32.44633],[-53.46496,-32.49528],[-53.39183,-32.58862],[-53.37535,-32.56931],[-53.09417,-32.69024],[-53.53459,-33.16843],[-53.52794,-33.68908],[-53.44031,-33.69344],[-53.39593,-33.75169],[-53.37138,-33.74313],[-53.18243,-33.86894],[-28.58184,-20.70346],[-29.12784,1.16305],[-51.55535,4.70281],[-51.61983,4.14596],[-51.65531,4.05811],[-51.76551,3.98036],[-51.79599,3.89336],[-51.82312,3.85825],[-51.85573,3.83427],[-52.31787,3.17896],[-52.6906,2.37298],[-52.96539,2.1881],[-53.78743,2.34412],[-54.16286,2.10779],[-54.6084,2.32856],[-55.01919,2.564],[-55.71493,2.40342],[-55.96292,2.53188],[-56.13054,2.27723],[-55.92159,2.05236],[-55.89863,1.89861],[-55.99278,1.83137],[-56.47045,1.95135],[-56.7659,1.89509],[-57.07092,1.95304],[-57.09109,2.01854],[-57.23981,1.95808],[-57.35073,1.98327],[-57.55743,1.69605],[-57.77281,1.73344],[-57.97336,1.64566],[-58.01873,1.51966],[-58.33887,1.58014],[-58.4858,1.48399],[-58.53571,1.29154],[-58.84229,1.17749],[-58.92072,1.31293],[-59.25583,1.40559],[-59.74845,1.88038],[-59.7264,2.27497],[-59.91177,2.36759],[-59.98466,2.91115],[-59.8275,3.33272],[-59.8336,3.35526],[-59.80622,3.35423],[-59.80373,3.36888],[-59.81111,3.37916],[-59.85145,3.55762],[-59.51963,3.91951],[-59.73353,4.20399],[-59.69361,4.34069],[-59.78878,4.45637],[-60.15953,4.53456],[-60.04189,4.69801],[-59.97642,5.07367],[-60.20944,5.28754],[-60.32352,5.21299],[-60.73204,5.20931],[-60.5802,4.94312],[-60.86539,4.70512],[-60.96399,4.5457],[-61.11548,4.5155],[-61.14758,4.48093],[-61.15299,4.49599],[-61.18835,4.52063],[-61.31457,4.54167],[-61.29675,4.44216],[-61.48569,4.43149],[-61.54629,4.2822],[-62.13094,4.08309],[-62.44822,4.18621],[-62.57656,4.04754],[-62.74411,4.03331],[-62.7655,3.73099],[-62.98296,3.59935],[-63.21111,3.96219],[-63.4464,3.9693],[-63.42233,3.89995],[-63.50611,3.83592],[-63.67099,4.01731],[-63.70218,3.91417],[-63.86082,3.94796],[-63.99183,3.90172],[-64.14512,4.12932],[-64.61814,4.12591],[-64.74414,4.28068],[-64.84028,4.24665],[-64.70123,3.99852],[-64.16839,3.52575],[-64.02908,2.79797],[-64.0257,2.48156],[-63.39114,2.4317],[-63.39827,2.16098],[-64.06135,1.94722],[-64.08274,1.64792],[-64.34654,1.35569],[-64.38932,1.5125],[-65.11657,1.12046],[-65.57288,0.62856],[-65.50158,0.92086],[-65.6727,1.01353],[-66.30249,0.74361],[-66.85051,1.22968],[-67.08222,1.17441],[-67.15784,1.80439],[-67.299,1.87494],[-67.40488,2.22258],[-67.9292,1.82455],[-68.18632,2.00091],[-68.26699,1.83463],[-68.18128,1.72881],[-69.37934,1.73008],[-69.54002,1.77023],[-69.84352,1.69919],[-69.84283,1.07848],[-69.26017,1.06856],[-69.14422,0.84172],[-69.20976,0.57958],[-69.47696,0.71065],[-70.04162,0.55437],[-70.03658,-0.19681],[-69.603,-0.51947],[-69.59796,-0.75136],[-69.4215,-1.01853],[-69.43395,-1.42219],[-69.93326,-4.21973],[-69.93567,-4.21946],[-69.94741,-4.22816],[-69.95389,-4.32159],[-69.99449,-4.32082],[-70.02917,-4.35214],[-70.02694,-4.37268],[-70.05483,-4.36737],[-70.04144,-4.3429],[-70.11305,-4.27281],[-70.19582,-4.3607],[-70.33236,-4.15214],[-70.77601,-4.15717],[-70.96814,-4.36915],[-71.87003,-4.51661],[-72.64391,-5.0391],[-72.83973,-5.14765],[-73.24579,-6.05764],[-73.12983,-6.43852],[-73.73986,-6.87919],[-73.77011,-7.28944],[-73.96938,-7.58465]]]}},{"type":"Feature","properties":{"id":"BB"},"geometry":{"type":"Polygon","coordinates":[[[-59.92255,13.58015],[-59.88892,12.77667],[-59.14146,12.80638],[-59.17509,13.60976],[-59.92255,13.58015]]]}},{"type":"Feature","properties":{"id":"BN"},"geometry":{"type":"Polygon","coordinates":[[[114.07589,4.58395],[114.15813,4.57],[114.26876,4.49878],[114.32176,4.34942],[114.32176,4.2552],[114.4416,4.27588],[114.49922,4.13108],[114.64211,4.00694],[114.78539,4.12205],[114.88039,4.4257],[114.83189,4.42387],[114.77303,4.72871],[114.8266,4.75062],[114.86858,4.80961],[114.97346,4.80893],[115.00402,4.88949],[115.05964,4.87187],[115.03509,4.82124],[115.02977,4.73503],[115.04064,4.63706],[115.07737,4.53418],[115.09978,4.39123],[115.31275,4.30806],[115.36346,4.33563],[115.2851,4.42295],[115.27819,4.63661],[115.20737,4.8256],[115.15092,4.87604],[115.16236,5.01011],[114.82635,5.58592],[114.07859,4.64657],[114.07589,4.58395]]]}},{"type":"Feature","properties":{"id":"BT"},"geometry":{"type":"Polygon","coordinates":[[[88.74219,27.144],[88.87184,27.10917],[88.8799,27.0397],[88.87072,26.95627],[88.87836,26.94594],[88.92153,26.99467],[88.95217,26.96927],[88.949,26.93247],[88.98136,26.91694],[89.01904,26.94173],[89.07937,26.89742],[89.09525,26.89153],[89.09362,26.87223],[89.12924,26.81189],[89.1949,26.81135],[89.26219,26.82322],[89.31816,26.84815],[89.37913,26.86224],[89.38319,26.85963],[89.3901,26.84225],[89.42349,26.83727],[89.63369,26.74402],[89.86124,26.73307],[90.05527,26.72859],[90.18882,26.76768],[90.23174,26.85868],[90.29972,26.84857],[90.40838,26.90829],[90.54914,26.81671],[90.67715,26.77215],[91.50067,26.79223],[91.7391,26.82315],[91.82458,26.86358],[91.87351,26.93018],[92.02045,26.84995],[92.08637,26.85684],[92.11469,26.89405],[92.10834,26.98082],[92.03457,27.07334],[92.04702,27.26861],[92.11512,27.27667],[92.01132,27.47352],[91.65007,27.48287],[91.55819,27.6144],[91.64794,27.7756],[91.57842,27.82313],[91.48973,27.93903],[91.46327,28.0064],[91.34685,28.04713],[90.91976,27.86153],[90.69894,28.07784],[90.58842,28.02838],[90.13387,28.19178],[89.79762,28.23979],[89.59525,28.16433],[89.12825,27.62502],[89.15782,27.55789],[88.99131,27.49319],[88.96694,27.40027],[89.00216,27.32532],[88.96947,27.30319],[88.93678,27.33777],[88.91901,27.32483],[88.90531,27.27522],[88.82257,27.25478],[88.74219,27.144]]]}},{"type":"Feature","properties":{"id":"BW"},"geometry":{"type":"Polygon","coordinates":[[[19.99817,-24.76768],[20.02809,-24.78725],[20.03678,-24.81004],[20.29826,-24.94869],[20.64795,-25.47827],[20.86081,-26.14892],[20.61754,-26.4692],[20.63275,-26.78181],[20.68596,-26.9039],[20.87031,-26.80047],[21.13353,-26.86661],[21.37869,-26.82083],[21.69322,-26.86152],[21.7854,-26.79199],[21.78348,-26.68212],[21.83291,-26.65959],[21.90703,-26.66808],[22.05368,-26.63303],[22.23083,-26.38295],[22.41921,-26.23078],[22.57278,-26.20134],[22.70808,-25.99186],[22.86012,-25.50572],[23.03497,-25.29971],[23.47588,-25.29971],[23.70489,-25.45679],[23.7557,-25.46722],[23.97903,-25.64462],[24.18287,-25.62916],[24.339,-25.77608],[24.43393,-25.73542],[24.57092,-25.77361],[24.68387,-25.82353],[24.89673,-25.81225],[25.00368,-25.7348],[25.08178,-25.73078],[25.08873,-25.74168],[25.10427,-25.74099],[25.10221,-25.7486],[25.10946,-25.74992],[25.12298,-25.7641],[25.34296,-25.76851],[25.5832,-25.63719],[25.66474,-25.46698],[25.70268,-25.28862],[25.72895,-25.2568],[25.88996,-24.88129],[25.85125,-24.77917],[25.8515,-24.75727],[26.09364,-24.70613],[26.39224,-24.63827],[26.41027,-24.64608],[26.46346,-24.60358],[26.51842,-24.47871],[26.85916,-24.24273],[26.99151,-23.65663],[27.33768,-23.40917],[27.53156,-23.3785],[27.6066,-23.21894],[27.74154,-23.2137],[27.93539,-23.04941],[27.94647,-22.96202],[28.04752,-22.90243],[28.04562,-22.8394],[28.16139,-22.75766],[28.171,-22.70216],[28.34874,-22.5694],[28.47454,-22.57003],[28.51226,-22.58825],[28.56565,-22.55988],[28.65414,-22.55116],[28.72366,-22.51073],[28.83207,-22.48801],[28.83172,-22.45426],[28.92511,-22.4572],[28.97292,-22.3715],[29.01781,-22.22308],[29.10851,-22.21241],[29.15415,-22.21589],[29.18974,-22.18599],[29.21955,-22.17771],[29.37364,-22.1957],[29.3533,-22.18363],[29.24648,-22.05967],[29.1974,-22.07472],[29.14501,-22.07275],[29.08495,-22.04867],[29.04108,-22.00563],[29.02191,-21.95665],[29.02191,-21.90647],[29.04023,-21.85864],[29.07763,-21.81877],[28.58114,-21.63455],[28.49942,-21.66634],[28.29416,-21.59037],[28.01669,-21.57624],[27.91407,-21.31621],[27.69241,-21.06559],[27.72972,-20.51735],[27.69361,-20.48531],[27.28643,-20.48523],[27.29831,-20.28935],[27.21278,-20.08244],[26.72246,-19.92707],[26.17227,-19.53709],[25.96226,-19.08152],[25.99837,-19.02943],[25.94326,-18.90362],[25.82353,-18.82808],[25.79217,-18.6355],[25.68859,-18.56165],[25.53465,-18.39041],[25.39972,-18.12691],[25.31799,-18.07091],[25.23909,-17.90832],[25.26433,-17.79571],[25.16882,-17.78253],[25.05895,-17.84452],[24.95586,-17.79674],[24.73364,-17.89338],[24.71887,-17.9218],[24.6303,-17.9863],[24.57485,-18.07151],[24.40577,-17.95726],[24.19416,-18.01919],[23.61088,-18.4881],[23.29618,-17.99855],[23.0996,-18.00075],[21.45556,-18.31795],[20.99904,-18.31743],[20.99751,-22.00026],[19.99912,-21.99991],[19.99817,-24.76768]]]}},{"type":"Feature","properties":{"id":"CF"},"geometry":{"type":"Polygon","coordinates":[[[14.42917,6.00508],[14.50796,5.89352],[14.55302,5.9036],[14.5598,5.90044],[14.60974,5.91838],[14.62375,5.70466],[14.58951,5.59777],[14.62531,5.51411],[14.52724,5.28319],[14.57083,5.23979],[14.66949,5.19865],[14.73383,4.6135],[14.83428,4.50643],[15.00904,4.42343],[15.08609,4.30282],[15.10843,4.12848],[15.18035,4.05383],[15.07686,4.01805],[15.24902,3.70941],[15.79353,3.10212],[15.95077,3.02052],[16.08346,2.79816],[16.06553,2.64341],[16.08278,2.5852],[16.08252,2.45708],[16.19357,2.21537],[16.50126,2.84739],[16.46701,2.92512],[16.57598,3.47999],[16.68283,3.54257],[17.01746,3.55136],[17.35649,3.63045],[17.46876,3.70515],[17.60966,3.63705],[17.83421,3.61068],[17.85842,3.53378],[18.05656,3.56893],[18.14902,3.54476],[18.17323,3.47665],[18.24148,3.50302],[18.2723,3.57992],[18.39558,3.58212],[18.49245,3.63924],[18.58711,3.49423],[18.62749,3.46784],[18.59504,3.65596],[18.57753,3.77655],[18.59916,3.89439],[18.63315,3.94029],[18.65444,3.99852],[18.63109,4.10708],[18.57341,4.24918],[18.54423,4.32381],[18.60088,4.36968],[18.62525,4.35514],[18.71829,4.36866],[18.78301,4.42651],[18.80876,4.49411],[18.83005,4.58309],[18.93768,4.68575],[19.02917,4.79729],[19.09715,4.92883],[19.20289,4.94695],[19.24169,5.01587],[19.35962,5.07384],[19.41404,5.12821],[19.66278,5.14428],[19.74895,5.12001],[19.8571,5.06375],[19.89366,4.99894],[20.02395,4.96474],[20.34736,4.72151],[20.58494,4.41487],[20.90383,4.44877],[21.08793,4.39603],[21.11214,4.33895],[21.21341,4.29285],[21.25744,4.33676],[21.5586,4.24849],[21.64203,4.31491],[22.10721,4.20723],[22.24937,4.11701],[22.45504,4.13039],[22.5431,4.22041],[22.60915,4.48821],[22.6928,4.47285],[22.78526,4.71423],[22.84691,4.69887],[22.89094,4.79321],[22.94817,4.82392],[23.38847,4.60013],[24.46719,5.0915],[24.71816,4.90509],[25.31256,5.03668],[25.34558,5.29101],[25.53271,5.37431],[25.86073,5.19455],[26.13371,5.25594],[26.48595,5.04984],[26.74572,5.10685],[26.85579,5.03887],[26.93064,5.13535],[27.09575,5.22305],[27.43801,5.07623],[27.44848,5.01587],[27.46616,5.08666],[27.29158,5.25045],[27.24386,5.35147],[27.28621,5.56382],[27.22705,5.62889],[27.22705,5.71254],[26.46434,6.11222],[26.55807,6.23953],[26.32729,6.36272],[26.40735,6.64244],[25.90076,7.09549],[25.37461,7.33024],[25.35281,7.42595],[25.20337,7.50312],[25.20649,7.61115],[25.29214,7.66675],[25.25319,7.8487],[24.98855,7.96588],[24.85156,8.16933],[24.35965,8.26177],[24.14451,8.34195],[24.25691,8.69288],[23.51905,8.71749],[23.59065,8.99743],[23.44744,8.99128],[23.4848,9.16959],[23.56263,9.19418],[23.64358,9.28637],[23.64981,9.44303],[23.62179,9.53823],[23.69628,9.67147],[23.67164,9.86923],[23.3128,10.45214],[23.02221,10.69235],[22.87758,10.91915],[22.45889,11.00246],[21.72139,10.64136],[21.71479,10.29932],[21.63553,10.217],[21.52766,10.2105],[21.34934,9.95907],[21.26348,9.97642],[20.82979,9.44696],[20.36748,9.11019],[19.76165,9.04039],[19.0702,9.00835],[18.86388,8.87971],[19.11044,8.68172],[18.8618,8.34535],[18.79898,8.25668],[18.67177,8.21845],[18.62612,8.14163],[18.64153,8.08714],[18.6085,8.05009],[18.02731,8.01085],[17.92436,7.95825],[17.66378,7.98511],[16.99619,7.63596],[16.921,7.61878],[16.835,7.53336],[16.65716,7.67288],[16.65801,7.74739],[16.59415,7.76444],[16.59072,7.88557],[16.41583,7.77971],[16.40703,7.68809],[15.79942,7.44149],[15.73118,7.52006],[15.49743,7.52179],[15.23397,7.25135],[15.21846,7.11349],[15.04717,6.77085],[14.96749,6.75905],[14.74206,6.26356],[14.56149,6.18928],[14.43073,6.08867],[14.42917,6.00508]]]}},{"type":"Feature","properties":{"id":"CA"},"geometry":{"type":"Polygon","coordinates":[[[-141.00116,60.30648],[-140.5227,60.22077],[-140.45648,60.30919],[-139.98024,60.18027],[-139.68991,60.33693],[-139.05831,60.35205],[-139.20603,60.08896],[-139.05365,59.99655],[-138.71149,59.90728],[-138.62145,59.76431],[-137.60623,59.24465],[-137.4925,58.89415],[-136.82619,59.16198],[-136.52365,59.16752],[-136.47323,59.46617],[-136.33727,59.44466],[-136.22381,59.55526],[-136.31566,59.59083],[-135.48007,59.79937],[-135.03069,59.56208],[-135.00267,59.28745],[-134.7047,59.2458],[-134.49912,59.12103],[-134.27175,58.8634],[-133.84645,58.73543],[-133.38523,58.42773],[-131.8271,56.62247],[-130.77769,56.36185],[-130.33965,56.10849],[-130.10173,56.12178],[-130.00093,56.00325],[-130.00857,55.91344],[-130.15373,55.74895],[-129.97513,55.28029],[-130.08035,55.21556],[-130.18765,55.07744],[-130.27203,54.97174],[-130.53591,54.80215],[-130.65082,54.7709],[-130.61931,54.70835],[-133.92876,54.62289],[-132.11664,51.5313],[-125.03842,48.53282],[-123.15614,48.22053],[-123.26565,48.6959],[-122.98526,48.79206],[-123.3218,49.00227],[-117.03266,49.00056],[-116.04938,48.99999],[-114.0683,48.99885],[-110.0051,48.99901],[-104.05004,48.99925],[-101.36198,48.99935],[-97.24024,48.99952],[-95.15355,48.9996],[-95.15357,49.37852],[-95.05825,49.35311],[-94.95766,49.37046],[-94.85381,49.32492],[-94.8159,49.32299],[-94.82487,49.29483],[-94.68751,48.84286],[-94.70477,48.82975],[-94.68691,48.77498],[-94.58903,48.71803],[-94.54885,48.71543],[-94.53826,48.70216],[-94.44258,48.69223],[-94.4174,48.71049],[-94.26485,48.70188],[-94.2464,48.65422],[-93.84515,48.63011],[-93.82292,48.62313],[-93.80279,48.51892],[-93.66382,48.51845],[-93.45374,48.54834],[-93.46377,48.58567],[-93.40693,48.60948],[-93.39758,48.60364],[-93.3712,48.60599],[-93.33946,48.62787],[-93.22713,48.64334],[-92.94973,48.60866],[-92.62836,48.52564],[-92.62747,48.50278],[-92.69927,48.49573],[-92.71323,48.46081],[-92.65606,48.43471],[-92.50712,48.44921],[-92.45588,48.40624],[-92.48147,48.36609],[-92.37185,48.22259],[-92.27167,48.25046],[-92.30939,48.31251],[-92.26662,48.35651],[-92.05339,48.35958],[-91.98929,48.25409],[-91.71231,48.18936],[-91.70451,48.11805],[-91.55649,48.10611],[-91.58025,48.04339],[-91.45829,48.07454],[-91.43248,48.04912],[-91.25025,48.08522],[-91.08016,48.18096],[-90.87588,48.2484],[-90.75045,48.09143],[-90.56444,48.12184],[-90.56312,48.09488],[-90.07418,48.11043],[-89.89974,47.98109],[-89.77248,48.02607],[-89.57972,48.00023],[-89.48837,48.01412],[-88.37033,48.30586],[-85.04547,46.88317],[-84.55635,46.45974],[-84.47607,46.45225],[-84.4402,46.49657],[-84.34174,46.50683],[-84.29893,46.49127],[-84.26351,46.49508],[-84.2264,46.53337],[-84.1945,46.54061],[-84.17723,46.52753],[-84.12953,46.53233],[-84.11196,46.50248],[-84.14875,46.40366],[-84.11254,46.32329],[-84.11615,46.2681],[-84.09756,46.25512],[-84.1096,46.23987],[-83.94231,46.05681],[-83.90453,46.05922],[-83.82299,46.12002],[-83.57017,46.105],[-83.43746,45.99749],[-83.59589,45.82131],[-81.54485,44.86396],[-82.42469,42.992],[-82.4146,42.97626],[-82.4253,42.95423],[-82.45331,42.93139],[-82.4826,42.8068],[-82.46613,42.76615],[-82.51063,42.66025],[-82.51617,42.61668],[-82.59645,42.5468],[-82.64242,42.55594],[-82.83152,42.37811],[-82.97944,42.33438],[-83.07871,42.31244],[-83.12724,42.2376],[-83.14962,42.04089],[-83.08506,41.89693],[-82.71775,41.66281],[-80.55605,42.3348],[-79.78216,42.57325],[-78.89518,42.84543],[-78.91213,42.93838],[-79.02074,42.98444],[-79.02424,43.01983],[-78.99941,43.05612],[-79.01055,43.06659],[-79.07636,43.07797],[-79.06276,43.09706],[-79.05782,43.11153],[-79.06881,43.12029],[-79.0427,43.13934],[-79.04652,43.16396],[-79.05178,43.17029],[-79.05512,43.25375],[-79.06921,43.26183],[-79.25796,43.54052],[-76.79706,43.63099],[-76.43859,44.09393],[-76.35324,44.13493],[-76.31222,44.19894],[-76.244,44.19643],[-76.1664,44.23051],[-76.16285,44.28262],[-76.00018,44.34896],[-75.95947,44.34463],[-75.8217,44.43176],[-75.76813,44.51537],[-75.41441,44.76614],[-75.2193,44.87821],[-75.01363,44.95608],[-74.99101,44.98051],[-74.8447,45.00606],[-74.66689,45.00646],[-74.32699,44.99029],[-73.35073,45.01056],[-72.69824,45.01566],[-72.52504,45.00826],[-72.08662,45.00571],[-71.50067,45.01357],[-71.48735,45.07784],[-71.42778,45.12624],[-71.40364,45.21382],[-71.44252,45.2361],[-71.37133,45.24624],[-71.29371,45.29996],[-71.22338,45.25184],[-71.19723,45.25438],[-71.14568,45.24128],[-71.08364,45.30623],[-70.9406,45.34341],[-70.89864,45.2398],[-70.81726,45.2219],[-70.80715,45.4143],[-70.78372,45.43269],[-70.65383,45.37592],[-70.62518,45.42286],[-70.72651,45.49771],[-70.59168,45.64987],[-70.38934,45.73215],[-70.41523,45.79497],[-70.25976,45.89675],[-70.24694,45.95138],[-70.31025,45.96424],[-70.23855,46.1453],[-70.29078,46.18832],[-70.18547,46.35357],[-70.05812,46.41768],[-69.99966,46.69543],[-69.22485,47.4596],[-69.05148,47.42012],[-69.05073,47.30076],[-69.05039,47.2456],[-68.89222,47.1807],[-68.79784,47.21665],[-68.69424,47.24148],[-68.62206,47.24142],[-68.60069,47.25063],[-68.58687,47.28272],[-68.55228,47.28243],[-68.50009,47.30088],[-68.44216,47.28307],[-68.37598,47.28668],[-68.38431,47.32567],[-68.37203,47.35126],[-68.32998,47.36028],[-68.23565,47.35464],[-68.16578,47.32422],[-68.14012,47.29972],[-67.96382,47.20172],[-67.93155,47.15995],[-67.86495,47.09981],[-67.79027,47.06731],[-67.78111,45.9392],[-67.75196,45.91814],[-67.80961,45.87531],[-67.75955,45.82748],[-67.80653,45.80022],[-67.80705,45.69528],[-67.6049,45.60725],[-67.45536,45.60851],[-67.42017,45.57415],[-67.42863,45.56872],[-67.42144,45.50584],[-67.50578,45.48971],[-67.42394,45.37969],[-67.48789,45.28207],[-67.40215,45.16097],[-67.34351,45.1246],[-67.29623,45.14751],[-67.30074,45.16588],[-67.29168,45.17229],[-67.29359,45.17737],[-67.28924,45.18799],[-67.28456,45.19153],[-67.26508,45.1902],[-67.23321,45.16882],[-67.15904,45.16312],[-67.0216,44.95333],[-66.96824,44.90965],[-66.98249,44.87071],[-66.96824,44.83078],[-66.93432,44.82597],[-67.16117,44.20069],[-65.81187,42.91911],[-59.437,43.71683],[-57.60106,47.38123],[-56.67989,47.3339],[-56.25228,47.31192],[-55.8643,46.64935],[-52.04856,46.06197],[-53.04203,51.48141],[-64.00822,60.49118],[-60.64457,66.70518],[-78.6941,75.13595],[-73.91222,78.42484],[-67.1326,80.75493],[-66.45595,80.82603],[-59.93819,82.31398],[-64.90918,83.25283],[-80.79441,83.23241],[-110.08928,79.34392],[-123.66215,76.4243],[-127.92083,71.23557],[-136.44585,69.52838],[-140.97259,70.50112],[-140.97259,70.50112],[-141.00116,60.30648]]]}},{"type":"Feature","properties":{"id":"CN"},"geometry":{"type":"Polygon","coordinates":[[[73.5004,39.38402],[73.55396,39.3543],[73.54572,39.27567],[73.60638,39.24534],[73.75823,39.023],[73.81728,39.04007],[73.82964,38.91517],[73.7445,38.93867],[73.7033,38.84782],[73.80656,38.66449],[73.79806,38.61106],[73.97933,38.52945],[74.17022,38.65504],[74.51217,38.47034],[74.69619,38.42947],[74.69894,38.22155],[74.80331,38.19889],[74.82665,38.07359],[74.9063,38.03033],[74.92416,37.83428],[75.00935,37.77486],[74.8912,37.67576],[74.94338,37.55501],[75.06011,37.52779],[75.13907,37.42124],[75.12328,37.31839],[74.88887,37.23275],[74.80605,37.21565],[74.49981,37.24518],[74.56453,37.03023],[75.13839,37.02622],[75.40481,36.95382],[75.45562,36.71971],[75.72737,36.7529],[75.92391,36.56986],[76.0324,36.41198],[76.00906,36.17511],[75.93028,36.13136],[76.15325,35.9264],[76.14913,35.82848],[76.33453,35.84296],[76.50961,35.8908],[76.77323,35.66062],[76.84539,35.67356],[76.96624,35.5932],[77.44277,35.46132],[77.70232,35.46244],[77.80532,35.52058],[78.11664,35.48022],[78.03466,35.3785],[78.00033,35.23954],[78.22692,34.88771],[78.18435,34.7998],[78.27781,34.61484],[78.54964,34.57283],[78.56475,34.50835],[78.74465,34.45174],[79.05364,34.32482],[78.99802,34.3027],[78.91769,34.15452],[78.66225,34.08858],[78.65657,34.03195],[78.73367,34.01121],[78.77349,33.73871],[78.67599,33.66445],[78.73636,33.56521],[79.15252,33.17156],[79.14016,33.02545],[79.46562,32.69668],[79.26768,32.53277],[79.13174,32.47766],[79.0979,32.38051],[78.99322,32.37948],[78.96713,32.33655],[78.7831,32.46873],[78.73916,32.69438],[78.38897,32.53938],[78.4645,32.45367],[78.49609,32.2762],[78.68754,32.10256],[78.74404,32.00384],[78.78036,31.99478],[78.69933,31.78723],[78.84516,31.60631],[78.71032,31.50197],[78.77898,31.31209],[79.01931,31.42817],[79.14016,31.43403],[79.22805,31.34963],[79.59884,30.93943],[79.93255,30.88288],[80.20721,30.58541],[80.54504,30.44936],[80.83343,30.32023],[81.03953,30.20059],[81.12842,30.01395],[81.24362,30.0126],[81.29032,30.08806],[81.2623,30.14596],[81.33355,30.15303],[81.39928,30.21862],[81.41018,30.42153],[81.62033,30.44703],[81.99082,30.33423],[82.10135,30.35439],[82.10757,30.23745],[82.19475,30.16884],[82.16984,30.0692],[82.38622,30.02608],[82.5341,29.9735],[82.73024,29.81695],[83.07116,29.61957],[83.28131,29.56813],[83.44787,29.30513],[83.63156,29.16249],[83.82303,29.30513],[83.97559,29.33091],[84.18107,29.23451],[84.24801,29.02783],[84.2231,28.89571],[84.47528,28.74023],[84.62317,28.73887],[84.85511,28.58041],[85.06059,28.68562],[85.19135,28.62825],[85.18668,28.54076],[85.10729,28.34092],[85.38127,28.28336],[85.4233,28.32996],[85.59765,28.30529],[85.60854,28.25045],[85.69105,28.38475],[85.71907,28.38064],[85.74864,28.23126],[85.84672,28.18187],[85.90743,28.05144],[85.97813,27.99023],[85.9486,27.94085],[86.06309,27.90021],[86.12069,27.93047],[86.08333,28.02121],[86.088,28.09264],[86.18607,28.17364],[86.22966,27.9786],[86.42736,27.91122],[86.51609,27.96623],[86.56265,28.09569],[86.74181,28.10638],[86.75582,28.04182],[87.03757,27.94835],[87.11696,27.84104],[87.56996,27.84517],[87.72718,27.80938],[87.82681,27.95248],[88.13378,27.88015],[88.1278,27.95417],[88.25332,27.9478],[88.54858,28.06057],[88.63235,28.12356],[88.83559,28.01936],[88.88091,27.85192],[88.77517,27.45415],[88.82981,27.38814],[88.91901,27.32483],[88.93678,27.33777],[88.96947,27.30319],[89.00216,27.32532],[88.96694,27.40027],[88.99131,27.49319],[89.15782,27.55789],[89.12825,27.62502],[89.59525,28.16433],[89.79762,28.23979],[90.13387,28.19178],[90.58842,28.02838],[90.69894,28.07784],[90.91976,27.86153],[91.34685,28.04713],[91.46327,28.0064],[91.48973,27.93903],[91.57842,27.82313],[91.61189,27.83786],[91.66975,27.82435],[91.73738,27.77332],[91.83437,27.78411],[91.87471,27.71605],[92.27432,27.89077],[92.32101,27.79363],[92.42538,27.80092],[92.7275,27.98662],[92.73025,28.05814],[92.65472,28.07632],[92.67486,28.15018],[92.93609,28.23181],[93.14635,28.37035],[93.19221,28.52903],[93.37623,28.53687],[93.62377,28.68426],[93.72797,28.68821],[94.35897,29.01965],[94.2752,29.11687],[94.69318,29.31739],[94.81353,29.17804],[95.0978,29.14446],[95.11291,29.09527],[95.2214,29.10727],[95.26122,29.07727],[95.3038,29.13847],[95.41091,29.13007],[95.50842,29.13487],[95.72086,29.20797],[95.75149,29.32063],[95.84899,29.31464],[96.05361,29.38167],[96.31316,29.18643],[96.18682,29.11087],[96.20467,29.02325],[96.3626,29.10607],[96.61391,28.72742],[96.40929,28.51526],[96.48895,28.42955],[96.6455,28.61657],[96.85561,28.4875],[96.88445,28.39452],[96.98882,28.32564],[97.1289,28.3619],[97.34547,28.21385],[97.41729,28.29783],[97.47085,28.2688],[97.50518,28.49716],[97.56835,28.55628],[97.70705,28.5056],[97.79763,28.3278],[97.90191,28.37327],[98.00474,28.27641],[98.01589,28.2073],[98.08404,28.19913],[98.15337,28.12114],[98.13829,27.96302],[98.32641,27.51385],[98.42529,27.55404],[98.43353,27.67086],[98.69582,27.56499],[98.7333,26.85615],[98.77547,26.61994],[98.72741,26.36183],[98.67797,26.24487],[98.7329,26.17218],[98.66884,26.09165],[98.63128,26.15492],[98.57085,26.11547],[98.60763,26.01512],[98.70818,25.86241],[98.63128,25.79937],[98.54064,25.85129],[98.40606,25.61129],[98.31268,25.55307],[98.25774,25.6051],[98.16848,25.62739],[98.18084,25.56298],[98.12591,25.50722],[98.14925,25.41547],[97.92541,25.20815],[97.83857,25.27186],[97.75926,25.09679],[97.72216,25.08508],[97.71729,24.98193],[97.72903,24.91332],[97.76046,24.88223],[97.79949,24.85655],[97.76481,24.8289],[97.73127,24.83015],[97.70236,24.84356],[97.64354,24.79171],[97.56648,24.76475],[97.56383,24.75535],[97.5542,24.74943],[97.54675,24.74202],[97.57026,24.72297],[97.56286,24.54535],[97.52757,24.43748],[97.60029,24.4401],[97.67146,24.45472],[97.72047,24.35945],[97.65624,24.33781],[97.66723,24.30027],[97.73368,24.29672],[97.76799,24.26365],[97.72998,24.2302],[97.726,24.20293],[97.72799,24.18883],[97.7439,24.1843],[97.75305,24.16902],[97.73454,24.15192],[97.72903,24.12606],[97.63309,24.04983],[97.62639,24.00907],[97.51653,23.9395],[97.63395,23.87955],[97.64511,23.84407],[97.72302,23.89288],[97.76415,23.91189],[97.76827,23.93479],[97.79456,23.94836],[97.79416,23.95663],[97.80608,23.95268],[97.80964,23.96229],[97.8266,23.95409],[97.82368,23.97252],[97.83835,23.96272],[97.84328,23.97603],[97.86545,23.97723],[97.88749,23.97456],[97.89541,23.97778],[97.89715,23.9797],[97.89693,23.98399],[97.8942,23.98357],[97.88814,23.98605],[97.88414,23.99405],[97.88556,24.00291],[97.90792,24.02043],[97.93951,24.01953],[97.98508,24.03556],[97.99583,24.04932],[98.01753,24.05724],[98.04709,24.07616],[98.05274,24.07375],[98.05671,24.07961],[98.06271,24.07825],[98.07007,24.08204],[98.07602,24.07868],[98.09529,24.08901],[98.21537,24.11369],[98.3587,24.09943],[98.36299,24.11354],[98.54667,24.12944],[98.59256,24.08371],[98.71713,24.12873],[98.85319,24.13042],[98.87998,24.15624],[98.89632,24.10612],[98.86776,24.08572],[98.86961,24.07808],[98.67797,23.9644],[98.68209,23.80492],[98.79607,23.77947],[98.82933,23.72921],[98.8179,23.70253],[98.88656,23.59734],[98.80294,23.5345],[98.8276,23.47867],[98.88021,23.49017],[98.91334,23.41883],[98.92104,23.36946],[98.87573,23.33038],[98.93958,23.31414],[98.92515,23.29535],[98.88519,23.18423],[99.05975,23.16382],[99.04601,23.12215],[99.105,23.10152],[99.19881,23.11004],[99.25014,23.08225],[99.30353,23.10404],[99.33803,23.13341],[99.41888,23.08668],[99.52214,23.08218],[99.51939,22.99821],[99.56531,22.93317],[99.54218,22.90014],[99.43305,22.94385],[99.45931,22.84991],[99.39949,22.82856],[99.37957,22.76715],[99.31537,22.73977],[99.3861,22.57755],[99.37972,22.50188],[99.28771,22.4105],[99.22903,22.25541],[99.1783,22.18398],[99.17362,22.17969],[99.1822,22.17576],[99.20285,22.17218],[99.15573,22.16197],[99.27246,22.10281],[99.32121,22.10051],[99.4315,22.10544],[99.47585,22.13345],[99.65423,22.09295],[99.70762,22.04332],[99.72564,22.06686],[99.85267,22.03186],[99.96906,22.05971],[99.99084,21.97053],[99.94331,21.82548],[99.98654,21.71064],[100.04931,21.66763],[100.06965,21.69284],[100.11918,21.70608],[100.17486,21.65306],[100.10757,21.59945],[100.12493,21.51185],[100.16887,21.48645],[100.18447,21.51898],[100.242,21.46752],[100.29624,21.48014],[100.35201,21.53176],[100.43091,21.54243],[100.4811,21.46148],[100.57861,21.45637],[100.71999,21.51272],[100.8847,21.6855],[101.00383,21.70974],[101.08228,21.77081],[101.11744,21.77659],[101.11627,21.69252],[101.16682,21.6437],[101.15156,21.56129],[101.21618,21.55879],[101.19953,21.43709],[101.14013,21.40265],[101.19009,21.32487],[101.2504,21.29478],[101.21661,21.23234],[101.28767,21.1736],[101.54563,21.25668],[101.6068,21.23329],[101.59491,21.18621],[101.60886,21.17947],[101.66904,21.20272],[101.70548,21.14911],[101.76408,21.14204],[101.79025,21.20369],[101.83887,21.20983],[101.84412,21.25291],[101.74014,21.30967],[101.75142,21.45195],[101.7727,21.51794],[101.7475,21.5873],[101.80001,21.57461],[101.83257,21.61562],[101.74555,21.72852],[101.7791,21.83019],[101.62566,21.96574],[101.57525,22.13026],[101.60675,22.13513],[101.54882,22.23586],[101.56581,22.27428],[101.62074,22.27325],[101.68973,22.46843],[101.7685,22.50337],[101.86828,22.38397],[101.90714,22.38688],[101.91344,22.44417],[101.98487,22.42766],[102.03633,22.46164],[102.1245,22.43372],[102.14099,22.40092],[102.16621,22.43336],[102.26428,22.41321],[102.25339,22.4607],[102.41061,22.64184],[102.38415,22.67919],[102.42618,22.69212],[102.46665,22.77108],[102.51802,22.77969],[102.57095,22.7036],[102.60675,22.73376],[102.8636,22.60735],[102.9321,22.48659],[103.0722,22.44775],[103.07843,22.50097],[103.17961,22.55705],[103.15741,22.59463],[103.16604,22.60913],[103.18513,22.64498],[103.28079,22.68063],[103.28933,22.7366],[103.33602,22.80933],[103.43179,22.75816],[103.43646,22.70648],[103.52675,22.59155],[103.57812,22.65764],[103.56056,22.69884],[103.64175,22.79881],[103.87667,22.56598],[103.93286,22.52703],[103.94513,22.52553],[103.95191,22.5134],[103.96352,22.50584],[103.9692,22.51243],[103.97499,22.50609],[103.99247,22.51958],[104.00666,22.5187],[104.01147,22.52385],[104.00705,22.54216],[104.04108,22.72647],[104.12116,22.81261],[104.14155,22.81083],[104.27084,22.8457],[104.25683,22.76534],[104.27201,22.74539],[104.33947,22.72172],[104.3532,22.69369],[104.36797,22.68696],[104.39835,22.70161],[104.41371,22.73249],[104.47225,22.75813],[104.58005,22.8564],[104.60134,22.81637],[104.65507,22.83797],[104.72828,22.81906],[104.77114,22.90017],[104.84942,22.93631],[104.86765,22.95178],[104.8334,23.01484],[104.79892,23.1192],[104.87497,23.12915],[104.88072,23.16585],[104.90638,23.18084],[104.9438,23.15235],[104.96509,23.20182],[104.99547,23.20277],[105.07002,23.26248],[105.11281,23.24686],[105.17267,23.28826],[105.23357,23.26035],[105.24078,23.28826],[105.25846,23.31813],[105.24971,23.33555],[105.27996,23.3419],[105.30807,23.38196],[105.32376,23.39684],[105.37905,23.31128],[105.42411,23.28219],[105.43767,23.303],[105.48986,23.22983],[105.5211,23.18612],[105.54822,23.19259],[105.56505,23.15961],[105.56247,23.07073],[105.72401,23.06425],[105.87558,22.91887],[105.89498,22.93815],[105.99815,22.94116],[106.00622,22.99268],[106.20088,22.98557],[106.26869,22.87459],[106.30868,22.86399],[106.32568,22.87451],[106.34817,22.85695],[106.37632,22.88273],[106.49749,22.91164],[106.51306,22.94891],[106.53631,22.94242],[106.56274,22.92068],[106.60179,22.92884],[106.6516,22.86862],[106.6734,22.89587],[106.71387,22.88296],[106.70702,22.86708],[106.7817,22.81455],[106.81271,22.8226],[106.83766,22.80672],[106.82404,22.7881],[106.76293,22.73491],[106.72582,22.63587],[106.71698,22.58432],[106.65316,22.5757],[106.60875,22.60481],[106.58188,22.51842],[106.58502,22.47552],[106.57592,22.46945],[106.5721,22.47655],[106.55794,22.4637],[106.58523,22.38039],[106.55605,22.34309],[106.6516,22.33977],[106.70239,22.22014],[106.69535,22.20647],[106.67029,22.18565],[106.69389,22.13295],[106.71243,22.09375],[106.70917,22.0255],[106.68274,21.99811],[106.69381,21.95721],[106.70913,21.97369],[106.71887,21.97421],[106.74671,22.00817],[106.77706,22.00672],[106.81217,21.97385],[106.9178,21.97357],[106.9295,21.93197],[106.97387,21.92282],[106.99252,21.95191],[107.05634,21.92303],[107.06101,21.88982],[107.00348,21.84556],[107.02558,21.81969],[107.10631,21.79855],[107.20734,21.71493],[107.24625,21.7077],[107.29728,21.74586],[107.35834,21.6672],[107.35989,21.60063],[107.38636,21.59774],[107.41593,21.64839],[107.47197,21.6672],[107.48637,21.64362],[107.49658,21.61322],[107.48268,21.59862],[107.5001,21.59547],[107.54173,21.5904],[107.56005,21.61306],[107.58254,21.61697],[107.59541,21.60417],[107.67674,21.60808],[107.71674,21.62188],[107.72094,21.62751],[107.80755,21.6591],[107.8257,21.65355],[107.8387,21.64314],[107.8593,21.65427],[107.8766,21.64043],[107.89814,21.59072],[107.92397,21.58936],[107.94264,21.56522],[107.95341,21.53756],[107.96745,21.53604],[107.97074,21.54072],[107.97539,21.53955],[107.97932,21.54503],[108.03212,21.54782],[108.0569,21.53604],[108.10003,21.47338],[108.26073,20.07614],[107.44022,18.66249],[110.2534,15.19951],[112.88221,15.61902],[117.76968,23.10828],[118.41371,24.06775],[118.179,24.33015],[118.09488,24.38193],[118.28244,24.51231],[118.35291,24.51645],[118.42453,24.54644],[118.56434,24.49266],[120.49232,25.22863],[121.03532,26.8787],[123.5458,31.01942],[122.29378,31.76513],[122.80525,33.30571],[122.7354,36.70228],[123.90497,38.79949],[124.16679,39.77477],[124.23201,39.9248],[124.34703,39.94804],[124.37346,40.0278],[124.3322,40.05573],[124.40719,40.13655],[124.46994,40.17546],[124.59423,40.27271],[124.71919,40.32115],[124.741,40.37048],[124.88502,40.4668],[125.0354,40.46079],[125.0045,40.52371],[125.46592,40.70562],[125.70608,40.86562],[125.77423,40.89262],[125.80839,40.86614],[125.90984,40.91195],[125.9131,40.88574],[126.00889,40.91286],[126.12768,41.0532],[126.11824,41.08219],[126.15016,41.08698],[126.22364,41.12746],[126.23445,41.14608],[126.2717,41.15371],[126.2923,41.16948],[126.28389,41.18795],[126.30552,41.18989],[126.31736,41.2285],[126.35307,41.24322],[126.36594,41.28438],[126.43203,41.33042],[126.43341,41.35026],[126.51615,41.37333],[126.47186,41.34356],[126.53189,41.35206],[126.61485,41.67304],[126.74497,41.67342],[126.70394,41.75274],[126.79304,41.69156],[126.80308,41.76401],[126.83166,41.71437],[126.86797,41.78193],[126.91337,41.80171],[126.93792,41.80548],[126.93122,41.77406],[127.0119,41.73904],[127.04092,41.74685],[127.12537,41.5976],[127.18305,41.58848],[127.10057,41.54462],[127.16872,41.52245],[127.20605,41.53209],[127.20623,41.5177],[127.23318,41.52098],[127.23635,41.49527],[127.26931,41.51307],[127.28588,41.50407],[127.24888,41.48022],[127.28776,41.48395],[127.3536,41.45546],[127.35643,41.47675],[127.39445,41.47855],[127.40604,41.4581],[127.44775,41.4588],[127.47367,41.46897],[127.52998,41.46787],[127.58156,41.42573],[127.61049,41.43037],[127.64044,41.40745],[127.65452,41.42052],[127.69992,41.41859],[127.74653,41.42515],[127.83914,41.42065],[127.85614,41.40771],[127.93055,41.44491],[128.03827,41.41698],[128.0326,41.39149],[128.05904,41.39438],[128.10882,41.36205],[128.1235,41.38022],[128.14925,41.38138],[128.16478,41.40224],[128.18546,41.41279],[128.20272,41.40874],[128.20658,41.42702],[128.22126,41.44472],[128.30716,41.60322],[128.15119,41.74568],[128.04487,42.01769],[128.73229,42.03756],[128.94601,42.02098],[128.95683,42.08013],[129.01914,42.09185],[129.04815,42.13425],[129.12823,42.14825],[129.12729,42.15984],[129.16368,42.16403],[129.15887,42.1807],[129.19492,42.20296],[129.2023,42.23843],[129.17707,42.25749],[129.21792,42.26524],[129.19801,42.31387],[129.25449,42.32162],[129.21243,42.37236],[129.2665,42.38016],[129.29912,42.41255],[129.35646,42.4574],[129.37963,42.4422],[129.42821,42.44626],[129.54794,42.37052],[129.59901,42.45449],[129.71694,42.43137],[129.74226,42.47526],[129.73669,42.56231],[129.75294,42.59409],[129.77183,42.69435],[129.76037,42.72179],[129.7835,42.76521],[129.80719,42.79218],[129.81342,42.8474],[129.83711,42.87269],[129.84372,42.92054],[129.87204,42.91633],[129.84603,42.95277],[129.8865,43.00395],[129.95082,43.01051],[129.96409,42.97306],[130.14369,42.98468],[130.09735,42.91243],[130.26849,42.9025],[130.2309,42.79174],[130.24051,42.72658],[130.35467,42.63408],[130.38007,42.60149],[130.43226,42.60831],[130.4302,42.55156],[130.47397,42.55295],[130.47328,42.61665],[130.51105,42.61981],[130.51345,42.57836],[130.55143,42.52158],[130.62107,42.58413],[130.56576,42.68925],[130.40213,42.70788],[130.44361,42.76205],[130.66524,42.84753],[131.02438,42.86518],[131.02668,42.91246],[131.135,42.94114],[131.10274,43.04734],[131.20414,43.13654],[131.19031,43.21385],[131.30324,43.39498],[131.30739,43.47335],[131.19492,43.53047],[131.22962,43.6552],[131.20525,43.82164],[131.26176,43.94011],[131.23583,43.96085],[131.25484,44.03131],[131.30365,44.04262],[131.10603,44.70673],[130.95639,44.85154],[131.48415,44.99513],[131.68466,45.12374],[131.66852,45.2196],[131.76532,45.22609],[131.86903,45.33636],[131.99417,45.2567],[132.83978,45.05916],[132.96373,45.0212],[133.12293,45.1332],[133.09279,45.25693],[133.19652,45.51284],[133.41083,45.57723],[133.47976,45.67212],[133.43616,45.71049],[133.48457,45.86203],[133.52251,45.89849],[133.57915,45.8655],[133.61228,45.90171],[133.60645,45.9379],[133.67013,45.94136],[133.67569,45.9759],[133.73897,46.0637],[133.67065,46.14416],[133.91441,46.26273],[133.86154,46.34526],[133.94977,46.40117],[133.84104,46.46681],[133.91647,46.59638],[134.01466,46.66663],[134.02255,46.77937],[134.11388,47.06591],[134.24777,47.12224],[134.14375,47.26222],[134.20074,47.34301],[134.31644,47.43737],[134.50252,47.44666],[134.7671,47.72051],[134.57221,48.006],[134.67098,48.1564],[134.76619,48.36286],[134.49516,48.42884],[132.66989,47.96491],[132.57309,47.71741],[131.90448,47.68011],[131.2635,47.73325],[131.09871,47.6852],[130.95985,47.6957],[130.90915,47.90623],[130.65103,48.10052],[130.84462,48.30942],[130.52147,48.61745],[130.66946,48.88251],[130.61358,48.88862],[130.43232,48.90844],[130.2355,48.86741],[129.85416,49.11067],[129.67598,49.29596],[129.50685,49.42398],[129.40398,49.44194],[129.35317,49.3481],[129.23232,49.40353],[129.11153,49.36813],[128.72896,49.58676],[127.83476,49.5748],[127.53516,49.84306],[127.49299,50.01251],[127.60515,50.23503],[127.37384,50.28393],[127.36009,50.43787],[127.28765,50.46585],[127.36335,50.58306],[127.28165,50.72075],[127.14586,50.91152],[126.93135,51.0841],[126.90369,51.3238],[126.68349,51.70607],[126.44606,51.98254],[126.56524,52.12042],[126.03378,52.58052],[126.08562,52.79923],[125.96099,52.76995],[125.6131,53.07229],[125.17522,53.20225],[124.46078,53.21881],[123.86158,53.49391],[123.26989,53.54843],[122.85966,53.47395],[122.35063,53.49565],[121.48681,53.33169],[120.85633,53.28499],[120.0451,52.7359],[120.04049,52.58773],[120.46454,52.63811],[120.71673,52.54099],[120.61346,52.32447],[120.77337,52.20805],[120.65907,51.93544],[120.10963,51.671],[119.13553,50.37412],[119.38598,50.35162],[119.27996,50.13348],[119.11003,50.00276],[118.61623,49.93809],[117.82343,49.52696],[117.48208,49.62324],[117.27597,49.62544],[117.07142,49.68482],[116.71193,49.83813],[116.03781,48.87014],[116.06565,48.81716],[115.78876,48.51781],[115.811,48.25699],[115.52082,48.15367],[115.57128,47.91988],[115.94296,47.67741],[116.08431,47.80693],[116.2527,47.87766],[116.4465,47.83662],[116.67405,47.89039],[116.87527,47.88836],[117.08918,47.82242],[117.37875,47.63627],[117.50181,47.77216],[117.80196,48.01661],[118.03676,48.00982],[118.11009,48.04],[118.22677,48.03853],[118.29654,48.00246],[118.55766,47.99277],[118.7564,47.76947],[119.12343,47.66458],[119.13995,47.53997],[119.35892,47.48104],[119.31964,47.42617],[119.54918,47.29505],[119.56019,47.24874],[119.62403,47.24575],[119.71209,47.19192],[119.85518,46.92196],[119.91242,46.90091],[119.89261,46.66423],[119.80455,46.67631],[119.77373,46.62947],[119.68127,46.59015],[119.65265,46.62342],[119.42827,46.63783],[119.37306,46.61132],[119.30261,46.6083],[119.24978,46.64761],[119.10448,46.65516],[119.00541,46.74273],[118.92616,46.72765],[118.89974,46.77139],[118.8337,46.77742],[118.78747,46.68689],[118.30534,46.73519],[117.69554,46.50991],[117.60748,46.59771],[117.41782,46.57862],[117.36609,46.36335],[117.07252,46.35818],[116.83166,46.38637],[116.75551,46.33083],[116.58612,46.30211],[116.26678,45.96479],[116.24012,45.8778],[116.27366,45.78637],[116.16989,45.68603],[115.91898,45.6227],[115.69688,45.45761],[115.35757,45.39106],[114.94546,45.37377],[114.74612,45.43585],[114.54801,45.38337],[114.5166,45.27189],[114.08071,44.92847],[113.909,44.91444],[113.63821,44.74326],[112.74662,44.86297],[112.4164,45.06858],[111.98695,45.09074],[111.76275,44.98032],[111.40498,44.3461],[111.96289,43.81596],[111.93776,43.68709],[111.79758,43.6637],[111.59087,43.51207],[111.0149,43.3289],[110.4327,42.78293],[110.08401,42.6411],[109.89402,42.63111],[109.452,42.44842],[109.00679,42.45302],[108.84489,42.40246],[108.23156,42.45532],[107.57258,42.40898],[107.49681,42.46221],[107.29755,42.41395],[107.24774,42.36107],[106.76517,42.28741],[105.24708,41.7442],[105.01119,41.58382],[104.91272,41.64619],[104.51667,41.66113],[104.52258,41.8706],[103.92804,41.78246],[103.3685,41.89696],[102.72403,42.14675],[102.42826,42.15137],[102.07645,42.22519],[101.80515,42.50074],[101.28833,42.58524],[100.84979,42.67087],[100.33297,42.68231],[99.50671,42.56535],[97.2134,42.79942],[96.37926,42.72055],[96.35658,42.90363],[95.89543,43.2528],[95.52594,43.99353],[95.32891,44.02407],[95.39772,44.2805],[95.01191,44.25274],[94.71959,44.35284],[94.10003,44.71016],[93.51161,44.95964],[91.64048,45.07408],[90.89169,45.19667],[90.65114,45.49314],[90.70907,45.73437],[91.03026,46.04194],[90.99672,46.14207],[90.89639,46.30711],[91.07696,46.57315],[91.0147,46.58171],[91.03649,46.72916],[90.84035,46.99525],[90.76108,46.99399],[90.48542,47.30438],[90.48854,47.41826],[90.33598,47.68303],[90.10871,47.7375],[90.06512,47.88177],[89.76624,47.82745],[89.55453,48.0423],[89.0711,47.98528],[88.93186,48.10263],[88.8011,48.11302],[88.58316,48.21893],[88.58939,48.34531],[87.96361,48.58478],[88.0788,48.71436],[87.73822,48.89582],[87.88171,48.95853],[87.81333,49.17354],[87.48983,49.13794],[87.478,49.07403],[87.28386,49.11626],[86.89258,49.13859],[86.73568,48.99918],[86.75343,48.70331],[86.38069,48.46064],[85.73581,48.3939],[85.5169,48.05493],[85.61067,47.49753],[85.69696,47.2898],[85.54294,47.06171],[85.22443,47.04816],[84.93995,46.87399],[84.73077,47.01394],[83.92184,46.98912],[83.06453,47.23658],[82.21792,45.56619],[82.58474,45.40027],[82.53478,45.16824],[81.73278,45.3504],[80.08655,45.0301],[79.88433,44.9016],[80.38384,44.63073],[80.40229,44.23319],[80.40031,44.10986],[80.75156,43.44948],[80.69718,43.32589],[80.77771,43.30065],[80.78817,43.14235],[80.62913,43.141],[80.3735,43.01557],[80.58999,42.9011],[80.38169,42.83142],[80.26886,42.8366],[80.16892,42.61137],[80.26841,42.23797],[80.17807,42.21166],[80.17842,42.03211],[79.92977,42.04113],[78.3732,41.39603],[78.15757,41.38565],[78.12873,41.23091],[77.81287,41.14307],[77.76206,41.01574],[77.52723,41.00227],[77.3693,41.0375],[77.28004,41.0033],[76.99302,41.0696],[76.75681,40.95354],[76.5261,40.46114],[76.33659,40.3482],[75.96168,40.38064],[75.91361,40.2948],[75.69663,40.28642],[75.5854,40.66874],[75.22834,40.45382],[75.08243,40.43945],[74.82013,40.52197],[74.78168,40.44886],[74.85996,40.32857],[74.69875,40.34668],[74.35063,40.09742],[74.25533,40.13191],[73.97049,40.04378],[73.83006,39.76136],[73.9051,39.75073],[73.92354,39.69565],[73.94683,39.60733],[73.87018,39.47879],[73.59831,39.46425],[73.59241,39.40843],[73.5004,39.38402]]]}},{"type":"Feature","properties":{"id":"CL"},"geometry":{"type":"Polygon","coordinates":[[[-110.01673,-26.93058],[-68.49548,-57.51715],[-66.07313,-55.19618],[-67.10428,-54.93922],[-67.99952,-54.88833],[-68.60733,-54.9125],[-68.60702,-52.65781],[-68.4165,-52.33208],[-68.56979,-52.32411],[-68.80153,-52.23831],[-69.97824,-52.00845],[-71.9165,-52.00094],[-72.02791,-51.97673],[-71.91204,-51.88475],[-72.29733,-51.69804],[-72.33329,-51.58698],[-72.43492,-51.584],[-72.26257,-51.24515],[-72.31343,-50.58411],[-73.15765,-50.78337],[-73.55259,-49.92488],[-73.45156,-49.79461],[-73.09655,-49.14342],[-72.53242,-48.79736],[-72.56126,-48.53934],[-72.27662,-48.28727],[-72.50478,-47.80586],[-71.86225,-47.1568],[-71.97177,-46.9036],[-71.64802,-46.67064],[-71.67154,-46.52591],[-71.72733,-46.27863],[-71.87461,-46.16984],[-71.61163,-45.98742],[-71.78192,-45.63132],[-71.30298,-45.25676],[-72.06985,-44.81756],[-71.22333,-44.77403],[-71.13853,-44.46931],[-71.8238,-44.38767],[-71.62433,-43.63905],[-71.89144,-43.47235],[-71.74896,-43.17714],[-72.16026,-42.90111],[-72.15541,-42.15941],[-71.72699,-42.12929],[-71.94293,-40.71109],[-71.67755,-40.09908],[-71.46125,-39.58134],[-71.41319,-38.89076],[-70.89532,-38.6923],[-71.19484,-37.64631],[-71.11312,-37.10447],[-71.03141,-36.47209],[-70.37155,-36.04243],[-70.51025,-35.26243],[-69.87386,-34.13344],[-69.88099,-33.34489],[-70.55832,-31.51559],[-70.14479,-30.36595],[-69.81262,-30.22822],[-70.01174,-29.31903],[-69.80969,-29.07185],[-69.66709,-28.44055],[-69.22504,-27.95042],[-68.77586,-27.16029],[-68.43363,-27.08414],[-68.27677,-26.90626],[-68.59048,-26.49861],[-68.55949,-26.28479],[-68.38096,-26.177],[-68.57622,-25.32505],[-68.38372,-25.08636],[-68.56909,-24.69831],[-68.24569,-24.39838],[-67.32284,-24.03517],[-66.99632,-22.99839],[-67.18382,-22.81525],[-67.54284,-22.89771],[-67.85114,-22.87076],[-68.17943,-21.30184],[-68.40403,-20.94562],[-68.53957,-20.91542],[-68.55383,-20.7355],[-68.43658,-20.63824],[-68.73733,-20.45725],[-68.74273,-20.08817],[-68.57132,-20.03134],[-68.54611,-19.84651],[-68.66761,-19.72118],[-68.41218,-19.40499],[-68.61989,-19.27584],[-68.80602,-19.08355],[-68.87082,-19.06003],[-68.94987,-18.93302],[-68.92822,-18.86015],[-69.07432,-18.28259],[-69.14546,-18.16387],[-69.07496,-18.03715],[-69.28671,-17.94844],[-69.34126,-17.72753],[-69.46784,-17.60463],[-69.46846,-17.49842],[-69.66483,-17.65083],[-69.79087,-17.65563],[-69.82868,-17.72048],[-69.75305,-17.94605],[-69.81607,-18.12582],[-69.96732,-18.25992],[-70.16394,-18.31737],[-70.31267,-18.31258],[-70.378,-18.3495],[-70.59118,-18.35072],[-73.98689,-20.10822],[-110.01673,-26.93058]]]}},{"type":"Feature","properties":{"id":"CI"},"geometry":{"type":"Polygon","coordinates":[[[-8.59645,6.50277],[-8.48453,6.42874],[-8.45397,6.50686],[-8.38453,6.35887],[-8.3298,6.36381],[-8.17557,6.28222],[-8.00642,6.31684],[-7.90692,6.27728],[-7.83478,6.20309],[-7.8497,6.08932],[-7.79747,6.07696],[-7.78254,5.99037],[-7.70294,5.90625],[-7.67309,5.94337],[-7.48155,5.80974],[-7.46057,5.86193],[-7.43677,5.84687],[-7.43926,5.74787],[-7.37209,5.61173],[-7.43428,5.42355],[-7.36463,5.32944],[-7.46165,5.26256],[-7.48901,5.14118],[-7.55369,5.08667],[-7.53876,4.94294],[-7.59349,4.8909],[-7.55962,4.541],[-7.55413,4.49916],[-7.56563,4.46057],[-7.53966,4.43798],[-7.56975,4.39775],[-7.53026,4.36335],[-7.52774,3.7105],[-3.34019,4.17519],[-3.10348,5.08596],[-3.11073,5.12675],[-3.063,5.13665],[-2.96554,5.10397],[-2.95261,5.12477],[-2.75502,5.10657],[-2.73074,5.1364],[-2.78554,5.28652],[-2.77625,5.34621],[-2.72737,5.34789],[-2.76614,5.60963],[-2.85378,5.65156],[-2.93132,5.62137],[-2.96671,5.6415],[-2.95323,5.71865],[-3.01896,5.71697],[-3.25999,6.62521],[-3.21954,6.74407],[-3.23327,6.81744],[-3.08818,7.05251],[-3.04424,7.08556],[-2.95438,7.23737],[-2.97575,7.27018],[-2.92339,7.60847],[-2.80494,7.86559],[-2.78395,7.94974],[-2.74819,7.92613],[-2.67787,8.02055],[-2.59723,8.03033],[-2.62901,8.11495],[-2.49037,8.20872],[-2.58899,8.78668],[-2.66357,9.01771],[-2.80323,9.05565],[-2.66315,9.24817],[-2.72186,9.31695],[-2.68802,9.49343],[-2.76494,9.40778],[-2.93012,9.57403],[-3.00765,9.74019],[-3.16609,9.85147],[-3.19306,9.93781],[-3.27228,9.84981],[-3.31779,9.91125],[-3.69703,9.94279],[-4.24951,9.75893],[-4.31392,9.60062],[-4.6426,9.70696],[-4.96621,9.89132],[-4.96453,9.99923],[-5.12465,10.29788],[-5.39602,10.2929],[-5.51058,10.43177],[-5.65135,10.46767],[-5.78124,10.43952],[-5.99478,10.19694],[-6.18851,10.24244],[-6.1731,10.46983],[-6.21757,10.53979],[-6.24795,10.74248],[-6.325,10.68624],[-6.40502,10.71458],[-6.42847,10.5694],[-6.52974,10.59104],[-6.64913,10.67275],[-6.66423,10.34092],[-6.93921,10.35291],[-7.01186,10.25111],[-6.97444,10.21644],[-7.00966,10.15794],[-7.0603,10.14711],[-7.13331,10.24877],[-7.3707,10.24677],[-7.44555,10.44602],[-7.52261,10.4655],[-7.54462,10.40921],[-7.63048,10.46334],[-7.92107,10.15577],[-7.97971,10.17117],[-8.01225,10.1021],[-8.11921,10.04577],[-8.15652,9.94288],[-8.09434,9.86936],[-8.14657,9.55062],[-8.03463,9.39604],[-7.85056,9.41812],[-7.90777,9.20456],[-7.73862,9.08422],[-7.93075,9.00648],[-7.95503,8.81146],[-7.69882,8.66148],[-7.65653,8.36873],[-7.8432,8.51278],[-8.22991,8.48438],[-8.2411,8.24196],[-8.062,8.16071],[-7.98675,8.20134],[-7.99919,8.11023],[-7.94695,8.00925],[-8.06449,8.04989],[-8.13414,7.87991],[-8.09931,7.78626],[-8.21374,7.54466],[-8.4003,7.6285],[-8.47114,7.55676],[-8.42634,7.5308],[-8.36351,7.25341],[-8.28266,7.17175],[-8.29931,7.13359],[-8.28746,6.9922],[-8.32162,6.96783],[-8.31235,6.90921],[-8.33261,6.89081],[-8.31819,6.8124],[-8.45449,6.64909],[-8.5338,6.59759],[-8.59645,6.50277]]]}},{"type":"Feature","properties":{"id":"CM"},"geometry":{"type":"Polygon","coordinates":[[[8.34397,4.30689],[8.6479,4.06346],[9.22018,3.72052],[9.6225,2.44901],[9.82123,2.35097],[9.83754,2.32428],[9.83238,2.29079],[9.84716,2.24676],[9.87859,2.23154],[9.90749,2.20049],[10.01589,2.16561],[11.1494,2.16722],[11.19764,2.19192],[11.35831,2.17202],[11.38046,2.3005],[13.28534,2.25716],[13.29688,2.17031],[14.61145,2.17866],[15.00996,1.98887],[15.20456,2.04096],[15.34776,1.91264],[15.48942,1.98265],[16.02959,1.76483],[16.02647,1.65591],[16.14634,1.70259],[16.0596,1.98297],[16.08563,2.19733],[16.15568,2.18955],[16.19357,2.21537],[16.08252,2.45708],[16.08278,2.5852],[16.06553,2.64341],[16.08346,2.79816],[15.95077,3.02052],[15.79353,3.10212],[15.24902,3.70941],[15.07686,4.01805],[15.18035,4.05383],[15.10843,4.12848],[15.08609,4.30282],[15.00904,4.42343],[14.83428,4.50643],[14.73383,4.6135],[14.66949,5.19865],[14.57083,5.23979],[14.52724,5.28319],[14.62531,5.51411],[14.58951,5.59777],[14.62375,5.70466],[14.60974,5.91838],[14.5598,5.90044],[14.55302,5.9036],[14.50796,5.89352],[14.42917,6.00508],[14.43073,6.08867],[14.56149,6.18928],[14.74206,6.26356],[14.96749,6.75905],[15.04717,6.77085],[15.21846,7.11349],[15.23397,7.25135],[15.49743,7.52179],[15.56964,7.58936],[15.59272,7.7696],[15.50743,7.79302],[15.39493,8.09526],[15.20426,8.50892],[15.10911,8.65892],[14.83566,8.80557],[14.35707,9.19611],[14.37698,9.26986],[14.10318,9.51983],[14.03795,9.60785],[13.97544,9.6365],[14.02198,9.72885],[14.13476,9.81969],[14.20789,10.0008],[14.46727,9.99995],[14.80201,9.93368],[14.95722,9.97926],[15.0753,9.94416],[15.14043,9.99246],[15.24618,9.99246],[15.42171,9.93097],[15.68761,9.99344],[15.52385,10.09934],[15.30601,10.31238],[15.2794,10.40087],[15.22911,10.49203],[15.14533,10.53169],[15.16113,10.6164],[15.14087,10.67191],[15.06311,10.82049],[15.09967,10.88017],[15.04957,11.02347],[15.10021,11.04101],[15.0585,11.40481],[15.0856,11.46185],[15.1225,11.5103],[15.14413,11.56042],[15.1086,11.58161],[15.06311,11.713],[15.12104,11.78939],[15.04808,11.8731],[15.06174,11.9347],[15.04423,11.97509],[15.08345,11.98097],[15.08345,12.00465],[15.05762,12.00624],[15.03822,12.03537],[15.05659,12.03655],[15.06122,12.05871],[15.03461,12.08288],[15.03599,12.10772],[15.01779,12.11855],[15.00146,12.1223],[14.98535,12.10571],[14.9608,12.09513],[14.88432,12.16478],[14.91145,12.18609],[14.89248,12.20212],[14.90321,12.22535],[14.89239,12.25161],[14.90552,12.27517],[14.89411,12.31132],[14.90801,12.33245],[14.90913,12.38577],[14.87754,12.43691],[14.87832,12.46364],[14.85342,12.45283],[14.85257,12.56151],[14.83025,12.63983],[14.7742,12.63372],[14.75326,12.68522],[14.71326,12.66161],[14.71584,12.73202],[14.67301,12.71963],[14.62391,12.74826],[14.62211,12.77296],[14.58683,12.74651],[14.57722,12.76517],[14.55559,12.77581],[14.54624,12.79832],[14.56538,12.83573],[14.5537,12.86544],[14.56443,12.91125],[14.55006,12.93697],[14.53521,12.9463],[14.46881,13.08259],[14.08251,13.0797],[14.20317,12.52624],[14.18154,12.46163],[14.19013,12.43816],[14.17579,12.42526],[14.20077,12.38519],[14.2339,12.3649],[14.2569,12.36892],[14.48118,12.35199],[14.53834,12.2865],[14.54778,12.23852],[14.66125,12.17896],[14.6422,11.91186],[14.60735,11.86768],[14.61612,11.7798],[14.55207,11.72001],[14.6434,11.65257],[14.64717,11.57623],[14.61353,11.5071],[14.17821,11.23831],[14.16223,11.24786],[14.15622,11.24213],[14.14352,11.25173],[13.9789,11.31427],[13.93873,11.22235],[13.91418,11.19002],[13.87418,11.14269],[13.88088,11.13545],[13.78217,11.00186],[13.73548,11.00641],[13.73085,10.98147],[13.70853,10.95636],[13.72879,10.92282],[13.71814,10.87713],[13.64398,10.80177],[13.63334,10.74696],[13.54964,10.61236],[13.57824,10.53642],[13.53446,10.46873],[13.53361,10.41657],[13.47061,10.16457],[13.43627,10.13196],[13.41739,10.12148],[13.34083,10.11641],[13.25025,10.03647],[13.25323,10.00127],[13.286,9.9822],[13.27697,9.93926],[13.24007,9.91474],[13.25025,9.86042],[13.29941,9.8296],[13.26118,9.77385],[13.22642,9.57266],[13.02385,9.49334],[12.85628,9.36698],[12.91958,9.33905],[12.90138,9.11023],[12.82327,8.96969],[12.79,8.75361],[12.71701,8.7595],[12.68474,8.65111],[12.49351,8.64127],[12.44356,8.61768],[12.4489,8.52536],[12.32768,8.4272],[12.25421,8.44418],[12.24782,8.17904],[12.19271,8.10826],[12.20909,7.97553],[11.99908,7.67302],[12.01844,7.52981],[11.93205,7.47812],[11.84864,7.26098],[11.87396,7.09398],[11.63117,6.9905],[11.55521,6.86047],[11.57755,6.74059],[11.51499,6.60892],[11.42749,6.59606],[11.42578,6.53211],[11.31814,6.50755],[11.23506,6.53176],[11.15936,6.50362],[11.09495,6.51717],[11.09644,6.68437],[10.96572,6.69086],[10.8179,6.83377],[10.83646,6.92216],[10.60789,7.06885],[10.59746,7.14719],[10.57214,7.16345],[10.53639,6.93432],[10.21466,6.88996],[10.15135,7.03781],[9.86314,6.77756],[9.78486,6.79059],[9.70315,6.51249],[9.63294,6.5171],[9.52634,6.43778],[9.46678,6.45655],[9.40189,6.3286],[8.84209,5.82562],[8.88156,5.78857],[8.83687,5.68483],[8.92029,5.58403],[8.78027,5.1243],[8.60302,4.87353],[8.34397,4.30689]]]}},{"type":"Feature","properties":{"id":"CD"},"geometry":{"type":"Polygon","coordinates":[[[11.95767,-5.94705],[12.42245,-6.07585],[13.04371,-5.87078],[16.55507,-5.85631],[16.96282,-7.21787],[17.5828,-8.13784],[18.33635,-8.00126],[19.33698,-7.99743],[19.5469,-7.00195],[20.30218,-6.98955],[20.31846,-6.91953],[20.61689,-6.90876],[20.56263,-7.28566],[21.78726,-7.28414],[21.84856,-9.59871],[22.19039,-9.94628],[22.32604,-10.76291],[22.17954,-10.85884],[22.25951,-11.24911],[22.54205,-11.05784],[23.16602,-11.10577],[23.45631,-10.946],[23.86868,-11.02856],[24.00027,-10.89356],[24.34528,-11.06816],[24.42612,-11.44975],[25.34069,-11.19707],[25.33058,-11.65767],[26.01777,-11.91488],[26.87736,-11.99297],[27.04351,-11.61312],[27.22541,-11.60323],[27.21914,-11.75073],[27.6234,-12.26955],[27.76296,-12.29287],[27.77137,-12.27224],[27.81635,-12.25395],[27.8124,-12.21956],[27.92827,-12.24372],[27.94921,-12.35073],[28.34043,-12.43322],[28.53149,-12.63464],[28.45355,-12.71704],[28.57131,-12.9005],[28.64067,-12.78903],[28.81885,-12.98682],[29.00356,-13.42001],[29.60918,-13.20886],[29.63527,-13.43403],[29.81551,-13.44683],[29.8139,-12.14898],[29.48404,-12.23604],[29.52575,-12.45769],[29.0736,-12.36314],[28.73319,-11.98357],[28.49063,-11.87507],[28.37241,-11.57848],[28.65032,-10.65133],[28.62144,-9.93909],[28.68532,-9.78],[28.56208,-9.49122],[28.51627,-9.44726],[28.52636,-9.35379],[28.36562,-9.30091],[28.38526,-9.23393],[28.96339,-8.4632],[30.41702,-8.2774],[30.80326,-8.29201],[29.48706,-5.95084],[29.43398,-4.41764],[29.21463,-3.3514],[29.25633,-3.05471],[29.17258,-2.99385],[29.16037,-2.95457],[29.09797,-2.91935],[29.09119,-2.87871],[29.0505,-2.81774],[29.00404,-2.81978],[29.00167,-2.78523],[29.04064,-2.74317],[29.00454,-2.70519],[28.94346,-2.69124],[28.89793,-2.66111],[28.89537,-2.65366],[28.9058,-2.6474],[28.90226,-2.62385],[28.89762,-2.57984],[28.89228,-2.55896],[28.87752,-2.55077],[28.86972,-2.53867],[28.86193,-2.53185],[28.86209,-2.5231],[28.87497,-2.50887],[28.88846,-2.50493],[28.89342,-2.49017],[28.89277,-2.47462],[28.86846,-2.44866],[28.89601,-2.37321],[28.95642,-2.37321],[29.00051,-2.29001],[29.105,-2.27043],[29.17562,-2.12278],[29.11847,-1.90576],[29.24458,-1.69663],[29.24323,-1.66826],[29.25719,-1.65801],[29.2692,-1.62841],[29.35315,-1.53609],[29.36322,-1.50887],[29.45038,-1.5054],[29.53062,-1.40499],[29.59061,-1.39016],[29.58388,-0.89821],[29.63006,-0.8997],[29.62708,-0.71055],[29.67176,-0.55714],[29.67474,-0.47969],[29.65091,-0.46777],[29.72687,-0.08051],[29.7224,0.07291],[29.77454,0.16675],[29.81922,0.16824],[29.87284,0.39166],[29.97413,0.52124],[29.95477,0.64486],[29.98307,0.84295],[30.1484,0.89805],[30.22139,0.99635],[30.24671,1.14974],[30.48503,1.21675],[31.30127,2.11006],[31.30588,2.1571],[31.24803,2.1999],[31.2028,2.22082],[31.20301,2.29072],[31.16666,2.27554],[31.14126,2.28094],[31.13259,2.26242],[31.11165,2.27134],[31.07019,2.30273],[31.081,2.30243],[31.0856,2.30877],[31.07727,2.31653],[31.06766,2.33472],[31.07379,2.34432],[31.02848,2.37802],[31.02659,2.38253],[30.98951,2.40324],[30.95063,2.39526],[30.9417,2.37459],[30.94754,2.36087],[30.93698,2.33695],[30.91325,2.34115],[30.91059,2.3318],[30.89188,2.33973],[30.88209,2.34068],[30.87269,2.3688],[30.84978,2.38205],[30.83274,2.42244],[30.77733,2.43659],[30.75073,2.4221],[30.74652,2.49045],[30.75612,2.5863],[30.8857,2.83923],[30.8574,2.9508],[30.76738,3.04872],[30.79313,3.06552],[30.83059,3.22869],[30.84081,3.23846],[30.83596,3.25166],[30.84033,3.26794],[30.86462,3.27797],[30.89681,3.35209],[30.93689,3.40795],[30.94081,3.50847],[30.89681,3.50142],[30.86977,3.47726],[30.84849,3.48729],[30.85997,3.5743],[30.80463,3.5939],[30.77609,3.68333],[30.74712,3.64362],[30.56277,3.62703],[30.57378,3.74567],[30.55396,3.84451],[30.47691,3.83353],[30.2869,3.96427],[30.22374,3.93896],[30.1621,4.10586],[30.06964,4.13221],[29.79666,4.37809],[29.82087,4.56246],[29.49726,4.7007],[29.43341,4.50101],[29.22207,4.34297],[29.03054,4.48784],[28.8126,4.48784],[28.6651,4.42638],[28.20719,4.35614],[27.79551,4.59976],[27.76469,4.79284],[27.65462,4.89375],[27.56656,4.89375],[27.44848,5.01587],[27.43801,5.07623],[27.09575,5.22305],[26.93064,5.13535],[26.85579,5.03887],[26.74572,5.10685],[26.48595,5.04984],[26.13371,5.25594],[25.86073,5.19455],[25.53271,5.37431],[25.34558,5.29101],[25.31256,5.03668],[24.71816,4.90509],[24.46719,5.0915],[23.38847,4.60013],[22.94817,4.82392],[22.89094,4.79321],[22.84691,4.69887],[22.78526,4.71423],[22.6928,4.47285],[22.60915,4.48821],[22.5431,4.22041],[22.45504,4.13039],[22.24937,4.11701],[22.10721,4.20723],[21.64203,4.31491],[21.5586,4.24849],[21.25744,4.33676],[21.21341,4.29285],[21.11214,4.33895],[21.08793,4.39603],[20.90383,4.44877],[20.58494,4.41487],[20.34736,4.72151],[20.02395,4.96474],[19.89366,4.99894],[19.8571,5.06375],[19.74895,5.12001],[19.66278,5.14428],[19.41404,5.12821],[19.35962,5.07384],[19.24169,5.01587],[19.20289,4.94695],[19.09715,4.92883],[19.02917,4.79729],[18.93768,4.68575],[18.83005,4.58309],[18.80876,4.49411],[18.78301,4.42651],[18.71829,4.36866],[18.62525,4.35514],[18.60088,4.36968],[18.54423,4.32381],[18.57341,4.24918],[18.63109,4.10708],[18.65444,3.99852],[18.63315,3.94029],[18.59916,3.89439],[18.57753,3.77655],[18.59504,3.65596],[18.62749,3.46784],[18.64448,3.21867],[18.61839,3.14188],[18.54286,3.07709],[18.32931,2.58743],[18.23352,2.51094],[18.22082,2.42828],[18.10683,2.26876],[18.06426,1.86923],[18.07491,1.54854],[17.9774,1.3608],[17.92453,1.14627],[17.84419,1.01412],[17.87647,0.79442],[17.86989,0.58873],[17.95255,0.48128],[17.93877,0.32424],[17.81204,0.23884],[17.66051,-0.26535],[17.75459,-0.50467],[17.32438,-0.99265],[16.97999,-1.12762],[16.70724,-1.45815],[16.50336,-1.8795],[16.17565,-2.17579],[16.23641,-2.59428],[16.17977,-2.87995],[16.1755,-3.25014],[16.21407,-3.2969],[15.89448,-3.9513],[15.53081,-4.042],[15.48121,-4.22062],[15.41785,-4.28381],[15.32693,-4.27282],[15.25411,-4.31121],[15.1978,-4.32388],[14.83101,-4.80838],[14.67948,-4.92093],[14.5059,-4.84956],[14.41474,-4.89376],[14.37366,-4.56125],[14.47284,-4.42941],[14.3957,-4.36623],[14.40672,-4.28381],[13.9108,-4.50906],[13.81162,-4.41842],[13.71794,-4.44864],[13.70417,-4.72601],[13.50305,-4.77818],[13.41764,-4.89897],[13.11182,-4.5942],[13.10153,-4.68319],[13.09656,-4.69037],[12.85606,-4.74734],[12.8013,-4.84861],[12.73941,-4.89325],[12.71238,-4.95474],[12.62723,-4.94969],[12.60251,-5.01715],[12.45557,-5.08427],[12.49815,-5.14058],[12.51589,-5.1332],[12.53586,-5.14658],[12.53599,-5.1618],[12.52301,-5.17481],[12.52318,-5.74353],[12.26557,-5.74031],[12.20376,-5.76338],[11.95767,-5.94705]]]}},{"type":"Feature","properties":{"id":"CG"},"geometry":{"type":"Polygon","coordinates":[[[10.75913,-4.39519],[11.50888,-5.33417],[12.01335,-5.03191],[12.08805,-4.95978],[12.16634,-4.89507],[12.19808,-4.79456],[12.32957,-4.79011],[12.40964,-4.60609],[12.64835,-4.55937],[12.76844,-4.38709],[12.84387,-4.4111],[13.10153,-4.68319],[13.11182,-4.5942],[13.41764,-4.89897],[13.50305,-4.77818],[13.70417,-4.72601],[13.71794,-4.44864],[13.81162,-4.41842],[13.9108,-4.50906],[14.40672,-4.28381],[14.3957,-4.36623],[14.47284,-4.42941],[14.37366,-4.56125],[14.41474,-4.89376],[14.5059,-4.84956],[14.67948,-4.92093],[14.83101,-4.80838],[15.1978,-4.32388],[15.25411,-4.31121],[15.32693,-4.27282],[15.41785,-4.28381],[15.48121,-4.22062],[15.53081,-4.042],[15.89448,-3.9513],[16.21407,-3.2969],[16.1755,-3.25014],[16.17977,-2.87995],[16.23641,-2.59428],[16.17565,-2.17579],[16.50336,-1.8795],[16.70724,-1.45815],[16.97999,-1.12762],[17.32438,-0.99265],[17.75459,-0.50467],[17.66051,-0.26535],[17.81204,0.23884],[17.93877,0.32424],[17.95255,0.48128],[17.86989,0.58873],[17.87647,0.79442],[17.84419,1.01412],[17.92453,1.14627],[17.9774,1.3608],[18.07491,1.54854],[18.06426,1.86923],[18.10683,2.26876],[18.22082,2.42828],[18.23352,2.51094],[18.32931,2.58743],[18.54286,3.07709],[18.61839,3.14188],[18.64448,3.21867],[18.62749,3.46784],[18.58711,3.49423],[18.49245,3.63924],[18.39558,3.58212],[18.2723,3.57992],[18.24148,3.50302],[18.17323,3.47665],[18.14902,3.54476],[18.05656,3.56893],[17.85842,3.53378],[17.83421,3.61068],[17.60966,3.63705],[17.46876,3.70515],[17.35649,3.63045],[17.01746,3.55136],[16.68283,3.54257],[16.57598,3.47999],[16.46701,2.92512],[16.50126,2.84739],[16.19357,2.21537],[16.15568,2.18955],[16.08563,2.19733],[16.0596,1.98297],[16.14634,1.70259],[16.02647,1.65591],[16.02959,1.76483],[15.48942,1.98265],[15.34776,1.91264],[15.20456,2.04096],[15.00996,1.98887],[14.61145,2.17866],[13.29688,2.17031],[13.13461,1.57238],[13.25447,1.32339],[13.15519,1.23368],[13.89582,1.4261],[14.25186,1.39842],[14.48179,0.9152],[14.31346,0.55617],[14.10909,0.58563],[13.88648,0.26652],[13.90632,-0.2287],[14.06862,-0.20826],[14.2165,-0.38261],[14.41887,-0.44799],[14.52569,-0.57818],[14.41838,-1.89412],[14.25932,-1.97624],[14.23518,-2.15671],[14.16202,-2.23916],[14.23829,-2.33715],[14.10442,-2.49268],[13.85846,-2.46935],[13.92073,-2.35581],[13.75884,-2.09293],[13.53996,-2.44405],[13.02759,-2.33098],[12.81692,-1.9092],[12.61312,-1.8129],[12.44656,-1.92025],[12.47925,-2.32626],[12.04895,-2.41704],[11.96866,-2.33559],[11.74605,-2.39936],[11.57637,-2.33379],[11.64487,-2.61865],[11.5359,-2.85654],[11.64798,-2.81146],[11.80365,-3.00424],[11.70558,-3.0773],[11.70227,-3.17465],[11.96554,-3.30267],[11.8318,-3.5812],[11.92719,-3.62768],[11.87083,-3.71571],[11.68608,-3.68942],[11.57949,-3.52798],[11.48764,-3.51089],[11.22301,-3.69888],[11.12647,-3.94169],[10.75913,-4.39519]]]}},{"type":"Feature","properties":{"id":"CO"},"geometry":{"type":"Polygon","coordinates":[[[-82.20985,12.16504],[-78.79327,9.93766],[-77.58292,9.22278],[-77.32389,8.81247],[-77.45064,8.49991],[-77.17257,7.97422],[-77.57185,7.51147],[-77.72514,7.72348],[-77.72157,7.47612],[-77.81426,7.48319],[-77.89178,7.22681],[-78.06168,7.07793],[-82.12561,4.00341],[-78.87137,1.47457],[-78.42749,1.15389],[-77.96722,0.83218],[-77.85677,0.80197],[-77.7148,0.85003],[-77.68613,0.83029],[-77.66416,0.81604],[-77.69565,0.7479],[-77.56201,0.65324],[-77.47447,0.66139],[-77.52001,0.40782],[-77.12471,0.37284],[-76.9079,0.24676],[-76.60251,0.23225],[-76.53368,0.25817],[-76.4094,0.24015],[-76.41215,0.38228],[-76.26091,0.42228],[-76.10504,0.31499],[-75.85338,0.13029],[-75.41015,-0.08892],[-75.25764,-0.11943],[-75.18513,-0.0308],[-74.42701,-0.50218],[-74.26675,-0.97229],[-73.65312,-1.26222],[-73.423,-1.77057],[-73.21666,-1.74827],[-73.15521,-2.24852],[-72.92587,-2.44514],[-72.02362,-2.3161],[-71.74106,-2.14869],[-71.28376,-2.37682],[-70.94377,-2.23142],[-70.04609,-2.73906],[-70.71693,-3.79762],[-70.57617,-3.81698],[-70.49789,-3.88172],[-70.36708,-3.79711],[-70.28228,-3.82383],[-69.94741,-4.22816],[-69.93567,-4.21946],[-69.93326,-4.21973],[-69.43395,-1.42219],[-69.4215,-1.01853],[-69.59796,-0.75136],[-69.603,-0.51947],[-70.03658,-0.19681],[-70.04162,0.55437],[-69.47696,0.71065],[-69.20976,0.57958],[-69.14422,0.84172],[-69.26017,1.06856],[-69.84283,1.07848],[-69.84352,1.69919],[-69.54002,1.77023],[-69.37934,1.73008],[-68.18128,1.72881],[-68.26699,1.83463],[-68.18632,2.00091],[-67.9292,1.82455],[-67.40488,2.22258],[-67.299,1.87494],[-67.15784,1.80439],[-67.08222,1.17441],[-66.85051,1.22968],[-67.06466,1.91709],[-67.25349,2.41902],[-67.7053,2.81068],[-67.85862,2.79173],[-67.85911,2.85011],[-67.30945,3.38393],[-67.50067,3.75812],[-67.61467,3.75086],[-67.79285,4.22247],[-67.85358,4.53249],[-67.84984,5.30737],[-67.59141,5.5369],[-67.63914,5.64963],[-67.58558,5.84537],[-67.4176,6.00024],[-67.49107,6.13987],[-67.44987,6.1938],[-67.60654,6.2891],[-69.42672,6.10641],[-70.10716,6.96516],[-70.68251,7.0962],[-70.75118,7.09297],[-70.7596,7.09799],[-70.78469,7.08219],[-70.85374,7.07414],[-71.02128,6.97942],[-71.37234,7.01588],[-71.39173,7.03632],[-71.42924,7.037],[-71.43516,7.02699],[-71.45662,7.01375],[-71.62536,7.05387],[-71.82441,7.04314],[-71.94053,7.0169],[-72.04895,7.03837],[-72.19437,7.37034],[-72.43132,7.40034],[-72.47415,7.48928],[-72.45321,7.57232],[-72.47827,7.65604],[-72.46763,7.79518],[-72.45028,7.81874],[-72.44539,7.85845],[-72.4617,7.90376],[-72.45732,7.91031],[-72.47042,7.92306],[-72.48435,7.93139],[-72.48801,7.94329],[-72.47277,7.96144],[-72.44689,7.96828],[-72.43577,7.99132],[-72.4205,7.99089],[-72.41415,8.03432],[-72.39137,8.03534],[-72.34934,8.00636],[-72.36987,8.19976],[-72.4042,8.36513],[-72.65415,8.61456],[-72.77415,9.10165],[-72.94052,9.10663],[-73.00792,9.28612],[-73.36905,9.16636],[-72.9499,9.85183],[-72.88002,10.44309],[-72.4767,11.1117],[-72.24983,11.14138],[-71.97212,11.64719],[-71.3275,11.85],[-70.92579,11.96275],[-71.19849,12.65801],[-78.64081,16.14942],[-80.72975,15.87257],[-82.20985,12.16504]]]}},{"type":"Feature","properties":{"id":"CR"},"geometry":{"type":"Polygon","coordinates":[[[-87.41779,5.02401],[-82.94503,7.93865],[-82.90317,8.04061],[-82.89081,8.05204],[-82.88592,8.10851],[-82.9388,8.26634],[-83.05209,8.33394],[-82.93056,8.43465],[-82.9145,8.42958],[-82.8679,8.44042],[-82.8382,8.48117],[-82.83339,8.52364],[-82.83854,8.5323],[-82.83975,8.54755],[-82.82232,8.56667],[-82.83459,8.61522],[-82.87412,8.69735],[-82.91931,8.74749],[-82.91484,8.77023],[-82.86652,8.80653],[-82.88652,8.82994],[-82.85399,8.85708],[-82.76782,8.88541],[-82.70542,8.95697],[-82.93516,9.07687],[-82.93516,9.46741],[-82.84871,9.4973],[-82.88523,9.56977],[-82.84953,9.6219],[-82.74456,9.58026],[-82.66667,9.49746],[-82.61345,9.49881],[-82.58903,9.56012],[-82.5547,9.53989],[-82.56507,9.57279],[-82.51044,9.65379],[-83.54024,10.96805],[-83.68276,11.01562],[-83.66397,10.80835],[-83.90018,10.71728],[-84.17381,10.79486],[-84.22187,10.87241],[-84.68433,11.08306],[-84.91024,10.9449],[-85.60949,11.21958],[-85.70132,11.0648],[-86.03263,11.09328],[-87.41779,5.02401]]]}},{"type":"Feature","properties":{"id":"CZ"},"geometry":{"type":"Polygon","coordinates":[[[12.09287,50.25032],[12.19335,50.19997],[12.21484,50.16399],[12.1917,50.13434],[12.2073,50.10315],[12.23709,50.10213],[12.27433,50.0771],[12.26111,50.06331],[12.31824,50.05129],[12.49908,49.97305],[12.47264,49.94222],[12.55197,49.92094],[12.48256,49.83575],[12.46603,49.78882],[12.40489,49.76321],[12.4462,49.70233],[12.52553,49.68415],[12.52321,49.64512],[12.53544,49.61888],[12.56466,49.61438],[12.59779,49.53066],[12.64782,49.52565],[12.64121,49.47628],[12.669,49.42935],[12.70886,49.42437],[12.75854,49.3989],[12.78001,49.3448],[12.84799,49.34184],[12.88794,49.3306],[12.88138,49.3514],[12.95185,49.3419],[13.02995,49.30475],[13.02952,49.2706],[13.05883,49.26259],[13.17665,49.16713],[13.17019,49.14339],[13.20405,49.12303],[13.23689,49.11412],[13.28242,49.1228],[13.39479,49.04812],[13.40802,48.98851],[13.50221,48.93752],[13.50552,48.97441],[13.58319,48.96899],[13.61624,48.9462],[13.67739,48.87886],[13.73854,48.88538],[13.76994,48.83537],[13.78977,48.83319],[13.8096,48.77877],[13.84023,48.76988],[13.98267,48.71022],[14.06151,48.66873],[14.00997,48.63937],[14.04516,48.62411],[14.09348,48.59466],[14.20691,48.5898],[14.27102,48.58097],[14.33732,48.55678],[14.44324,48.59244],[14.44483,48.64337],[14.4668,48.64646],[14.54675,48.61438],[14.56237,48.60374],[14.60808,48.62881],[14.66691,48.58029],[14.72099,48.60062],[14.72756,48.69502],[14.80584,48.73489],[14.80124,48.74719],[14.80999,48.77949],[14.81545,48.7874],[14.94773,48.76268],[14.95641,48.75915],[14.9758,48.76857],[14.98112,48.77524],[14.9782,48.7766],[14.98032,48.77959],[14.95072,48.79101],[14.98917,48.90082],[14.97612,48.96983],[14.99878,49.01444],[15.04088,49.01237],[15.15418,48.99424],[15.16937,48.96342],[15.16018,48.94229],[15.26177,48.95766],[15.28305,48.98831],[15.34823,48.98444],[15.48027,48.94481],[15.51357,48.91549],[15.61622,48.89541],[15.6921,48.85973],[15.75341,48.8516],[15.78048,48.87487],[15.83842,48.86815],[15.9851,48.78645],[16.06295,48.75477],[16.37345,48.729],[16.40915,48.74576],[16.46134,48.80865],[16.67008,48.77699],[16.68518,48.7281],[16.71883,48.73806],[16.79779,48.70998],[16.90354,48.71541],[16.93955,48.60371],[17.00215,48.70887],[17.10957,48.8313],[17.15433,48.84605],[17.19355,48.87602],[17.29857,48.84929],[17.3853,48.80936],[17.45671,48.85004],[17.50952,48.81973],[17.5166,48.82518],[17.51991,48.81264],[17.5295,48.81117],[17.7094,48.86721],[17.73126,48.87885],[17.78158,48.92529],[17.88316,48.92681],[17.91329,48.99407],[17.91814,49.01784],[18.05572,49.03111],[18.07834,49.0431],[18.09298,49.05828],[18.1104,49.08624],[18.15022,49.24518],[18.18456,49.28909],[18.37077,49.32534],[18.4139,49.36517],[18.40969,49.39911],[18.45145,49.39349],[18.54848,49.47059],[18.53063,49.49022],[18.57183,49.51162],[18.6144,49.49824],[18.67791,49.50787],[18.71967,49.4999],[18.7443,49.48903],[18.84521,49.51672],[18.85854,49.54559],[18.81906,49.6176],[18.80479,49.6815],[18.71907,49.68351],[18.70958,49.70422],[18.66675,49.70849],[18.62667,49.72203],[18.62943,49.74603],[18.62645,49.75002],[18.61368,49.75426],[18.61401,49.76197],[18.56869,49.83399],[18.58998,49.84611],[18.5847,49.85229],[18.60568,49.86023],[18.57045,49.87849],[18.57697,49.91565],[18.54299,49.92537],[18.54495,49.9079],[18.53423,49.89906],[18.49599,49.9053],[18.41604,49.93498],[18.33562,49.94747],[18.33261,49.92456],[18.31953,49.91503],[18.28979,49.92906],[18.27953,49.93967],[18.27107,49.96779],[18.21752,49.97309],[18.20241,49.99958],[18.10924,49.99813],[18.07898,50.04535],[18.03212,50.06574],[18.00212,50.04865],[18.04585,50.03311],[18.04667,50.00768],[18.00191,50.01723],[17.87252,49.97374],[17.85003,49.98682],[17.84196,49.98743],[17.77669,50.02253],[17.77107,50.0492],[17.73262,50.09668],[17.6888,50.12037],[17.67635,50.10321],[17.64975,50.111],[17.64155,50.12756],[17.58859,50.16326],[17.70528,50.18812],[17.75493,50.21426],[17.76609,50.23545],[17.72176,50.25665],[17.74648,50.29966],[17.69292,50.32859],[17.67764,50.28977],[17.58889,50.27837],[17.42929,50.27145],[17.37307,50.28325],[17.35196,50.26289],[17.33831,50.2833],[17.34981,50.32853],[17.28372,50.31707],[17.19991,50.3654],[17.19579,50.38817],[17.14498,50.38117],[17.1224,50.39494],[16.97748,50.41956],[16.90916,50.44865],[16.88169,50.44395],[16.85933,50.41093],[16.90877,50.38642],[16.94448,50.31281],[16.99803,50.30316],[17.02138,50.27772],[16.99803,50.25753],[17.02825,50.23118],[17.00353,50.21449],[16.98018,50.24172],[16.8456,50.20834],[16.7014,50.09659],[16.63137,50.1142],[16.56021,50.16441],[16.56373,50.1797],[16.55699,50.21379],[16.54382,50.23142],[16.48361,50.27162],[16.48,50.27647],[16.46713,50.2855],[16.46241,50.29668],[16.42674,50.32509],[16.39379,50.3207],[16.3622,50.34875],[16.36495,50.37679],[16.30289,50.38292],[16.28276,50.36648],[16.23684,50.41431],[16.21585,50.40627],[16.19526,50.43291],[16.2029,50.44722],[16.23229,50.44323],[16.23714,50.46075],[16.2556,50.46766],[16.26448,50.47984],[16.32426,50.50207],[16.36053,50.49606],[16.39391,50.54321],[16.41099,50.54818],[16.40868,50.56372],[16.44597,50.58041],[16.33611,50.66579],[16.23174,50.67101],[16.21135,50.63013],[16.10265,50.66405],[16.02437,50.60046],[15.98317,50.61528],[16.0175,50.63009],[15.97884,50.69743],[15.87331,50.67188],[15.81683,50.75666],[15.73186,50.73885],[15.43798,50.80833],[15.3803,50.77187],[15.36656,50.83956],[15.2773,50.8907],[15.27039,50.93441],[15.28807,50.9635],[15.2361,50.99886],[15.1743,50.9833],[15.18276,51.01656],[15.148,51.01413],[15.11937,50.99021],[15.10152,51.01095],[15.06019,51.02304],[15.04131,51.01029],[15.02433,51.0242],[14.96419,50.99108],[15.01633,50.97837],[14.99659,50.90054],[15.00054,50.86144],[14.86754,50.87745],[14.86269,50.8694],[14.82803,50.86966],[14.79139,50.81438],[14.70661,50.84096],[14.61842,50.85792],[14.64091,50.90054],[14.65259,50.90513],[14.64802,50.93241],[14.58024,50.91443],[14.55757,50.92167],[14.58151,50.94279],[14.59452,50.96453],[14.59967,50.97983],[14.59907,50.98539],[14.58096,50.99398],[14.57864,51.00022],[14.56272,51.00778],[14.53438,51.00374],[14.53321,51.01679],[14.49873,51.02242],[14.50809,51.0427],[14.49991,51.04692],[14.49154,51.04382],[14.49202,51.02286],[14.45827,51.03712],[14.41835,51.01872],[14.30098,51.05515],[14.2593,50.98769],[14.2769,50.98293],[14.28177,50.9787],[14.29426,50.97831],[14.32353,50.98556],[14.32793,50.97379],[14.30251,50.96606],[14.31422,50.95243],[14.39848,50.93866],[14.38691,50.89907],[14.30098,50.88448],[14.27123,50.89386],[14.24314,50.88761],[14.22331,50.86049],[14.02982,50.80662],[13.98864,50.8177],[13.89375,50.78097],[13.89444,50.74142],[13.82942,50.7251],[13.76316,50.73487],[13.70204,50.71771],[13.65977,50.73096],[13.52474,50.70394],[13.53748,50.67654],[13.5226,50.64721],[13.50339,50.63372],[13.46413,50.60102],[13.42189,50.61243],[13.37388,50.65049],[13.37805,50.627],[13.32264,50.60317],[13.32594,50.58009],[13.29454,50.57904],[13.25158,50.59268],[13.19921,50.50048],[13.13424,50.51709],[13.09407,50.49896],[13.03107,50.50982],[13.03261,50.50111],[13.0236,50.48787],[13.02038,50.4734],[13.02147,50.44763],[12.98433,50.42016],[12.9418,50.40906],[12.82465,50.45738],[12.73435,50.43237],[12.73171,50.42709],[12.73133,50.42335],[12.70731,50.39948],[12.67261,50.41949],[12.51356,50.39694],[12.48707,50.37045],[12.49214,50.35228],[12.48261,50.34674],[12.46928,50.35489],[12.43722,50.33774],[12.43371,50.32506],[12.39924,50.32302],[12.40158,50.29521],[12.36594,50.28289],[12.35871,50.24059],[12.33262,50.24259],[12.33532,50.22008],[12.32445,50.20442],[12.33847,50.19432],[12.32596,50.17146],[12.29232,50.17524],[12.28063,50.19544],[12.28755,50.22429],[12.23943,50.24594],[12.24791,50.25525],[12.26953,50.25189],[12.25119,50.27079],[12.20823,50.2729],[12.18323,50.32245],[12.1247,50.31576],[12.10573,50.32086],[12.13716,50.27396],[12.09287,50.25032]]]}},{"type":"Feature","properties":{"id":"DJ"},"geometry":{"type":"Polygon","coordinates":[[[41.77727,11.49902],[41.8096,11.33606],[41.80056,10.97127],[41.97429,10.91843],[42.13691,10.97586],[42.19934,10.96444],[42.42669,10.98493],[42.62781,11.0925],[42.75111,11.06992],[42.79037,10.98493],[42.95776,10.98533],[43.42013,11.71655],[43.90659,12.3823],[43.32909,12.59711],[43.29075,12.79154],[42.88255,12.59695],[42.7996,12.42629],[42.68669,12.36247],[42.46941,12.52661],[42.4037,12.46478],[41.95461,11.81157],[41.82878,11.72361],[41.77727,11.49902]]]}},{"type":"Feature","properties":{"id":"DM"},"geometry":{"type":"Polygon","coordinates":[[[-61.78844,15.65993],[-61.51867,14.96709],[-60.78194,15.1733],[-61.07397,15.7179],[-61.78844,15.65993]]]}},{"type":"Feature","properties":{"id":"DO"},"geometry":{"type":"Polygon","coordinates":[[[-72.29523,17.48026],[-71.87936,17.20162],[-68.20301,17.83927],[-67.99519,18.97186],[-70.39828,20.32236],[-72.17094,20.08703],[-71.75865,19.70231],[-71.75539,19.67863],[-71.74432,19.66513],[-71.7429,19.58445],[-71.71261,19.55073],[-71.71268,19.53374],[-71.6802,19.45008],[-71.69448,19.37866],[-71.77766,19.33823],[-71.73229,19.26686],[-71.62642,19.21212],[-71.65337,19.11759],[-71.69938,19.10916],[-71.71088,19.08353],[-71.74088,19.0437],[-71.88766,18.95175],[-71.77766,18.95007],[-71.72624,18.87802],[-71.71885,18.78423],[-71.82556,18.62551],[-71.95412,18.64939],[-72.00201,18.62312],[-71.88102,18.50125],[-71.90875,18.45821],[-71.69566,18.3414],[-71.78526,18.18528],[-71.75465,18.14405],[-71.74994,18.11115],[-71.74608,18.1009],[-71.7384,18.09747],[-71.73827,18.06949],[-71.75671,18.03456],[-72.29523,17.48026]]]}},{"type":"Feature","properties":{"id":"DZ"},"geometry":{"type":"Polygon","coordinates":[[[-8.6715,28.71194],[-8.66879,27.6666],[-8.66674,27.31569],[-4.83423,24.99935],[1.15698,21.12843],[1.20992,20.73533],[3.24648,19.81703],[3.12501,19.1366],[3.36082,18.9745],[4.26651,19.14224],[5.8153,19.45101],[7.38361,20.79165],[7.48273,20.87258],[11.96886,23.51735],[11.62498,24.26669],[11.41061,24.21456],[10.85323,24.5595],[10.33159,24.5465],[10.02432,24.98124],[10.03146,25.35635],[9.39863,26.1938],[9.51318,26.38471],[9.90255,26.50908],[9.90417,28.77066],[9.78136,29.40961],[9.3876,30.16738],[9.55749,30.22843],[9.07483,32.07865],[8.35999,32.50101],[8.31895,32.83483],[8.1179,33.05086],[8.11433,33.10175],[7.83028,33.18851],[7.73687,33.42114],[7.54088,33.7726],[7.52851,34.06493],[7.66174,34.20167],[7.74207,34.16492],[7.81242,34.21841],[7.86264,34.3987],[8.20482,34.57575],[8.29655,34.72798],[8.25446,34.926],[8.30727,34.95378],[8.3555,35.10007],[8.47489,35.2388],[8.30329,35.29884],[8.36086,35.47774],[8.35371,35.66373],[8.26472,35.73669],[8.2626,35.91733],[8.40731,36.42208],[8.18936,36.44939],[8.16167,36.48817],[8.47609,36.66607],[8.46537,36.7706],[8.57613,36.78062],[8.67706,36.8364],[8.62972,36.86499],[8.64044,36.9401],[8.28559,38.46209],[-2.19467,35.59878],[-2.21248,35.08532],[-2.21305,35.04679],[-2.08465,34.9525],[-2.04734,34.93218],[-1.97273,34.93456],[-1.97367,34.88223],[-1.89325,34.84198],[-1.88999,34.81042],[-1.7391,34.74323],[-1.84569,34.61907],[-1.69788,34.48056],[-1.78244,34.3926],[-1.71009,34.31111],[-1.65412,34.09474],[-1.73494,33.71721],[-1.59508,33.59929],[-1.67067,33.27084],[-1.46249,33.0499],[-1.54244,32.95499],[-1.37794,32.73628],[-0.99426,32.51526],[-1.24998,32.32993],[-1.24453,32.1917],[-1.15735,32.12096],[-1.22829,32.07832],[-2.46166,32.16603],[-2.93873,32.06557],[-2.82784,31.79459],[-3.66314,31.6339],[-3.66386,31.39202],[-3.77647,31.31912],[-3.77103,31.14984],[-3.54944,31.0503],[-3.65418,30.85566],[-3.64735,30.67539],[-4.31774,30.53229],[-4.6058,30.28343],[-5.21671,29.95253],[-5.58831,29.48103],[-5.72121,29.52322],[-5.75616,29.61407],[-6.69965,29.51623],[-6.78351,29.44634],[-6.95824,29.50924],[-7.61585,29.36252],[-8.6715,28.71194]]]}},{"type":"Feature","properties":{"id":"EC"},"geometry":{"type":"Polygon","coordinates":[[[-92.21178,1.96283],[-91.67642,-1.23307],[-84.52388,-3.36941],[-80.30602,-3.39149],[-80.20647,-3.431],[-80.24137,-3.46137],[-80.23999,-3.47701],[-80.24484,-3.47898],[-80.24296,-3.48135],[-80.24585,-3.4872],[-80.23427,-3.48712],[-80.22834,-3.50074],[-80.21787,-3.50048],[-80.20813,-3.53351],[-80.21186,-3.56524],[-80.21787,-3.57475],[-80.21143,-3.59368],[-80.19848,-3.59249],[-80.19092,-3.59925],[-80.19289,-3.62307],[-80.18741,-3.63994],[-80.18817,-3.66221],[-80.19926,-3.68894],[-80.15341,-3.85483],[-80.12603,-3.89439],[-80.29478,-4.0129],[-80.39812,-3.9819],[-80.46386,-4.01342],[-80.45579,-4.0269],[-80.46987,-4.04509],[-80.4822,-4.05477],[-80.47528,-4.06102],[-80.47584,-4.07561],[-80.48446,-4.08893],[-80.44953,-4.12908],[-80.45023,-4.20938],[-80.30954,-4.20227],[-80.36816,-4.28179],[-80.4276,-4.33802],[-80.42889,-4.3518],[-80.4485,-4.37384],[-80.4509,-4.42668],[-80.39121,-4.4808],[-80.36061,-4.47682],[-80.30096,-4.4414],[-80.28671,-4.41573],[-80.23847,-4.38682],[-80.16672,-4.29642],[-80.13882,-4.29326],[-79.99068,-4.38158],[-79.95154,-4.39861],[-79.91231,-4.3892],[-79.86408,-4.42232],[-79.83987,-4.47083],[-79.79876,-4.49659],[-79.70272,-4.46493],[-79.64358,-4.43344],[-79.60221,-4.45633],[-79.56616,-4.50891],[-79.54256,-4.5268],[-79.51269,-4.51593],[-79.48402,-4.53296],[-79.49011,-4.59567],[-79.48651,-4.62681],[-79.46428,-4.65778],[-79.45861,-4.70149],[-79.3824,-4.82689],[-79.26511,-4.96696],[-79.16181,-4.9632],[-79.10533,-4.97893],[-79.01659,-5.01481],[-78.85814,-4.67378],[-78.68394,-4.60754],[-78.38624,-3.64037],[-78.34362,-3.38633],[-78.24589,-3.39907],[-78.22642,-3.51113],[-78.14324,-3.47653],[-78.19369,-3.36431],[-77.94147,-3.05454],[-76.63547,-2.59017],[-76.05203,-2.12179],[-75.57429,-1.55961],[-75.3872,-0.9374],[-75.22862,-0.95588],[-75.22862,-0.60048],[-75.53615,-0.19213],[-75.60169,-0.18708],[-75.61997,-0.10012],[-75.40192,-0.17196],[-75.25764,-0.11943],[-75.41015,-0.08892],[-75.85338,0.13029],[-76.10504,0.31499],[-76.26091,0.42228],[-76.41215,0.38228],[-76.4094,0.24015],[-76.53368,0.25817],[-76.60251,0.23225],[-76.9079,0.24676],[-77.12471,0.37284],[-77.52001,0.40782],[-77.47447,0.66139],[-77.56201,0.65324],[-77.69565,0.7479],[-77.66416,0.81604],[-77.68613,0.83029],[-77.7148,0.85003],[-77.85677,0.80197],[-77.96722,0.83218],[-78.42749,1.15389],[-78.87137,1.47457],[-92.21178,1.96283]]]}},{"type":"Feature","properties":{"id":"EG"},"geometry":{"type":"Polygon","coordinates":[[[24.71117,30.17441],[24.99968,29.24574],[24.99885,21.99535],[33.17563,22.00405],[34.0765,22.00501],[37.8565,22.00903],[34.51305,27.70027],[34.46254,27.99552],[34.88293,29.37455],[34.92298,29.45305],[34.26742,31.21998],[34.24012,31.29591],[34.23572,31.2966],[34.21853,31.32363],[34.052,31.46619],[33.62659,31.82938],[25.63787,31.9359],[25.09551,31.64052],[25.07251,31.55396],[24.82566,31.38031],[25.01077,30.73861],[24.71117,30.17441]]]}},{"type":"Feature","properties":{"id":"ER"},"geometry":{"type":"Polygon","coordinates":[[[36.43478,15.17022],[36.54087,14.27785],[36.55837,14.25789],[36.56747,14.26654],[36.55659,14.28237],[36.63364,14.31172],[36.85787,14.32201],[37.01622,14.2561],[37.09486,14.27155],[37.13206,14.40746],[37.3106,14.44657],[37.47319,14.2149],[37.528,14.18413],[37.91287,14.89447],[38.0364,14.72745],[38.25562,14.67287],[38.3533,14.51323],[38.45748,14.41445],[38.7747,14.47299],[38.98058,14.54895],[39.02834,14.63717],[39.16074,14.65187],[39.15887,14.60966],[39.20256,14.56638],[39.23888,14.56365],[39.26702,14.48596],[39.22822,14.4493],[39.2519,14.40393],[39.37685,14.54402],[39.5446,14.48986],[39.50585,14.55735],[39.58182,14.60987],[39.76632,14.54264],[39.9443,14.41024],[40.09048,14.55633],[40.14649,14.53969],[40.21128,14.39342],[40.25686,14.41445],[40.9167,14.11152],[41.25097,13.60787],[41.62864,13.38626],[42.05841,12.80912],[42.21469,12.75832],[42.2798,12.6355],[42.4037,12.46478],[42.46941,12.52661],[42.68669,12.36247],[42.7996,12.42629],[42.88255,12.59695],[43.29075,12.79154],[42.63806,13.58268],[41.29956,15.565],[41.37609,16.19728],[39.63762,18.37348],[38.58398,18.02216],[38.45455,17.90132],[38.38674,17.73724],[38.33988,17.64271],[38.13362,17.53906],[37.50967,17.32199],[37.42694,17.04041],[36.99777,17.07172],[36.92161,16.63224],[36.92193,16.23451],[36.76371,15.80831],[36.69761,15.75323],[36.54276,15.23478],[36.43478,15.17022]]]}},{"type":"Feature","properties":{"id":"EE"},"geometry":{"type":"Polygon","coordinates":[[[19.84909,57.57876],[22.80496,57.87798],[23.20055,57.56697],[24.26221,57.91787],[24.3579,57.87471],[25.19484,58.0831],[25.28237,57.98539],[25.29581,58.08288],[25.70217,57.90536],[26.05949,57.84744],[26.0324,57.79037],[26.02456,57.78342],[26.027,57.78158],[26.0266,57.77441],[26.02069,57.77169],[26.02415,57.76865],[26.03332,57.7718],[26.0543,57.76105],[26.08098,57.76619],[26.2029,57.7206],[26.1866,57.6849],[26.29253,57.59244],[26.46527,57.56885],[26.54675,57.51813],[26.90364,57.62823],[27.34698,57.52242],[27.31919,57.57672],[27.40393,57.62125],[27.3746,57.66834],[27.52615,57.72843],[27.50171,57.78842],[27.56689,57.83356],[27.78526,57.83963],[27.81841,57.89244],[27.67282,57.92627],[27.62393,58.09462],[27.48541,58.22615],[27.55489,58.39525],[27.36366,58.78381],[27.74429,58.98351],[27.74476,59.02792],[27.76605,59.03295],[27.80794,59.12826],[27.88948,59.1856],[27.90911,59.24353],[28.00689,59.28351],[28.12808,59.29253],[28.18988,59.3457],[28.19598,59.35953],[28.21134,59.36941],[28.20302,59.37468],[28.20847,59.38382],[28.19061,59.39962],[28.04187,59.47017],[27.85643,59.58538],[26.90044,59.63819],[26.32936,60.00121],[20.5104,59.15546],[19.84909,57.57876]]]}},{"type":"Feature","properties":{"id":"ET"},"geometry":{"type":"Polygon","coordinates":[[[33.0025,7.94032],[33.01717,7.8462],[33.04944,7.78989],[33.24637,7.77939],[33.32531,7.71297],[33.44745,7.7543],[33.71407,7.65983],[33.87642,7.5491],[34.02984,7.36449],[34.03878,7.27437],[34.01495,7.25664],[34.19369,7.12807],[34.19369,7.04382],[34.35753,6.91963],[34.47669,6.91076],[34.53925,6.82794],[34.53776,6.74808],[34.65096,6.72589],[34.77459,6.5957],[34.87736,6.60161],[35.01738,6.46991],[34.96227,6.26415],[35.00546,5.89387],[35.12611,5.68937],[35.13058,5.62118],[35.31188,5.50106],[35.29938,5.34042],[35.50792,5.42431],[35.8576,5.33413],[35.81968,5.10757],[35.82118,4.77382],[35.9419,4.61933],[35.95449,4.53244],[36.03924,4.44406],[36.84474,4.44518],[37.07724,4.33503],[38.14168,3.62487],[38.45812,3.60445],[38.52336,3.62551],[38.91938,3.51198],[39.03562,3.52618],[39.05442,3.51941],[39.09261,3.53286],[39.19954,3.47834],[39.49444,3.45521],[39.51551,3.40895],[39.55132,3.39634],[39.58339,3.47434],[39.76808,3.67058],[39.86043,3.86974],[40.77498,4.27683],[41.1754,3.94079],[41.71371,3.99064],[41.83216,3.94885],[41.90586,3.98059],[42.07619,4.17667],[42.55853,4.20518],[42.84526,4.28357],[42.97746,4.44032],[43.04177,4.57923],[43.40263,4.79289],[44.02436,4.9451],[44.98104,4.91821],[45.40992,5.53602],[47.98416,8.00007],[47.92477,8.00111],[47.92099,8.0011],[46.99339,7.9989],[44.68816,8.764],[44.00161,8.99156],[43.30467,9.60684],[43.25523,9.84439],[43.0937,9.90579],[42.87643,10.18441],[42.69452,10.62672],[42.95776,10.98533],[42.79037,10.98493],[42.75111,11.06992],[42.62781,11.0925],[42.42669,10.98493],[42.19934,10.96444],[42.13691,10.97586],[41.97429,10.91843],[41.80056,10.97127],[41.8096,11.33606],[41.77727,11.49902],[41.82878,11.72361],[41.95461,11.81157],[42.4037,12.46478],[42.2798,12.6355],[42.21469,12.75832],[42.05841,12.80912],[41.62864,13.38626],[41.25097,13.60787],[40.9167,14.11152],[40.25686,14.41445],[40.21128,14.39342],[40.14649,14.53969],[40.09048,14.55633],[39.9443,14.41024],[39.76632,14.54264],[39.58182,14.60987],[39.50585,14.55735],[39.5446,14.48986],[39.37685,14.54402],[39.2519,14.40393],[39.22822,14.4493],[39.26702,14.48596],[39.23888,14.56365],[39.20256,14.56638],[39.15887,14.60966],[39.16074,14.65187],[39.02834,14.63717],[38.98058,14.54895],[38.7747,14.47299],[38.45748,14.41445],[38.3533,14.51323],[38.25562,14.67287],[38.0364,14.72745],[37.91287,14.89447],[37.528,14.18413],[37.47319,14.2149],[37.3106,14.44657],[37.13206,14.40746],[37.09486,14.27155],[37.01622,14.2561],[36.85787,14.32201],[36.63364,14.31172],[36.55659,14.28237],[36.56747,14.26654],[36.55837,14.25789],[36.44653,13.95666],[36.48824,13.83954],[36.38993,13.56459],[36.24545,13.36759],[36.15514,12.96658],[36.13374,12.92665],[36.16651,12.88019],[36.14268,12.70879],[36.01458,12.72478],[35.70144,12.66629],[35.24302,11.91132],[35.11492,11.85156],[35.05832,11.71158],[35.09556,11.56278],[34.95557,11.2471],[35.01215,11.19626],[34.93172,10.95946],[34.97789,10.91559],[34.97491,10.86147],[34.86916,10.78832],[34.86618,10.74588],[34.77756,10.6822],[34.77383,10.74588],[34.59062,10.89072],[34.4372,10.781],[34.27974,10.53726],[34.29399,10.52426],[34.34783,10.23914],[34.32352,10.1181],[34.22718,10.02506],[34.20484,9.9033],[34.13186,9.7492],[34.08717,9.55243],[34.10229,9.50238],[34.14304,9.04654],[34.14453,8.60204],[34.0256,8.4997],[33.89579,8.4842],[33.87195,8.41938],[33.71407,8.3678],[33.66938,8.44442],[33.55954,8.46736],[33.3119,8.45474],[33.19776,8.40903],[33.1853,8.29264],[33.18083,8.13047],[33.07846,8.05175],[33.03794,7.96845],[33.0025,7.94032]]]}},{"type":"Feature","properties":{"id":"FI"},"geometry":{"type":"Polygon","coordinates":[[[19.08191,60.19152],[20.5104,59.15546],[26.32936,60.00121],[27.44953,60.22766],[27.71177,60.3893],[27.77755,60.51759],[27.87282,60.60441],[28.54042,60.95561],[28.82228,61.12119],[29.01829,61.17448],[29.30666,61.33001],[29.64454,61.52023],[30.42354,62.02281],[31.1634,62.45585],[31.38369,62.66284],[31.58535,62.91642],[31.29294,63.09035],[31.23244,63.22239],[30.49637,63.46666],[29.98213,63.75795],[30.25437,63.83364],[30.55687,64.09036],[30.4762,64.25728],[30.06279,64.35782],[29.99267,64.58058],[30.12329,64.64862],[30.05271,64.79072],[29.68972,64.80789],[29.61914,65.05993],[29.84096,65.1109],[29.8813,65.22101],[29.61914,65.23791],[30.12517,65.7552],[29.91155,66.13863],[29.30622,66.66539],[28.9839,66.94139],[29.91155,67.51507],[30.02041,67.67523],[29.66955,67.79872],[29.34179,68.06655],[28.62982,68.19816],[28.43941,68.53366],[28.78224,68.86696],[28.45957,68.91417],[28.91738,69.04774],[28.81248,69.11997],[28.8629,69.22395],[29.31664,69.47994],[29.12697,69.69193],[28.36883,69.81658],[28.32849,69.88605],[27.97558,69.99671],[27.95542,70.0965],[27.57226,70.06215],[27.05802,69.92069],[26.64461,69.96565],[26.40261,69.91377],[25.96904,69.68397],[25.69679,69.27039],[25.75729,68.99383],[25.61613,68.89602],[25.42455,68.90328],[25.12206,68.78684],[25.10189,68.63307],[24.93048,68.61102],[24.90023,68.55579],[24.74898,68.65143],[24.18432,68.73936],[24.02299,68.81601],[23.781,68.84514],[23.68017,68.70276],[23.13064,68.64684],[22.53321,68.74393],[22.38367,68.71561],[22.27276,68.89514],[21.63833,69.27485],[21.27827,69.31281],[21.00732,69.22755],[20.98641,69.18809],[21.11099,69.10291],[21.05775,69.0356],[20.72171,69.11874],[20.55258,69.06069],[20.78802,69.03087],[20.91658,68.96764],[20.85104,68.93142],[20.90649,68.89696],[21.03001,68.88969],[22.00429,68.50692],[22.73028,68.40881],[23.10336,68.26551],[23.15377,68.14759],[23.26469,68.15134],[23.40081,68.05545],[23.65793,67.9497],[23.45627,67.85297],[23.54701,67.59306],[23.39577,67.46974],[23.72291,67.43912],[23.75372,67.29914],[23.58215,67.2654],[23.58735,67.20752],[23.56214,67.17038],[23.98563,66.84149],[23.98059,66.79585],[23.89488,66.772],[23.85959,66.56434],[23.63776,66.43568],[23.67591,66.3862],[23.64982,66.30603],[23.71339,66.21299],[23.90497,66.15802],[24.15292,65.86293],[24.13249,65.86342],[24.12597,65.85092],[24.14503,65.84046],[24.15107,65.81427],[24.14112,65.39731],[20.15877,63.06556],[19.23413,60.61414],[19.08191,60.19152]]]}},{"type":"Feature","properties":{"id":"NC"},"geometry":{"type":"Polygon","coordinates":[[[157.46481,-18.93777],[158.4748,-21.86428],[166.93331,-23.49588],[173.07304,-22.54607],[162.93363,-17.28904],[157.46481,-18.93777]]]}},{"type":"Feature","properties":{"id":"GF"},"geometry":{"type":"Polygon","coordinates":[[[-54.6084,2.32856],[-54.16286,2.10779],[-53.78743,2.34412],[-52.96539,2.1881],[-52.6906,2.37298],[-52.31787,3.17896],[-51.85573,3.83427],[-51.82312,3.85825],[-51.79599,3.89336],[-51.76551,3.98036],[-51.65531,4.05811],[-51.61983,4.14596],[-51.55535,4.70281],[-53.83024,6.10624],[-54.01074,5.68785],[-54.00724,5.55072],[-54.14457,5.36582],[-54.29117,5.24771],[-54.35743,5.1477],[-54.44051,4.94713],[-54.47879,4.90454],[-54.46918,4.88795],[-54.46223,4.78027],[-54.46394,4.72938],[-54.42,4.71911],[-54.43511,4.63494],[-54.41554,4.61483],[-54.42051,4.56581],[-54.44794,4.52564],[-54.39056,4.28273],[-54.39022,4.18207],[-54.32584,4.14937],[-54.35709,4.05006],[-54.19367,3.84387],[-54.20302,3.80858],[-54.13444,3.80139],[-54.09101,3.72919],[-54.08286,3.6742],[-54.05128,3.63557],[-54.02981,3.63078],[-54.02441,3.64559],[-54.01033,3.65193],[-53.97892,3.60482],[-54.00904,3.46724],[-54.01617,3.4178],[-54.059,3.38422],[-54.0529,3.364],[-54.06681,3.32347],[-54.06852,3.30299],[-54.1286,3.28688],[-54.28534,2.67798],[-54.42864,2.42442],[-54.6084,2.32856]]]}},{"type":"Feature","properties":{"id":"MQ"},"geometry":{"type":"Polygon","coordinates":[[[-61.51867,14.96709],[-61.26561,14.25664],[-60.5958,14.23076],[-60.78194,15.1733],[-61.51867,14.96709]]]}},{"type":"Feature","properties":{"id":"GP"},"geometry":{"type":"Polygon","coordinates":[[[-62.17275,16.35721],[-61.78844,15.65993],[-61.07397,15.7179],[-60.71337,16.48911],[-61.44461,16.81958],[-61.83929,16.66647],[-62.17275,16.35721]]]}},{"type":"Feature","properties":{"id":"GA"},"geometry":{"type":"Polygon","coordinates":[[[7.24416,-0.64092],[10.75913,-4.39519],[11.12647,-3.94169],[11.22301,-3.69888],[11.48764,-3.51089],[11.57949,-3.52798],[11.68608,-3.68942],[11.87083,-3.71571],[11.92719,-3.62768],[11.8318,-3.5812],[11.96554,-3.30267],[11.70227,-3.17465],[11.70558,-3.0773],[11.80365,-3.00424],[11.64798,-2.81146],[11.5359,-2.85654],[11.64487,-2.61865],[11.57637,-2.33379],[11.74605,-2.39936],[11.96866,-2.33559],[12.04895,-2.41704],[12.47925,-2.32626],[12.44656,-1.92025],[12.61312,-1.8129],[12.81692,-1.9092],[13.02759,-2.33098],[13.53996,-2.44405],[13.75884,-2.09293],[13.92073,-2.35581],[13.85846,-2.46935],[14.10442,-2.49268],[14.23829,-2.33715],[14.16202,-2.23916],[14.23518,-2.15671],[14.25932,-1.97624],[14.41838,-1.89412],[14.52569,-0.57818],[14.41887,-0.44799],[14.2165,-0.38261],[14.06862,-0.20826],[13.90632,-0.2287],[13.88648,0.26652],[14.10909,0.58563],[14.31346,0.55617],[14.48179,0.9152],[14.25186,1.39842],[13.89582,1.4261],[13.15519,1.23368],[13.25447,1.32339],[13.13461,1.57238],[13.29688,2.17031],[13.28534,2.25716],[11.38046,2.3005],[11.35831,2.17202],[11.32862,1.95381],[11.4141,1.87763],[11.33239,1.65939],[11.33514,1.00039],[10.95096,1.00039],[10.94993,1.02716],[10.78788,1.02819],[10.67871,1.00107],[9.99567,1.00039],[9.95653,0.92796],[9.79648,1.0019],[9.75106,1.06463],[9.6711,1.06561],[9.53783,0.98494],[7.24416,-0.64092]]]}},{"type":"Feature","properties":{"id":"MS"},"geometry":{"type":"Polygon","coordinates":[[[-62.52079,16.69392],[-62.17275,16.35721],[-61.83929,16.66647],[-62.14123,17.02632],[-62.52079,16.69392]]]}},{"type":"Feature","properties":{"id":"BM"},"geometry":{"type":"Polygon","coordinates":[[[-65.23529,32.66274],[-65.22652,31.98296],[-64.37503,31.99084],[-64.3838,32.67056],[-65.23529,32.66274]]]}},{"type":"Feature","properties":{"id":"GI"},"geometry":{"type":"Polygon","coordinates":[[[-5.40134,36.14896],[-5.39074,36.10278],[-5.36503,36.06205],[-5.32837,36.05935],[-5.3004,36.07439],[-5.28217,36.09907],[-5.27801,36.14942],[-5.33822,36.15272],[-5.34536,36.15501],[-5.36494,36.15496],[-5.38545,36.15481],[-5.40134,36.14896]]]}},{"type":"Feature","properties":{"id":"GE"},"geometry":{"type":"Polygon","coordinates":[[[39.8664,43.20124],[41.29005,42.40875],[41.2657,41.64323],[41.54366,41.52185],[41.7148,41.4932],[41.7124,41.47417],[41.81939,41.43621],[41.95134,41.52466],[42.26387,41.49346],[42.51772,41.43606],[42.60163,41.58516],[42.72794,41.59714],[42.84471,41.58912],[42.78995,41.50126],[42.84899,41.47265],[42.8785,41.50516],[43.02956,41.37891],[43.21707,41.30331],[43.13373,41.25503],[43.1945,41.25242],[43.23096,41.17536],[43.36118,41.2028],[43.44973,41.17666],[43.4717,41.12611],[43.67712,41.13398],[43.74717,41.1117],[43.84835,41.16329],[44.16591,41.19141],[44.18148,41.24644],[44.32139,41.2079],[44.34337,41.20312],[44.34417,41.2382],[44.46791,41.18204],[44.59322,41.1933],[44.61462,41.24018],[44.72814,41.20338],[44.82084,41.21513],[44.87887,41.20195],[44.89911,41.21366],[44.84358,41.23088],[44.81749,41.23488],[44.80053,41.25949],[44.81437,41.30371],[44.93493,41.25685],[45.0133,41.29747],[45.09867,41.34065],[45.1797,41.42231],[45.26285,41.46433],[45.31352,41.47168],[45.4006,41.42402],[45.45973,41.45898],[45.68389,41.3539],[45.71035,41.36208],[45.75705,41.35157],[45.69946,41.29545],[45.80842,41.2229],[45.95786,41.17956],[46.13221,41.19479],[46.27698,41.19011],[46.37661,41.10805],[46.456,41.09984],[46.48558,41.0576],[46.55096,41.1104],[46.63969,41.09515],[46.66148,41.20533],[46.72375,41.28609],[46.63658,41.37727],[46.4669,41.43331],[46.40307,41.48464],[46.33925,41.4963],[46.29794,41.5724],[46.34126,41.57454],[46.34039,41.5947],[46.3253,41.60912],[46.28182,41.60089],[46.26531,41.63339],[46.24429,41.59883],[46.19759,41.62327],[46.17891,41.72094],[46.20538,41.77205],[46.23962,41.75811],[46.30863,41.79133],[46.3984,41.84399],[46.42738,41.91323],[45.61676,42.20768],[45.78692,42.48358],[45.36501,42.55268],[45.15318,42.70598],[45.08266,42.71749],[44.88754,42.74934],[44.80941,42.61277],[44.70002,42.74679],[44.54202,42.75699],[44.22978,42.64904],[43.95517,42.55396],[43.73119,42.62043],[43.81453,42.74297],[43.36538,42.90656],[43.0419,43.02413],[43.03322,43.08883],[42.75889,43.19651],[42.66667,43.13917],[42.40563,43.23226],[42.13085,43.20326],[41.64935,43.22331],[40.65957,43.56212],[40.10657,43.57344],[40.04445,43.47776],[40.03312,43.44262],[40.01007,43.42411],[40.01552,43.42025],[40.00853,43.40578],[40.0078,43.38551],[39.8664,43.20124]]]}},{"type":"Feature","properties":{"id":"GH"},"geometry":{"type":"Polygon","coordinates":[[[-3.34019,4.17519],[1.07031,5.15655],[1.27574,5.93551],[1.19974,6.11307],[1.19956,6.16922],[1.08644,6.16905],[1.05969,6.22998],[1.03108,6.24064],[1.00404,6.33423],[0.89212,6.3373],[0.85659,6.38695],[0.76732,6.4296],[0.71048,6.53083],[0.74862,6.56517],[0.73471,6.59128],[0.6348,6.62777],[0.6497,6.73682],[0.58004,6.76161],[0.57558,6.80712],[0.52853,6.82921],[0.56528,6.92506],[0.52098,6.94391],[0.52217,6.9723],[0.60493,7.01775],[0.60424,7.13666],[0.65327,7.31643],[0.62943,7.41099],[0.56322,7.38868],[0.52455,7.45354],[0.51979,7.58706],[0.58295,7.62368],[0.58811,7.7018],[0.62943,7.85751],[0.58891,8.12779],[0.6056,8.13959],[0.61156,8.18324],[0.5913,8.19622],[0.63897,8.25873],[0.73432,8.29529],[0.64731,8.48866],[0.47211,8.59945],[0.37319,8.75262],[0.53094,8.86963],[0.45576,9.04785],[0.53352,9.2092],[0.51069,9.24546],[0.56665,9.40554],[0.49118,9.48339],[0.36485,9.49749],[0.33148,9.44812],[0.25758,9.42696],[0.2254,9.47869],[0.31241,9.50337],[0.30406,9.521],[0.2409,9.52335],[0.23851,9.57389],[0.3805,9.58106],[0.34816,9.71607],[0.32075,9.72781],[0.35739,9.85318],[0.36366,10.03309],[0.41252,10.02018],[0.41576,10.06317],[0.35293,10.09412],[0.35671,10.21509],[0.39584,10.31112],[0.32581,10.30782],[0.29453,10.41546],[0.19191,10.40357],[0.15604,10.46434],[0.14299,10.52443],[-0.05945,10.63458],[-0.09141,10.7147],[-0.07327,10.71845],[-0.07183,10.76794],[-0.0228,10.81916],[-0.02685,10.8783],[-0.00908,10.91644],[-0.0063,10.96417],[0.03355,10.9807],[0.02395,11.06229],[0.00342,11.08317],[-0.00514,11.10763],[-0.0275,11.11202],[-0.05733,11.08628],[-0.14462,11.10811],[-0.13493,11.14075],[-0.27374,11.17157],[-0.27277,11.12509],[-0.36907,11.08753],[-0.38219,11.12596],[-0.42391,11.11661],[-0.44298,11.04292],[-0.56991,10.98787],[-0.61937,10.91305],[-0.67143,10.99811],[-1.11476,10.99747],[-1.47731,11.01702],[-1.73,10.97725],[-2.83373,11.0067],[-2.84357,10.89096],[-2.94055,10.71273],[-2.94232,10.64281],[-2.83241,10.33619],[-2.75825,10.24296],[-2.80048,10.196],[-2.74174,9.83172],[-2.78915,9.74103],[-2.76534,9.56589],[-2.68802,9.49343],[-2.72186,9.31695],[-2.66315,9.24817],[-2.80323,9.05565],[-2.66357,9.01771],[-2.58899,8.78668],[-2.49037,8.20872],[-2.62901,8.11495],[-2.59723,8.03033],[-2.67787,8.02055],[-2.74819,7.92613],[-2.78395,7.94974],[-2.80494,7.86559],[-2.92339,7.60847],[-2.97575,7.27018],[-2.95438,7.23737],[-3.04424,7.08556],[-3.08818,7.05251],[-3.23327,6.81744],[-3.21954,6.74407],[-3.25999,6.62521],[-3.01896,5.71697],[-2.95323,5.71865],[-2.96671,5.6415],[-2.93132,5.62137],[-2.85378,5.65156],[-2.76614,5.60963],[-2.72737,5.34789],[-2.77625,5.34621],[-2.78554,5.28652],[-2.73074,5.1364],[-2.75502,5.10657],[-2.95261,5.12477],[-2.96554,5.10397],[-3.063,5.13665],[-3.11073,5.12675],[-3.10348,5.08596],[-3.34019,4.17519]]]}},{"type":"Feature","properties":{"id":"GN"},"geometry":{"type":"Polygon","coordinates":[[[-15.96748,10.162],[-14.36218,8.64107],[-13.30375,9.03564],[-13.27388,9.06361],[-13.23886,9.07022],[-13.18668,9.09421],[-13.08953,9.0409],[-13.07089,9.08234],[-13.00884,9.10921],[-12.95906,9.17768],[-12.94309,9.29171],[-12.91013,9.27003],[-12.76765,9.33999],[-12.69126,9.4157],[-12.47394,9.85014],[-12.43824,9.8799],[-12.24262,9.92386],[-12.12634,9.87203],[-11.91023,9.93927],[-11.89624,9.99763],[-11.1997,10.00046],[-11.14614,9.86849],[-11.07593,9.84405],[-10.88496,9.59431],[-10.84419,9.51738],[-10.84213,9.45769],[-10.80488,9.4157],[-10.80642,9.38462],[-10.73381,9.38352],[-10.70514,9.33872],[-10.65472,9.2951],[-10.66806,9.26295],[-10.66051,9.19963],[-10.71467,9.18903],[-10.73355,9.07785],[-10.66961,9.08506],[-10.62,9.07361],[-10.57326,9.05098],[-10.59107,8.98927],[-10.57571,8.92687],[-10.5761,8.89851],[-10.56339,8.85818],[-10.55983,8.80823],[-10.52018,8.78346],[-10.51082,8.74147],[-10.46748,8.67941],[-10.46958,8.65281],[-10.49228,8.62599],[-10.57232,8.59778],[-10.57451,8.57478],[-10.62094,8.52805],[-10.61957,8.49215],[-10.64043,8.47373],[-10.63897,8.39247],[-10.6745,8.37761],[-10.703,8.28029],[-10.68686,8.28674],[-10.65768,8.35086],[-10.58876,8.33651],[-10.55399,8.3067],[-10.49915,8.34178],[-10.4001,8.44817],[-10.38208,8.49062],[-10.35684,8.48519],[-10.32225,8.5059],[-10.27822,8.48816],[-10.20775,8.47984],[-10.19835,8.49389],[-10.16158,8.51642],[-10.15926,8.52737],[-10.05375,8.50697],[-10.05798,8.42279],[-9.91001,8.50089],[-9.82915,8.5014],[-9.77182,8.55301],[-9.65852,8.50038],[-9.55089,8.38084],[-9.47415,8.35195],[-9.50898,8.18455],[-9.41445,8.02448],[-9.44789,7.91286],[-9.35724,7.74111],[-9.37465,7.62032],[-9.48171,7.36672],[-9.41943,7.41809],[-9.305,7.42056],[-9.20798,7.38109],[-9.20259,7.32552],[-9.09107,7.1985],[-8.92596,7.28831],[-8.87557,7.25562],[-8.83858,7.27597],[-8.82502,7.38357],[-8.71061,7.5131],[-8.70666,7.63273],[-8.67576,7.69576],[-8.55371,7.69576],[-8.55603,7.62388],[-8.47114,7.55676],[-8.4003,7.6285],[-8.21374,7.54466],[-8.09931,7.78626],[-8.13414,7.87991],[-8.06449,8.04989],[-7.94695,8.00925],[-7.99919,8.11023],[-7.98675,8.20134],[-8.062,8.16071],[-8.2411,8.24196],[-8.22991,8.48438],[-7.8432,8.51278],[-7.65653,8.36873],[-7.69882,8.66148],[-7.95503,8.81146],[-7.93075,9.00648],[-7.73862,9.08422],[-7.90777,9.20456],[-7.85056,9.41812],[-8.03463,9.39604],[-8.14657,9.55062],[-8.09434,9.86936],[-8.15652,9.94288],[-8.11921,10.04577],[-8.01225,10.1021],[-7.97971,10.17117],[-7.9578,10.2703],[-8.10207,10.44649],[-8.22711,10.41722],[-8.32614,10.69273],[-8.2667,10.91762],[-8.34686,11.07902],[-8.70082,10.94321],[-8.47457,11.23498],[-8.4835,11.27976],[-8.3218,11.3619],[-8.5786,11.47262],[-8.69602,11.63777],[-8.83438,11.65728],[-8.82459,11.82366],[-8.7985,11.82879],[-8.78133,11.94293],[-8.8797,12.042],[-8.96278,12.28432],[-8.93531,12.32792],[-9.13689,12.50875],[-9.38067,12.48446],[-9.32097,12.29009],[-9.63938,12.18312],[-9.7229,12.01212],[-10.10519,12.16973],[-10.33521,12.22309],[-10.44267,12.12224],[-10.5043,12.12358],[-10.53176,12.00909],[-10.70514,11.90464],[-10.80355,12.1053],[-11.01722,12.2553],[-11.08709,12.1318],[-11.256,11.99046],[-11.50006,12.17826],[-11.36793,12.40153],[-11.46267,12.44559],[-11.77425,12.38561],[-11.89888,12.44797],[-11.96926,12.40573],[-12.01818,12.40606],[-12.35601,12.31752],[-12.40734,12.38695],[-12.56544,12.36867],[-12.90018,12.54953],[-12.96077,12.47864],[-13.06603,12.49342],[-13.04901,12.63296],[-13.48915,12.67551],[-13.7075,12.67618],[-13.7099,12.6075],[-13.65089,12.49515],[-13.64004,12.43054],[-13.70851,12.24978],[-13.92745,12.24077],[-13.95452,12.16973],[-13.7039,12.00869],[-13.70681,11.70569],[-13.78097,11.68518],[-13.84998,11.74401],[-13.87229,11.66501],[-14.09799,11.63649],[-14.2048,11.67207],[-14.27235,11.66963],[-14.31513,11.60713],[-14.51173,11.49708],[-14.66677,11.51188],[-14.77334,11.36459],[-14.95993,10.99244],[-15.07174,10.89557],[-15.96748,10.162]]]}},{"type":"Feature","properties":{"id":"GM"},"geometry":{"type":"Polygon","coordinates":[[[-17.43966,13.04579],[-16.74676,13.06025],[-16.69343,13.16791],[-15.80355,13.16729],[-15.80478,13.34832],[-15.26908,13.37768],[-15.14917,13.57989],[-14.36795,13.23033],[-13.79409,13.34472],[-13.8955,13.59126],[-14.34721,13.46578],[-14.93719,13.80173],[-15.36504,13.79313],[-15.47902,13.58758],[-17.43598,13.59273],[-17.43966,13.04579]]]}},{"type":"Feature","properties":{"id":"GW"},"geometry":{"type":"Polygon","coordinates":[[[-17.4623,11.92379],[-15.96748,10.162],[-15.07174,10.89557],[-14.95993,10.99244],[-14.77334,11.36459],[-14.66677,11.51188],[-14.51173,11.49708],[-14.31513,11.60713],[-14.27235,11.66963],[-14.2048,11.67207],[-14.09799,11.63649],[-13.87229,11.66501],[-13.84998,11.74401],[-13.78097,11.68518],[-13.70681,11.70569],[-13.7039,12.00869],[-13.95452,12.16973],[-13.92745,12.24077],[-13.70851,12.24978],[-13.64004,12.43054],[-13.65089,12.49515],[-13.7099,12.6075],[-13.7075,12.67618],[-15.17582,12.6847],[-15.33674,12.6142],[-15.42892,12.5368],[-15.6804,12.42635],[-15.89035,12.45032],[-16.04278,12.4716],[-16.20591,12.46157],[-16.38191,12.36449],[-16.69097,12.35727],[-17.4623,11.92379]]]}},{"type":"Feature","properties":{"id":"GQ"},"geometry":{"type":"Polygon","coordinates":[[[5.37613,-1.68343],[5.85762,-1.69667],[7.24416,-0.64092],[9.53783,0.98494],[9.6711,1.06561],[9.75106,1.06463],[9.79648,1.0019],[9.95653,0.92796],[9.99567,1.00039],[10.67871,1.00107],[10.78788,1.02819],[10.94993,1.02716],[10.95096,1.00039],[11.33514,1.00039],[11.33239,1.65939],[11.4141,1.87763],[11.32862,1.95381],[11.35831,2.17202],[11.19764,2.19192],[11.1494,2.16722],[10.01589,2.16561],[9.90749,2.20049],[9.87859,2.23154],[9.84716,2.24676],[9.83238,2.29079],[9.83754,2.32428],[9.82123,2.35097],[9.6225,2.44901],[9.22018,3.72052],[8.6479,4.06346],[8.05799,3.48284],[8.0168,1.79377],[7.47763,0.84469],[6.69416,-0.53945],[5.87114,-1.20569],[5.38965,-1.19244],[5.37613,-1.68343]]]}},{"type":"Feature","properties":{"id":"GT"},"geometry":{"type":"Polygon","coordinates":[[[-92.37213,14.39277],[-90.42092,13.19698],[-90.11344,13.73679],[-90.10402,13.84958],[-89.88937,14.0396],[-89.82181,14.06431],[-89.7746,14.03201],[-89.73358,14.03501],[-89.74679,14.06448],[-89.70756,14.1537],[-89.64191,14.20348],[-89.58174,14.20315],[-89.58097,14.20315],[-89.5511,14.22228],[-89.52397,14.22628],[-89.50614,14.26084],[-89.60397,14.32925],[-89.57187,14.3527],[-89.57925,14.41556],[-89.5396,14.41522],[-89.53342,14.38247],[-89.47093,14.42936],[-89.43368,14.41223],[-89.39558,14.45088],[-89.34776,14.43013],[-89.35524,14.46825],[-89.23719,14.58046],[-89.15653,14.57802],[-89.13132,14.71582],[-89.23467,14.85596],[-89.15149,14.97775],[-89.18048,14.99967],[-89.15149,15.07392],[-88.97343,15.14039],[-88.55255,15.44606],[-88.34432,15.6184],[-88.31459,15.66942],[-88.24871,15.6894],[-88.22552,15.72294],[-88.20359,16.03858],[-88.40779,16.09624],[-88.95358,15.88698],[-89.02415,15.9063],[-89.17418,15.90898],[-89.22683,15.88619],[-89.15025,17.04813],[-89.14985,17.81563],[-90.98678,17.81655],[-90.99199,17.25192],[-91.43809,17.25373],[-91.04436,16.92175],[-90.69064,16.70697],[-90.61212,16.49832],[-90.40499,16.40524],[-90.44567,16.07573],[-91.73182,16.07371],[-92.20983,15.26077],[-92.04836,15.06278],[-92.14788,14.98341],[-92.1423,14.88647],[-92.17783,14.84644],[-92.16851,14.74931],[-92.14273,14.69003],[-92.14756,14.67925],[-92.14696,14.66115],[-92.1625,14.64936],[-92.18833,14.57079],[-92.21554,14.55293],[-92.22756,14.53116],[-92.37213,14.39277]]]}},{"type":"Feature","properties":{"id":"GY"},"geometry":{"type":"Polygon","coordinates":[[[-61.3883,5.94689],[-60.73204,5.20931],[-60.32352,5.21299],[-60.20944,5.28754],[-59.97642,5.07367],[-60.04189,4.69801],[-60.15953,4.53456],[-59.78878,4.45637],[-59.69361,4.34069],[-59.73353,4.20399],[-59.51963,3.91951],[-59.85145,3.55762],[-59.81111,3.37916],[-59.80373,3.36888],[-59.80622,3.35423],[-59.8336,3.35526],[-59.8275,3.33272],[-59.98466,2.91115],[-59.91177,2.36759],[-59.7264,2.27497],[-59.74845,1.88038],[-59.25583,1.40559],[-58.92072,1.31293],[-58.84229,1.17749],[-58.53571,1.29154],[-58.4858,1.48399],[-58.33887,1.58014],[-58.01873,1.51966],[-57.97336,1.64566],[-57.77281,1.73344],[-57.55743,1.69605],[-57.35073,1.98327],[-57.23981,1.95808],[-57.09109,2.01854],[-57.07092,1.95304],[-56.7659,1.89509],[-56.47045,1.95135],[-56.55439,2.02003],[-56.70519,2.02964],[-57.35891,3.32121],[-58.0307,3.95513],[-57.8699,4.89394],[-57.37442,5.0208],[-57.19036,5.18326],[-57.33078,5.32634],[-56.96411,6.23066],[-59.54058,8.6862],[-59.99771,8.54639],[-59.85562,8.35213],[-59.80661,8.28906],[-59.83156,8.23261],[-59.97059,8.20791],[-60.02407,8.04557],[-60.38056,7.8302],[-60.51959,7.83373],[-60.64793,7.56877],[-60.71923,7.55817],[-60.59802,7.33194],[-60.63367,7.25061],[-60.54098,7.14804],[-60.44116,7.20817],[-60.28074,7.1162],[-60.39419,6.94847],[-60.54873,6.8631],[-61.07909,6.72828],[-61.08943,6.70386],[-61.14767,6.70706],[-61.20762,6.58174],[-61.13324,6.20035],[-61.3883,5.94689]]]}},{"type":"Feature","properties":{"id":"HN"},"geometry":{"type":"Polygon","coordinates":[[[-89.35524,14.46825],[-89.34776,14.43013],[-89.21567,14.38846],[-89.17284,14.3636],[-89.09877,14.41157],[-89.07045,14.33823],[-89.0344,14.33225],[-89.02204,14.30813],[-88.96024,14.2098],[-88.90282,14.20581],[-88.86162,14.17386],[-88.82815,14.10619],[-88.80772,14.11668],[-88.79708,14.16445],[-88.73708,14.13083],[-88.7091,14.04583],[-88.50053,13.96938],[-88.49092,13.86074],[-88.26458,13.9124],[-88.22673,13.95789],[-88.2372,13.99911],[-88.10588,14.0007],[-88.07327,13.99237],[-88.01164,13.87141],[-87.82453,13.92623],[-87.70128,13.8044],[-87.73106,13.75443],[-87.75896,13.60211],[-87.7684,13.59293],[-87.78316,13.52117],[-87.72033,13.50465],[-87.72162,13.44964],[-87.83645,13.39692],[-87.73714,13.32715],[-87.61441,13.1834],[-87.37107,12.98646],[-87.06665,13.00455],[-87.03785,12.98682],[-86.95507,13.03733],[-86.95301,13.06442],[-86.92666,13.19073],[-86.91327,13.26567],[-86.84683,13.30385],[-86.70169,13.30126],[-86.77843,13.77373],[-86.54617,13.80107],[-86.34258,13.76706],[-86.12422,14.05033],[-86.00818,14.08047],[-86.04045,13.99537],[-85.74966,13.84141],[-85.7591,13.95622],[-85.41715,14.12026],[-85.33973,14.25123],[-85.17391,14.25839],[-85.21287,14.37615],[-85.14249,14.54935],[-85.02147,14.6065],[-85.00465,14.72209],[-84.90082,14.80489],[-84.80587,14.82965],[-84.70381,14.68473],[-84.57635,14.65484],[-84.35285,14.69453],[-84.10755,14.75927],[-83.8801,14.76907],[-83.69281,14.87129],[-83.62101,14.89448],[-83.5124,15.01211],[-83.21405,14.99354],[-83.13724,15.00002],[-83.04763,15.03256],[-82.28946,14.65367],[-82.11403,16.02046],[-83.87207,17.68503],[-88.20359,16.03858],[-88.22552,15.72294],[-88.24871,15.6894],[-88.31459,15.66942],[-88.34432,15.6184],[-88.55255,15.44606],[-88.97343,15.14039],[-89.15149,15.07392],[-89.18048,14.99967],[-89.15149,14.97775],[-89.23467,14.85596],[-89.13132,14.71582],[-89.15653,14.57802],[-89.23719,14.58046],[-89.35524,14.46825]]]}},{"type":"Feature","properties":{"id":"HR"},"geometry":{"type":"Polygon","coordinates":[[[13.12821,44.48877],[16.15283,42.18525],[18.45131,42.21682],[18.54128,42.39171],[18.52152,42.42302],[18.43588,42.48556],[18.44307,42.51077],[18.43735,42.55921],[18.3615,42.61867],[18.25052,42.60541],[18.17816,42.66028],[18.15713,42.65359],[18.13611,42.68142],[17.89775,42.81781],[17.82394,42.91796],[17.78961,42.89344],[17.6873,42.92839],[17.6444,42.88641],[17.5392,42.92787],[17.70879,42.97223],[17.64268,43.08595],[17.43118,43.18402],[17.42826,43.21868],[17.3529,43.2527],[17.286,43.33065],[17.25579,43.40353],[17.28612,43.43266],[17.28475,43.47154],[17.22321,43.49956],[17.16218,43.49272],[17.08313,43.54263],[17.02983,43.56391],[16.99748,43.58559],[16.80736,43.76011],[16.74882,43.77394],[16.70922,43.84887],[16.55472,43.95326],[16.50528,44.0244],[16.43629,44.02826],[16.43662,44.07523],[16.37666,44.08129],[16.26337,44.17764],[16.21727,44.2177],[16.22079,44.23572],[16.19088,44.27141],[16.18766,44.30855],[16.22028,44.34883],[16.12969,44.38275],[16.17144,44.40594],[16.10566,44.52586],[16.03012,44.55572],[16.01729,44.58025],[16.06407,44.61142],[15.95824,44.69361],[15.89348,44.74964],[15.8255,44.71501],[15.72584,44.82334],[15.79472,44.8455],[15.76096,44.87045],[15.74723,44.96818],[15.78374,44.9722],[15.74585,45.0638],[15.79061,45.11635],[15.76371,45.16508],[15.77778,45.17653],[15.77816,45.18515],[15.80108,45.20036],[15.81022,45.20979],[15.82709,45.20786],[15.8337,45.22201],[15.89103,45.21626],[15.92382,45.22739],[16.00965,45.21838],[16.12153,45.09616],[16.28937,44.99661],[16.35572,45.0031],[16.35863,45.03529],[16.3749,45.05206],[16.38219,45.05139],[16.38456,45.06424],[16.41091,45.12035],[16.4703,45.14621],[16.47974,45.18524],[16.49185,45.20877],[16.52403,45.22545],[16.53618,45.22702],[16.5501,45.2212],[16.58484,45.22506],[16.59952,45.23156],[16.64962,45.20714],[16.68754,45.20048],[16.7344,45.20719],[16.77723,45.19262],[16.81137,45.18434],[16.83804,45.18951],[16.83534,45.21614],[16.91001,45.25579],[16.92272,45.27694],[16.94203,45.26872],[16.93954,45.2289],[16.9767,45.24292],[17.0415,45.20759],[17.18004,45.14657],[17.2442,45.14581],[17.25131,45.14957],[17.26982,45.18832],[17.32092,45.16246],[17.34337,45.14148],[17.41727,45.13398],[17.4498,45.16119],[17.45942,45.12574],[17.48748,45.13264],[17.51469,45.10791],[17.60112,45.10836],[17.66571,45.13408],[17.84754,45.04478],[17.87148,45.04645],[17.93706,45.08016],[17.97336,45.12245],[17.97834,45.13831],[17.99479,45.14958],[18.01774,45.15111],[18.03121,45.12632],[18.06366,45.14596],[18.10718,45.0877],[18.17756,45.07739],[18.21344,45.08927],[18.20846,45.12804],[18.27541,45.13458],[18.32176,45.10151],[18.41896,45.11083],[18.46943,45.06787],[18.47926,45.05951],[18.53659,45.0583],[18.58886,45.08824],[18.66482,45.06667],[18.78687,44.98142],[18.78515,44.96742],[18.80661,44.93561],[18.76369,44.93707],[18.76347,44.90669],[18.86678,44.85233],[19.01994,44.85493],[18.98957,44.90645],[19.02871,44.92541],[19.06853,44.89915],[19.15573,44.95409],[19.05205,44.97692],[19.1011,45.01191],[19.07952,45.14668],[19.14063,45.12972],[19.19144,45.17863],[19.43589,45.17137],[19.41941,45.23475],[19.28208,45.23813],[19.10774,45.29547],[18.97446,45.37528],[18.99918,45.49333],[19.08364,45.48804],[19.07471,45.53086],[18.94562,45.53712],[18.88776,45.57253],[18.96691,45.66731],[18.90305,45.71863],[18.85783,45.85493],[18.81394,45.91329],[18.80211,45.87995],[18.6792,45.92057],[18.57483,45.80772],[18.48415,45.78069],[18.44192,45.73757],[18.12778,45.79302],[18.08869,45.76511],[17.99805,45.79671],[17.87377,45.78522],[17.66545,45.84207],[17.56821,45.93728],[17.34878,45.95234],[17.37041,45.97322],[17.2875,45.99576],[17.15841,46.17103],[16.94967,46.2435],[16.8903,46.28122],[16.8541,46.36255],[16.7154,46.39523],[16.6639,46.45203],[16.59527,46.47524],[16.52604,46.47831],[16.5007,46.49644],[16.38771,46.53608],[16.37193,46.55008],[16.31988,46.52745],[16.30276,46.51156],[16.2874,46.51522],[16.26551,46.51581],[16.26813,46.50474],[16.24169,46.49851],[16.24135,46.48474],[16.25161,46.48143],[16.27398,46.42875],[16.27329,46.41467],[16.30162,46.40437],[16.30233,46.37837],[16.18824,46.38282],[16.14859,46.40547],[16.05763,46.39353],[16.04965,46.3824],[16.07314,46.36458],[16.07814,46.34526],[15.97965,46.30652],[15.80563,46.25911],[15.78817,46.21719],[15.76688,46.20858],[15.75254,46.20751],[15.75436,46.21969],[15.67397,46.22539],[15.6434,46.21396],[15.64904,46.19229],[15.60492,46.16333],[15.61384,46.15319],[15.59646,46.14719],[15.6083,46.11992],[15.62238,46.094],[15.66058,46.07162],[15.72977,46.04682],[15.71246,46.01196],[15.70448,45.99922],[15.70636,45.92116],[15.67967,45.90455],[15.68383,45.88867],[15.69109,45.88534],[15.67967,45.86939],[15.71006,45.84874],[15.69392,45.84336],[15.66532,45.84138],[15.64787,45.82784],[15.57492,45.85182],[15.52234,45.82195],[15.47325,45.8253],[15.47531,45.79802],[15.40836,45.79491],[15.25423,45.72275],[15.29944,45.68593],[15.30168,45.68735],[15.29676,45.69119],[15.30972,45.69811],[15.31419,45.68729],[15.34356,45.71319],[15.3648,45.69754],[15.35189,45.68408],[15.35176,45.67158],[15.37103,45.69116],[15.4057,45.64727],[15.39098,45.63816],[15.34214,45.64702],[15.34695,45.63382],[15.30266,45.6336],[15.30661,45.61319],[15.27657,45.60515],[15.29837,45.5841],[15.30052,45.53401],[15.38077,45.48962],[15.33571,45.45115],[15.2746,45.46651],[15.23915,45.45103],[15.22413,45.42598],[15.17615,45.4208],[15.08379,45.46903],[15.0856,45.47981],[15.05496,45.49232],[15.02385,45.48533],[14.92266,45.52788],[14.91179,45.4821],[14.87136,45.46699],[14.81935,45.4591],[14.79918,45.49299],[14.72996,45.52874],[14.70481,45.53497],[14.6964,45.52312],[14.68605,45.53006],[14.6876,45.54483],[14.69884,45.564],[14.68048,45.58875],[14.65207,45.59181],[14.6476,45.5974],[14.59576,45.62812],[14.60977,45.66403],[14.57397,45.67165],[14.54718,45.62226],[14.50341,45.60689],[14.49603,45.54044],[14.36693,45.48642],[14.32487,45.47142],[14.27681,45.4902],[14.26611,45.48239],[14.24239,45.50607],[14.22371,45.50388],[14.20348,45.46896],[14.07116,45.48752],[14.00578,45.52352],[13.96063,45.50825],[13.99488,45.47551],[13.98353,45.45411],[13.90771,45.45149],[13.88124,45.42637],[13.81742,45.43729],[13.79196,45.47168],[13.67398,45.4436],[13.6251,45.46241],[13.56979,45.4895],[13.45644,45.59464],[13.21833,45.43996],[13.12821,44.48877]]]}},{"type":"Feature","properties":{"id":"HU"},"geometry":{"type":"Polygon","coordinates":[[[16.11282,46.86858],[16.13947,46.85514],[16.15565,46.85394],[16.18972,46.86591],[16.21892,46.86961],[16.2365,46.87775],[16.2941,46.87137],[16.34547,46.83836],[16.3408,46.80641],[16.31303,46.79838],[16.30966,46.7787],[16.32199,46.77666],[16.36516,46.70408],[16.42641,46.69228],[16.41863,46.66238],[16.38594,46.6549],[16.39217,46.63673],[16.50139,46.56684],[16.53138,46.53241],[16.52604,46.5051],[16.59527,46.47524],[16.6639,46.45203],[16.7154,46.39523],[16.8541,46.36255],[16.8903,46.28122],[16.94967,46.2435],[17.15841,46.17103],[17.2875,45.99576],[17.37041,45.97322],[17.34878,45.95234],[17.56821,45.93728],[17.66545,45.84207],[17.87377,45.78522],[17.99805,45.79671],[18.08869,45.76511],[18.12778,45.79302],[18.44192,45.73757],[18.48415,45.78069],[18.57483,45.80772],[18.6792,45.92057],[18.80211,45.87995],[18.81394,45.91329],[18.99712,45.93537],[19.01284,45.96529],[19.0791,45.96458],[19.10388,46.04015],[19.14543,45.9998],[19.28826,45.99694],[19.52473,46.1171],[19.56113,46.16824],[19.66007,46.19005],[19.81491,46.1313],[19.93508,46.17553],[20.01816,46.17696],[20.03533,46.14509],[20.09713,46.17315],[20.26068,46.12332],[20.28324,46.1438],[20.35573,46.16629],[20.45377,46.14405],[20.5022,46.18993],[20.63863,46.12728],[20.71798,46.16746],[20.76089,46.21737],[20.74574,46.25467],[20.86797,46.28884],[21.06572,46.24897],[21.16872,46.30118],[21.28061,46.44941],[21.26929,46.4993],[21.33214,46.63035],[21.43926,46.65109],[21.5277,46.73327],[21.48935,46.7577],[21.52028,46.84118],[21.59307,46.86935],[21.59581,46.91628],[21.68645,46.99595],[21.648,47.03902],[21.78395,47.11104],[21.94463,47.38046],[22.01055,47.37767],[22.03389,47.42508],[22.00527,47.48867],[22.31816,47.76126],[22.41979,47.7391],[22.43803,47.77382],[22.67543,47.78501],[22.70874,47.82537],[22.76617,47.8417],[22.77991,47.87211],[22.89849,47.95851],[22.84276,47.98602],[22.8828,48.04182],[22.85894,48.07693],[22.83044,48.09482],[22.82804,48.11442],[22.73427,48.12005],[22.67183,48.09195],[22.58733,48.10813],[22.59007,48.15121],[22.49806,48.25189],[22.38133,48.23726],[22.21276,48.42693],[22.14689,48.4005],[22.08869,48.38339],[22.00561,48.38418],[21.83412,48.35887],[21.83335,48.33422],[21.73602,48.34832],[21.66883,48.38944],[21.66435,48.41017],[21.61113,48.50545],[21.54144,48.51216],[21.51638,48.54718],[21.44063,48.58456],[21.1322,48.4959],[20.87608,48.55297],[20.82475,48.58092],[20.5215,48.53336],[20.40332,48.36126],[20.29943,48.26104],[20.24419,48.28056],[20.09382,48.20053],[19.97348,48.16539],[19.91889,48.12645],[19.86448,48.17593],[19.82688,48.16803],[19.76852,48.20602],[19.69745,48.20591],[19.63338,48.25006],[19.54965,48.20705],[19.54278,48.20923],[19.52025,48.18314],[19.47545,48.08691],[19.43601,48.09505],[19.40082,48.08174],[19.29864,48.08932],[19.23924,48.0595],[19.01952,48.07052],[18.98385,48.05766],[18.90575,48.05754],[18.82176,48.04206],[18.817,47.9998],[18.76614,47.98199],[18.76821,47.87469],[18.8506,47.82308],[18.74074,47.8157],[18.71567,47.77579],[18.61942,47.75663],[18.55672,47.76749],[18.49033,47.75225],[18.45668,47.76286],[18.29305,47.73541],[18.02938,47.75665],[17.83355,47.74209],[17.71215,47.7548],[17.23699,48.02094],[17.16001,48.00636],[17.09786,47.97336],[17.11227,47.92685],[17.08275,47.87719],[17.00696,47.86411],[17.07347,47.80944],[17.05048,47.79377],[17.08953,47.70841],[16.87271,47.68802],[16.86757,47.72279],[16.82663,47.6831],[16.74886,47.68155],[16.71179,47.73676],[16.65679,47.74197],[16.61183,47.76171],[16.54779,47.75074],[16.53514,47.73837],[16.55129,47.72268],[16.4222,47.66537],[16.58699,47.61772],[16.64193,47.63114],[16.71059,47.52692],[16.64821,47.50155],[16.6718,47.46139],[16.57152,47.40868],[16.52414,47.41007],[16.49908,47.39416],[16.45104,47.41181],[16.47782,47.25918],[16.44142,47.25079],[16.43663,47.21127],[16.41739,47.20649],[16.42801,47.18422],[16.4523,47.18812],[16.46442,47.16845],[16.44932,47.14418],[16.52863,47.13974],[16.46134,47.09395],[16.52176,47.05747],[16.43936,47.03548],[16.51369,47.00084],[16.28202,47.00159],[16.27688,46.96312],[16.25989,46.96016],[16.22403,46.939],[16.19904,46.94134],[16.11282,46.86858]]]}},{"type":"Feature","properties":{"id":"IR"},"geometry":{"type":"Polygon","coordinates":[[[44.03667,39.39223],[44.1043,39.19842],[44.20946,39.13975],[44.18863,38.93881],[44.30322,38.81581],[44.26155,38.71427],[44.28065,38.6465],[44.32058,38.62752],[44.3207,38.49799],[44.3119,38.37887],[44.38309,38.36117],[44.44386,38.38295],[44.50115,38.33939],[44.42476,38.25763],[44.22509,37.88859],[44.3883,37.85433],[44.45948,37.77065],[44.55498,37.783],[44.62096,37.71985],[44.56887,37.6429],[44.61401,37.60165],[44.58449,37.45018],[44.81021,37.2915],[44.75986,37.21549],[44.7868,37.16644],[44.78319,37.1431],[44.75229,37.11958],[44.81611,37.04383],[44.89862,37.01897],[44.91199,36.91468],[44.90173,36.86096],[44.83479,36.81362],[44.84725,36.77622],[45.01537,36.75128],[45.06985,36.6814],[45.06985,36.62645],[45.00759,36.5402],[45.11811,36.40751],[45.23953,36.43257],[45.27394,36.35846],[45.26261,36.3001],[45.30038,36.27769],[45.32813,36.15409],[45.37312,36.09917],[45.37652,36.06222],[45.33885,35.99647],[45.38275,35.97156],[45.46594,36.00042],[45.55245,35.99943],[45.60018,35.96069],[45.6645,35.92872],[45.76145,35.79898],[45.81442,35.82107],[45.89784,35.83708],[45.94711,35.82218],[46.08325,35.8581],[46.1654,35.79832],[46.32921,35.82655],[46.34166,35.78363],[46.23736,35.71414],[46.01631,35.69139],[46.0117,35.65059],[45.99452,35.63574],[46.0165,35.61501],[46.01307,35.59756],[46.03028,35.57416],[45.97263,35.58389],[46.01518,35.52012],[45.98453,35.49848],[46.05358,35.38568],[46.13152,35.32548],[46.15474,35.2883],[46.11476,35.23545],[46.18978,35.22549],[46.19738,35.18536],[46.15897,35.1658],[46.15642,35.1268],[46.19116,35.11097],[46.11763,35.07551],[46.07747,35.0838],[46.06508,35.03699],[46.00241,35.06618],[45.96825,35.07412],[45.94156,35.0895],[45.92203,35.09538],[45.92173,35.0465],[45.87864,35.03441],[45.89477,34.95805],[45.86532,34.89858],[45.78904,34.91135],[45.79682,34.85133],[45.73641,34.83975],[45.70031,34.82322],[45.68284,34.76624],[45.65672,34.7222],[45.70031,34.69277],[45.74123,34.54212],[45.5972,34.5499],[45.53219,34.60441],[45.51883,34.47692],[45.43879,34.45949],[45.48219,34.34244],[45.54075,34.35077],[45.58667,34.30147],[45.56528,34.14377],[45.47704,34.071],[45.47264,34.03099],[45.41077,33.97421],[45.42789,33.9458],[45.50261,33.94968],[45.60647,33.80126],[45.77814,33.60938],[45.89801,33.63661],[45.96183,33.55751],[45.86687,33.49263],[45.99919,33.5082],[46.2121,33.19847],[46.11974,33.11656],[46.05297,33.1272],[46.03966,33.09577],[46.1594,33.0714],[46.09103,32.98354],[46.17198,32.95612],[46.32298,32.9731],[46.46788,32.91992],[47.17218,32.45393],[47.37529,32.47808],[47.57144,32.20583],[47.52474,32.15972],[47.64771,32.07666],[47.86337,31.78422],[47.6804,31.39086],[47.68219,31.00004],[48.03085,31.00498],[48.02443,30.4789],[48.14585,30.44133],[48.18321,30.39703],[48.19425,30.32796],[48.21279,30.31644],[48.24385,30.33846],[48.26393,30.3408],[48.41117,30.19846],[48.41671,30.17254],[48.38714,30.13485],[48.38869,30.11062],[48.43384,30.08233],[48.4494,30.04456],[48.44785,30.00148],[48.51011,29.96238],[48.61441,29.93675],[48.83867,29.78572],[49.98877,27.87827],[50.37726,27.89227],[54.86679,25.39571],[55.81777,26.18798],[56.70052,26.88164],[56.82555,25.7713],[56.86325,25.03856],[61.54432,24.57536],[61.683,25.66638],[61.83968,25.7538],[61.83831,26.07249],[61.89391,26.26251],[62.05117,26.31647],[62.21304,26.26601],[62.31256,26.51988],[62.77352,26.64099],[63.19353,26.64316],[63.18688,26.83844],[63.25005,26.84212],[63.25005,27.08692],[63.32828,27.13461],[63.19649,27.25674],[62.80815,27.21341],[62.79684,27.34381],[62.84905,27.47627],[62.77107,28.08652],[62.79412,28.28108],[62.59499,28.24842],[62.40259,28.42703],[61.93581,28.55284],[61.63673,28.82151],[61.55055,28.97766],[61.31508,29.38903],[60.87249,29.8597],[61.80829,30.84224],[61.77732,30.92593],[61.82985,30.97731],[61.83257,31.0452],[61.7962,31.17755],[61.70929,31.37391],[60.85498,31.48782],[60.87558,32.20873],[60.58547,33.1341],[60.87706,33.49274],[60.94802,33.51535],[60.64147,33.57712],[60.5485,33.73422],[60.5838,33.80793],[60.51097,34.10356],[60.70529,34.30912],[60.91321,34.30411],[60.74684,34.5173],[61.00192,34.62558],[61.00197,34.70631],[61.06926,34.82139],[61.12831,35.09938],[61.0991,35.27845],[61.19041,35.29355],[61.27349,35.60734],[61.22719,35.67038],[61.26152,35.80749],[61.22444,35.92879],[61.11848,35.96063],[61.23332,36.11319],[61.14646,36.39061],[61.18187,36.55348],[61.14516,36.64644],[60.34767,36.63214],[60.03959,37.02612],[59.74678,37.12499],[59.55178,37.13594],[59.54606,37.177],[59.38968,37.33931],[59.39797,37.47892],[59.33507,37.53146],[59.22905,37.51161],[58.9338,37.67374],[58.6921,37.64548],[58.55335,37.70745],[58.47786,37.6433],[58.4028,37.62837],[58.22999,37.6856],[58.21399,37.77281],[57.79534,37.89299],[57.35042,37.98546],[57.37236,38.09321],[57.21169,38.28965],[57.02659,38.18328],[56.78815,38.25301],[56.75794,38.284],[56.62255,38.24005],[56.43303,38.26054],[56.32454,38.18502],[56.33278,38.08132],[55.97847,38.08024],[55.76561,38.12238],[55.44152,38.08564],[55.13412,37.94705],[54.851,37.75739],[54.77684,37.62264],[54.81804,37.61285],[54.77822,37.51597],[54.67247,37.43532],[54.58664,37.45809],[54.36211,37.34912],[54.24565,37.32047],[53.89734,37.3464],[49.20805,38.40869],[48.88288,38.43975],[48.84969,38.45015],[48.78979,38.45026],[48.70001,38.40564],[48.62217,38.40198],[48.58793,38.45076],[48.45084,38.61013],[48.3146,38.59958],[48.24773,38.71883],[48.02209,38.83422],[48.01409,38.90333],[48.07734,38.91616],[48.08475,38.94111],[48.28456,38.96314],[48.33884,39.03022],[48.30636,39.10408],[48.15067,39.20046],[48.13076,39.2498],[48.15984,39.30028],[48.37385,39.37584],[48.34264,39.42935],[47.98977,39.70999],[47.84774,39.66285],[47.50099,39.49615],[47.38978,39.45999],[47.31301,39.37492],[47.05927,39.24846],[47.05736,39.19301],[46.95539,39.13432],[46.92539,39.16644],[46.83822,39.13143],[46.75752,39.03231],[46.53497,38.86548],[46.34059,38.92076],[46.20601,38.85262],[46.14785,38.84206],[46.06766,38.87861],[46.00228,38.87376],[45.94624,38.89072],[45.90266,38.87739],[45.83883,38.90768],[45.65172,38.95199],[45.6155,38.94304],[45.6131,38.964],[45.44966,38.99243],[45.44811,39.04927],[45.40452,39.07224],[45.40148,39.09007],[45.30489,39.18333],[45.16168,39.21952],[45.08751,39.35052],[45.05932,39.36435],[44.96746,39.42998],[44.88916,39.59653],[44.81043,39.62677],[44.71806,39.71124],[44.65422,39.72163],[44.6137,39.78393],[44.47298,39.68788],[44.48111,39.61579],[44.41849,39.56659],[44.42832,39.4131],[44.37921,39.4131],[44.29818,39.378],[44.22452,39.4169],[44.03667,39.39223]]]}},{"type":"Feature","properties":{"id":"IQ"},"geometry":{"type":"Polygon","coordinates":[[[38.79171,33.37328],[39.08202,32.50304],[38.98762,32.47694],[39.04251,32.30203],[39.26157,32.35555],[39.29903,32.23259],[39.19715,32.15468],[40.42419,31.94517],[41.4418,31.37357],[42.05521,31.13963],[42.97796,30.48295],[44.72255,29.19736],[46.42415,29.05947],[46.5527,29.10283],[46.89695,29.50584],[47.15166,30.01044],[47.37192,30.10421],[47.68341,30.10474],[48.00029,29.97263],[48.06782,30.02906],[48.17332,30.02448],[48.40479,29.85763],[48.59531,29.66815],[48.83867,29.78572],[48.61441,29.93675],[48.51011,29.96238],[48.44785,30.00148],[48.4494,30.04456],[48.43384,30.08233],[48.38869,30.11062],[48.38714,30.13485],[48.41671,30.17254],[48.41117,30.19846],[48.26393,30.3408],[48.24385,30.33846],[48.21279,30.31644],[48.19425,30.32796],[48.18321,30.39703],[48.14585,30.44133],[48.02443,30.4789],[48.03085,31.00498],[47.68219,31.00004],[47.6804,31.39086],[47.86337,31.78422],[47.64771,32.07666],[47.52474,32.15972],[47.57144,32.20583],[47.37529,32.47808],[47.17218,32.45393],[46.46788,32.91992],[46.32298,32.9731],[46.17198,32.95612],[46.09103,32.98354],[46.1594,33.0714],[46.03966,33.09577],[46.05297,33.1272],[46.11974,33.11656],[46.2121,33.19847],[45.99919,33.5082],[45.86687,33.49263],[45.96183,33.55751],[45.89801,33.63661],[45.77814,33.60938],[45.60647,33.80126],[45.50261,33.94968],[45.42789,33.9458],[45.41077,33.97421],[45.47264,34.03099],[45.47704,34.071],[45.56528,34.14377],[45.58667,34.30147],[45.54075,34.35077],[45.48219,34.34244],[45.43879,34.45949],[45.51883,34.47692],[45.53219,34.60441],[45.5972,34.5499],[45.74123,34.54212],[45.70031,34.69277],[45.65672,34.7222],[45.68284,34.76624],[45.70031,34.82322],[45.73641,34.83975],[45.79682,34.85133],[45.78904,34.91135],[45.86532,34.89858],[45.89477,34.95805],[45.87864,35.03441],[45.92173,35.0465],[45.92203,35.09538],[45.94156,35.0895],[45.96825,35.07412],[46.00241,35.06618],[46.06508,35.03699],[46.07747,35.0838],[46.11763,35.07551],[46.19116,35.11097],[46.15642,35.1268],[46.15897,35.1658],[46.19738,35.18536],[46.18978,35.22549],[46.11476,35.23545],[46.15474,35.2883],[46.13152,35.32548],[46.05358,35.38568],[45.98453,35.49848],[46.01518,35.52012],[45.97263,35.58389],[46.03028,35.57416],[46.01307,35.59756],[46.0165,35.61501],[45.99452,35.63574],[46.0117,35.65059],[46.01631,35.69139],[46.23736,35.71414],[46.34166,35.78363],[46.32921,35.82655],[46.1654,35.79832],[46.08325,35.8581],[45.94711,35.82218],[45.89784,35.83708],[45.81442,35.82107],[45.76145,35.79898],[45.6645,35.92872],[45.60018,35.96069],[45.55245,35.99943],[45.46594,36.00042],[45.38275,35.97156],[45.33885,35.99647],[45.37652,36.06222],[45.37312,36.09917],[45.32813,36.15409],[45.30038,36.27769],[45.26261,36.3001],[45.27394,36.35846],[45.23953,36.43257],[45.11811,36.40751],[45.00759,36.5402],[45.06985,36.62645],[45.06985,36.6814],[45.01537,36.75128],[44.84725,36.77622],[44.83479,36.81362],[44.90173,36.86096],[44.91199,36.91468],[44.89862,37.01897],[44.81611,37.04383],[44.75229,37.11958],[44.78319,37.1431],[44.76698,37.16162],[44.63179,37.19229],[44.51514,37.1029],[44.4154,37.05216],[44.39429,37.04949],[44.38292,37.05914],[44.35315,37.04955],[44.35937,37.02843],[44.30897,36.96347],[44.25975,36.98119],[44.18503,37.09551],[44.22239,37.15756],[44.27998,37.16501],[44.2613,37.25055],[44.12984,37.31952],[44.01638,37.32904],[43.90949,37.22453],[43.84878,37.22205],[43.82699,37.19477],[43.8052,37.22825],[43.7009,37.23692],[43.63085,37.21957],[43.56702,37.25675],[43.50122,37.24262],[43.33508,37.33105],[43.30535,37.30355],[43.12683,37.37656],[42.94967,37.3157],[42.78887,37.38615],[42.56725,37.14878],[42.35212,37.10858],[42.36697,37.0627],[42.00416,36.74411],[41.97566,36.70737],[41.81736,36.58782],[41.40058,36.52502],[41.28864,36.35368],[41.2564,36.06012],[41.37027,35.84095],[41.38184,35.62502],[41.26569,35.42708],[41.21654,35.1508],[41.2345,34.80049],[41.12388,34.65742],[40.97676,34.39788],[40.64314,34.31604],[38.79171,33.37328]]]}},{"type":"Feature","properties":{"id":"IL"},"geometry":{"type":"Polygon","coordinates":[[[33.62659,31.82938],[34.052,31.46619],[34.29262,31.70393],[34.48681,31.59711],[34.56797,31.54197],[34.54925,31.51504],[34.5108,31.50026],[34.40077,31.40926],[34.36505,31.36404],[34.37381,31.30598],[34.36523,31.28963],[34.29417,31.24194],[34.26742,31.21998],[34.92298,29.45305],[34.97718,29.54294],[34.98207,29.58147],[35.02147,29.66343],[35.14108,30.07374],[35.19183,30.34636],[35.16218,30.43535],[35.19595,30.50297],[35.21379,30.60401],[35.29311,30.71365],[35.33456,30.81224],[35.33984,30.8802],[35.41371,30.95565],[35.43658,31.12444],[35.40316,31.25535],[35.47672,31.49578],[35.39675,31.49572],[35.22921,31.37445],[35.13033,31.3551],[35.02459,31.35979],[34.92571,31.34337],[34.88932,31.37093],[34.87833,31.39321],[34.89978,31.43657],[34.93128,31.47362],[34.9445,31.5067],[34.9415,31.55601],[34.95231,31.5944],[35.00638,31.65177],[35.08226,31.69107],[35.10782,31.71594],[35.11895,31.71454],[35.12723,31.73017],[35.13537,31.7346],[35.13807,31.72847],[35.15079,31.73665],[35.15474,31.73352],[35.16478,31.73242],[35.18023,31.72067],[35.20573,31.72358],[35.21937,31.71578],[35.22454,31.71904],[35.23884,31.70953],[35.24315,31.71244],[35.2438,31.7201],[35.24981,31.72543],[35.25207,31.73904],[35.263,31.74829],[35.25233,31.76648],[35.26049,31.79103],[35.25573,31.81362],[35.26469,31.82597],[35.251,31.83085],[35.25753,31.8387],[35.24701,31.84624],[35.2303,31.84136],[35.2245,31.85386],[35.22817,31.8638],[35.22567,31.86745],[35.22294,31.87889],[35.22014,31.88264],[35.2136,31.88241],[35.21276,31.88153],[35.21016,31.88237],[35.20945,31.8815],[35.20791,31.8821],[35.20673,31.88151],[35.20381,31.86716],[35.21128,31.863],[35.216,31.83894],[35.21469,31.81835],[35.19461,31.82687],[35.18169,31.82542],[35.18603,31.80901],[35.13874,31.81412],[35.10835,31.82528],[35.07677,31.85627],[35.05617,31.85685],[35.01978,31.82944],[34.97566,31.83396],[34.99712,31.85569],[35.03489,31.85919],[35.03978,31.89276],[35.03973,31.92222],[35.00578,31.92889],[34.98682,31.96935],[35.00261,32.027],[34.9863,32.09551],[34.99437,32.10962],[34.98507,32.12606],[34.99039,32.14626],[34.96009,32.17503],[34.95703,32.19522],[34.98885,32.20758],[35.01841,32.23981],[35.02939,32.2671],[35.01119,32.28684],[35.01772,32.33863],[35.04243,32.35008],[35.05142,32.3667],[35.0421,32.38242],[35.05311,32.4024],[35.05423,32.41754],[35.07059,32.4585],[35.08564,32.46948],[35.09236,32.47614],[35.10024,32.47856],[35.10882,32.4757],[35.15937,32.50466],[35.2244,32.55289],[35.25049,32.52453],[35.29306,32.50947],[35.30685,32.51024],[35.35212,32.52047],[35.40224,32.50136],[35.42034,32.46009],[35.41598,32.45593],[35.41048,32.43706],[35.42078,32.41562],[35.55807,32.38674],[35.55494,32.42687],[35.57485,32.48669],[35.56614,32.64393],[35.59813,32.65159],[35.61669,32.67999],[35.66411,32.6802],[35.6799,32.7057],[35.75983,32.74803],[35.83758,32.82817],[35.84632,32.87382],[35.87012,32.91976],[35.89298,32.9456],[35.87188,32.98028],[35.84802,33.1031],[35.81911,33.11077],[35.81911,33.1336],[35.84285,33.16673],[35.83846,33.19397],[35.81647,33.2028],[35.81295,33.24841],[35.77513,33.27342],[35.813,33.3172],[35.77477,33.33609],[35.65372,33.27679],[35.64316,33.28045],[35.61982,33.27156],[35.62283,33.24226],[35.58502,33.26653],[35.58326,33.28381],[35.56523,33.28969],[35.55555,33.25844],[35.5466,33.25437],[35.54808,33.236],[35.5362,33.23196],[35.54228,33.19865],[35.52094,33.11778],[35.50335,33.114],[35.50272,33.09056],[35.448,33.09264],[35.43059,33.06659],[35.35223,33.05617],[35.31429,33.10515],[35.22937,33.09556],[35.21461,33.09973],[35.1935,33.08622],[35.17787,33.09413],[35.15487,33.09039],[35.10645,33.09318],[34.78515,33.20368],[33.62659,31.82938]]]}},{"type":"Feature","properties":{"id":"JM"},"geometry":{"type":"Polygon","coordinates":[[[-78.75694,18.78765],[-78.34606,16.57862],[-75.50728,17.08879],[-76.34192,18.86145],[-78.75694,18.78765]]]}},{"type":"Feature","properties":{"id":"JO"},"geometry":{"type":"Polygon","coordinates":[[[34.88293,29.37455],[34.95987,29.35727],[36.07081,29.18469],[36.50005,29.49696],[36.75083,29.86903],[37.4971,29.99949],[37.66395,30.33245],[37.99354,30.49998],[36.99791,31.50081],[38.99233,31.99721],[39.19715,32.15468],[39.29903,32.23259],[39.26157,32.35555],[39.04251,32.30203],[38.98762,32.47694],[39.08202,32.50304],[38.79171,33.37328],[36.83946,32.31293],[36.71424,32.31557],[36.40354,32.37735],[36.24046,32.49991],[36.20875,32.49529],[36.20379,32.52751],[36.08074,32.51463],[36.02239,32.65911],[35.96633,32.66237],[35.93307,32.71966],[35.88409,32.71371],[35.75983,32.74803],[35.6799,32.7057],[35.66411,32.6802],[35.61669,32.67999],[35.59813,32.65159],[35.56614,32.64393],[35.57485,32.48669],[35.55494,32.42687],[35.55807,32.38674],[35.57111,32.21877],[35.52012,32.04076],[35.54375,31.96587],[35.52758,31.9131],[35.55941,31.76535],[35.47672,31.49578],[35.40316,31.25535],[35.43658,31.12444],[35.41371,30.95565],[35.33984,30.8802],[35.33456,30.81224],[35.29311,30.71365],[35.21379,30.60401],[35.19595,30.50297],[35.16218,30.43535],[35.19183,30.34636],[35.14108,30.07374],[35.02147,29.66343],[34.98207,29.58147],[34.97718,29.54294],[34.92298,29.45305],[34.88293,29.37455]]]}},{"type":"Feature","properties":{"id":"KZ"},"geometry":{"type":"Polygon","coordinates":[[[46.49011,48.43019],[47.11516,48.27188],[47.12107,47.83687],[47.38731,47.68176],[47.41689,47.83687],[47.64973,47.76559],[48.15348,47.74545],[48.45173,47.40818],[48.52326,47.4102],[49.01136,46.72716],[48.51142,46.69268],[48.54988,46.56267],[49.16518,46.38542],[49.32259,46.26944],[49.88945,46.04554],[50.07531,44.32066],[52.26048,41.69249],[52.47884,41.78034],[52.97575,42.1308],[54.20635,42.38477],[54.95182,41.92424],[55.45471,41.25609],[56.00314,41.32584],[55.99849,44.99862],[58.58792,45.59067],[61.01475,44.41383],[62.01711,43.51008],[64.53885,43.56941],[66.09482,42.93426],[66.69129,41.1311],[67.9644,41.14611],[67.98511,41.02794],[68.08273,41.08148],[68.12278,41.04181],[68.07523,41.02524],[68.0821,40.97873],[67.9676,40.82809],[68.48722,40.5721],[68.6618,40.59961],[68.62506,40.63089],[68.64875,40.66293],[68.56155,40.80627],[68.5794,40.92129],[68.49983,40.99669],[68.62221,41.03019],[68.64875,40.94373],[68.75278,40.97795],[68.7145,41.05812],[68.91586,41.17994],[69.02778,41.23483],[69.03121,41.26761],[69.01525,41.28606],[69.03242,41.30347],[69.0519,41.3683],[69.07902,41.37751],[69.08803,41.3694],[69.08031,41.35787],[69.0961,41.35812],[69.11481,41.39213],[69.12717,41.38949],[69.17275,41.40185],[69.15137,41.43078],[69.17701,41.43769],[69.18528,41.45175],[69.20439,41.45391],[69.22671,41.46298],[69.23332,41.45847],[69.25059,41.46693],[69.29778,41.43673],[69.35554,41.47211],[69.37468,41.46555],[69.45081,41.46246],[69.39485,41.51518],[69.45751,41.56863],[69.49545,41.545],[70.94483,42.26238],[70.85973,42.30188],[70.97717,42.50147],[71.15232,42.60486],[71.17807,42.67381],[71.22785,42.69248],[71.27002,42.77272],[71.53272,42.8014],[71.62405,42.76613],[71.88792,42.83578],[73.45355,42.42294],[73.50992,42.82356],[73.5102,42.9167],[73.55634,43.03071],[73.89816,43.12604],[73.96064,43.20392],[74.22489,43.24657],[74.57639,43.13268],[74.64615,43.05881],[74.70331,43.02519],[74.75,42.99029],[74.83217,43.00088],[75.2251,42.85507],[75.29565,42.85973],[75.54988,42.83116],[75.72174,42.79672],[75.82823,42.94848],[78.48469,42.89649],[78.91502,42.76839],[79.19763,42.804],[79.18807,42.69051],[79.52921,42.44778],[79.97364,42.42816],[80.17807,42.21166],[80.26841,42.23797],[80.16892,42.61137],[80.26886,42.8366],[80.38169,42.83142],[80.58999,42.9011],[80.3735,43.01557],[80.62913,43.141],[80.78817,43.14235],[80.77771,43.30065],[80.69718,43.32589],[80.75156,43.44948],[80.40031,44.10986],[80.40229,44.23319],[80.38384,44.63073],[79.88433,44.9016],[80.08655,45.0301],[81.73278,45.3504],[82.53478,45.16824],[82.58474,45.40027],[82.21792,45.56619],[83.06453,47.23658],[83.92184,46.98912],[84.73077,47.01394],[84.93995,46.87399],[85.22443,47.04816],[85.54294,47.06171],[85.69696,47.2898],[85.61067,47.49753],[85.5169,48.05493],[85.73581,48.3939],[86.38069,48.46064],[86.75343,48.70331],[86.73568,48.99918],[86.89258,49.13859],[87.28386,49.11626],[87.31465,49.23603],[87.03071,49.25142],[86.84881,49.51852],[86.60591,49.5968],[86.79056,49.74787],[86.6732,49.80874],[86.22413,49.49756],[85.24047,49.60239],[84.99198,50.06793],[84.29706,50.25115],[84.08386,50.64249],[83.8442,50.87375],[83.413,51.00079],[83.14607,51.00796],[82.99381,50.8974],[82.77168,50.91255],[82.57581,50.75258],[81.92796,50.79237],[81.47581,50.75177],[81.41248,50.97524],[81.06091,50.94833],[81.16999,51.15662],[80.80318,51.28262],[80.44819,51.20855],[80.47691,50.96815],[80.06183,50.85039],[80.07316,50.74427],[79.11255,52.01171],[77.90383,53.29807],[76.54243,53.99329],[76.44076,54.16017],[76.82266,54.1798],[76.93622,54.46385],[75.3668,54.07439],[75.43398,53.98652],[75.07405,53.80831],[74.71115,53.84402],[74.38945,53.45964],[73.99806,53.64097],[73.43879,53.43612],[73.23898,53.54887],[73.36807,53.78807],[73.69491,53.85698],[73.74778,54.07194],[73.28086,53.9383],[72.71026,54.1161],[72.43415,53.92685],[72.24575,54.37435],[72.17948,54.1389],[71.76784,54.23112],[71.76132,54.13911],[71.13098,54.12201],[71.01631,54.3045],[71.2017,54.32493],[71.18385,54.57803],[71.28822,54.65675],[71.09149,54.71004],[70.98197,54.88895],[70.99056,55.08622],[70.80482,55.28302],[70.46012,55.27598],[70.18615,55.14356],[69.72198,55.34906],[69.34224,55.36344],[68.93337,55.42706],[68.91654,55.32836],[68.73287,55.35472],[68.63159,55.21237],[68.19206,55.18823],[68.26661,55.09226],[68.22235,54.96263],[67.90099,54.9784],[67.70187,54.87818],[65.20174,54.55216],[65.24663,54.35721],[65.11033,54.33028],[64.97216,54.4212],[64.83032,54.37855],[63.97686,54.29763],[64.02715,54.22679],[63.91224,54.20013],[63.79486,54.27645],[63.0622,54.10651],[62.58651,54.05871],[62.56876,53.94047],[62.45931,53.90737],[62.37075,54.04386],[62.0046,54.03903],[62.03913,53.94768],[61.65318,54.02445],[61.56941,53.95703],[61.47603,54.08048],[61.3706,54.08464],[61.26863,53.92797],[60.99796,53.93699],[61.14283,53.90063],[61.22574,53.80268],[60.90626,53.62937],[61.55811,53.58048],[61.57185,53.50112],[61.37957,53.45887],[61.27864,53.51765],[61.15436,53.40809],[61.18629,53.2882],[61.67381,53.24138],[62.14574,53.09626],[62.12799,52.99133],[62.0422,52.96105],[61.24877,53.03584],[61.05842,52.92217],[60.71989,52.75923],[60.71693,52.66245],[60.84118,52.63912],[60.84709,52.52228],[60.98021,52.50068],[61.06853,52.3454],[60.78201,52.22067],[60.72581,52.15538],[60.51303,52.1575],[60.19925,51.99173],[59.99908,51.99397],[60.07392,51.87225],[60.51921,51.7929],[60.36787,51.66815],[60.5424,51.61675],[60.92401,51.61124],[60.95655,51.48615],[61.50677,51.40687],[61.55114,51.32746],[61.6813,51.25716],[61.56889,51.23679],[61.4431,50.80679],[60.81833,50.6629],[60.31914,50.67705],[60.17246,50.84041],[60.01288,50.8163],[59.81172,50.54451],[59.51886,50.49937],[59.48928,50.64216],[58.87974,50.70852],[58.61824,51.02455],[58.25946,51.14489],[58.13037,51.07182],[57.75578,51.13852],[57.74986,50.93017],[57.44221,50.88354],[57.17302,51.11253],[56.17906,50.93204],[56.11398,50.7471],[55.67774,50.54508],[54.72067,51.03261],[54.56685,51.01958],[54.71476,50.61214],[54.55797,50.52006],[54.41894,50.61214],[54.52308,50.83803],[54.20516,50.96923],[54.12248,51.11542],[53.69299,51.23466],[53.46165,51.49445],[52.55207,51.47389],[52.36119,51.74161],[51.8246,51.67916],[51.77431,51.49536],[51.28829,51.48886],[51.26254,51.68466],[50.59695,51.61859],[50.26859,51.28677],[49.97277,51.2405],[49.76866,51.11067],[49.39001,51.09396],[49.41959,50.85927],[49.12673,50.78639],[48.86936,50.61589],[48.61244,50.63291],[48.88572,50.00597],[48.68352,49.89546],[48.42564,49.82283],[48.24519,49.86099],[48.10044,50.09242],[47.58551,50.47867],[47.30448,50.30894],[47.34589,50.09308],[47.18319,49.93721],[46.9078,49.86707],[46.78398,49.34026],[46.98795,49.23531],[47.04416,49.17152],[47.01458,49.07085],[46.91104,48.99715],[46.78392,48.95352],[46.49011,48.43019]]]}},{"type":"Feature","properties":{"id":"KE"},"geometry":{"type":"Polygon","coordinates":[[[33.90936,0.10581],[33.93107,-0.99298],[34.02286,-1.00779],[34.03084,-1.05101],[34.0824,-1.02264],[36.78686,-2.54798],[37.51195,-2.95435],[37.67297,-3.06081],[37.71177,-3.30813],[37.57931,-3.44551],[37.63099,-3.50723],[37.75036,-3.54243],[37.81321,-3.69179],[39.21631,-4.67835],[39.56066,-4.99867],[39.62121,-4.68136],[41.75542,-1.85308],[41.55967,-1.66272],[41.56,-1.59812],[41.00099,-0.83068],[40.98767,2.82959],[41.31368,3.14314],[41.90586,3.98059],[41.83216,3.94885],[41.71371,3.99064],[41.1754,3.94079],[40.77498,4.27683],[39.86043,3.86974],[39.76808,3.67058],[39.58339,3.47434],[39.55132,3.39634],[39.51551,3.40895],[39.49444,3.45521],[39.19954,3.47834],[39.09261,3.53286],[39.05442,3.51941],[39.03562,3.52618],[38.91938,3.51198],[38.52336,3.62551],[38.45812,3.60445],[38.14168,3.62487],[37.07724,4.33503],[36.84474,4.44518],[36.03924,4.44406],[35.95449,4.53244],[35.9419,4.61933],[35.51424,4.61643],[35.42366,4.76969],[35.47843,4.91872],[35.30992,4.90402],[35.34151,5.02364],[34.47601,4.72162],[33.9873,4.23316],[34.06046,4.15235],[34.15429,3.80464],[34.45815,3.67385],[34.44922,3.51627],[34.39112,3.48802],[34.41794,3.44342],[34.40006,3.37949],[34.45815,3.18319],[34.56242,3.11478],[34.60114,2.93034],[34.65774,2.8753],[34.73967,2.85447],[34.78137,2.76223],[34.77244,2.70272],[34.95267,2.47209],[34.90947,2.42447],[34.98424,1.98512],[34.99978,1.96213],[34.99402,1.66316],[34.94132,1.5741],[34.87524,1.53361],[34.7918,1.36752],[34.82606,1.30944],[34.83189,1.26864],[34.79747,1.22067],[34.67001,1.20797],[34.58029,1.14712],[34.57427,1.09868],[34.52369,1.10692],[34.44492,0.85703],[34.42218,0.8472],[34.40952,0.82832],[34.41471,0.80832],[34.38613,0.79952],[34.3839,0.78953],[34.37858,0.79476],[34.3718,0.78167],[34.31545,0.76142],[34.31304,0.69598],[34.28,0.67873],[34.27867,0.64075],[34.20196,0.62289],[34.13493,0.58118],[34.11881,0.52244],[34.11941,0.48356],[34.08946,0.45472],[34.10868,0.36958],[33.90936,0.10581]]]}},{"type":"Feature","properties":{"id":"KH"},"geometry":{"type":"Polygon","coordinates":[[[102.33412,13.5533],[102.361,13.50551],[102.35563,13.47307],[102.35987,13.39162],[102.34537,13.3487],[102.36001,13.31142],[102.36202,13.26475],[102.43422,13.09061],[102.46011,13.08057],[102.49814,13.01074],[102.53067,13.00179],[102.47943,12.97879],[102.49335,12.92711],[102.50068,12.91719],[102.53053,12.77506],[102.4994,12.71736],[102.51963,12.66117],[102.57567,12.65358],[102.7796,12.43781],[102.78116,12.40284],[102.73134,12.37091],[102.70176,12.1686],[102.77026,12.06815],[102.78427,11.98746],[102.83957,11.8519],[102.90973,11.75613],[102.91449,11.65512],[102.52395,11.25257],[102.47649,9.66162],[103.99198,10.48391],[104.43778,10.42386],[104.47963,10.43046],[104.49869,10.4057],[104.59267,10.53296],[104.87933,10.52833],[104.95762,10.64053],[105.10345,10.72268],[105.06431,10.79182],[105.02722,10.89236],[105.08326,10.95656],[105.11778,10.96209],[105.11795,10.94473],[105.10285,10.92121],[105.24404,10.90647],[105.34011,10.86179],[105.42832,10.9743],[105.49836,10.95324],[105.66015,10.98602],[105.77533,11.03808],[105.86376,10.89839],[105.8464,10.86272],[105.93403,10.83853],[105.94535,10.9168],[106.06708,10.8098],[106.19247,10.79519],[106.14166,10.91607],[106.15453,10.98096],[106.20397,10.97557],[106.1757,11.07301],[106.1527,11.10476],[106.10444,11.07879],[105.86782,11.28343],[105.88962,11.43605],[105.87328,11.55953],[105.81645,11.56876],[105.80867,11.60536],[105.8507,11.66635],[105.88962,11.67854],[105.95188,11.63738],[106.00792,11.7197],[106.02038,11.77457],[106.06708,11.77761],[106.13158,11.73283],[106.18539,11.75171],[106.26478,11.72122],[106.30525,11.67549],[106.37219,11.69836],[106.44691,11.66787],[106.45158,11.68616],[106.41577,11.76999],[106.44535,11.8279],[106.44068,11.86294],[106.4687,11.86751],[106.4111,11.97413],[106.7126,11.97031],[106.79405,12.0807],[106.92325,12.06548],[106.99953,12.08983],[107.15831,12.27547],[107.34511,12.33327],[107.42917,12.24657],[107.4463,12.29373],[107.54447,12.35744],[107.5755,12.52177],[107.55993,12.7982],[107.49611,12.88926],[107.49144,13.01215],[107.62843,13.3668],[107.61909,13.52577],[107.53503,13.73908],[107.45252,13.78897],[107.46498,13.91593],[107.44318,13.99751],[107.38247,13.99147],[107.35757,14.02319],[107.37158,14.07906],[107.33577,14.11832],[107.40427,14.24509],[107.39493,14.32655],[107.44941,14.41552],[107.48521,14.40346],[107.52569,14.54665],[107.52102,14.59034],[107.55371,14.628],[107.54361,14.69092],[107.47238,14.61523],[107.44435,14.52785],[107.37897,14.54443],[107.3276,14.58812],[107.29803,14.58963],[107.26534,14.54292],[107.256,14.48716],[107.21241,14.48716],[107.17038,14.41782],[107.09722,14.3937],[107.03962,14.45099],[107.04585,14.41782],[106.98825,14.36806],[106.9649,14.3198],[106.90574,14.33639],[106.8497,14.29416],[106.80767,14.31226],[106.73762,14.42687],[106.63333,14.44194],[106.59908,14.50977],[106.57106,14.50525],[106.54148,14.59565],[106.50723,14.58963],[106.45898,14.55045],[106.47766,14.50977],[106.43874,14.52032],[106.40916,14.45249],[106.32355,14.44043],[106.25194,14.48415],[106.21302,14.36203],[106.00131,14.36957],[105.99509,14.32734],[106.04801,14.20363],[106.10872,14.18401],[106.11962,14.11307],[106.18656,14.06324],[106.16632,14.01794],[106.10094,13.98471],[106.10405,13.9137],[105.90734,13.93639],[105.78632,14.02601],[105.78769,14.0828],[105.5561,14.15684],[105.44869,14.10703],[105.36775,14.09948],[105.2759,14.17496],[105.19958,14.34173],[105.17748,14.34432],[105.14012,14.23873],[105.08408,14.20402],[105.02804,14.23722],[104.97667,14.38806],[104.69335,14.42726],[104.55014,14.36091],[104.27616,14.39861],[103.94405,14.32443],[103.70175,14.38052],[103.71109,14.4348],[103.53518,14.42575],[103.39353,14.35639],[103.16471,14.33424],[102.93275,14.19044],[102.89674,14.05632],[102.91251,14.01531],[102.86464,14.00353],[102.7831,13.93273],[102.76645,13.85608],[102.72388,13.79007],[102.73281,13.77273],[102.56848,13.69366],[102.5481,13.6589],[102.57095,13.63962],[102.57477,13.63003],[102.60101,13.62067],[102.62457,13.61041],[102.62483,13.60883],[102.58067,13.60657],[102.56235,13.57875],[102.51282,13.56848],[102.44601,13.5637],[102.36536,13.57749],[102.33412,13.5533]]]}},{"type":"Feature","properties":{"id":"KN"},"geometry":{"type":"Polygon","coordinates":[[[-63.11114,17.23125],[-62.8252,16.98937],[-62.62949,16.82364],[-62.27053,17.22145],[-62.45247,17.37627],[-62.76692,17.64353],[-63.11114,17.23125]]]}},{"type":"Feature","properties":{"id":"KR"},"geometry":{"type":"Polygon","coordinates":[[[124.36766,38.39606],[124.81566,33.9707],[125.67809,33.6695],[126.29984,32.61914],[127.37787,33.46942],[132.43845,37.34888],[128.65655,38.61914],[128.37487,38.62345],[128.31105,38.58462],[128.27652,38.41657],[128.02917,38.31861],[127.55013,38.32257],[127.49672,38.30647],[127.38727,38.33227],[127.15749,38.30722],[127.04479,38.25518],[126.95338,38.17735],[126.95887,38.1347],[126.88106,38.10246],[126.84961,38.0344],[126.67023,37.95852],[126.68793,37.9175],[126.68793,37.83728],[126.66067,37.7897],[126.59918,37.76364],[126.56709,37.76857],[126.46818,37.80873],[126.43239,37.84095],[126.1974,37.82546],[126.18398,37.72094],[125.81159,37.72949],[125.10527,37.56869],[124.36766,38.39606]]]}},{"type":"Feature","properties":{"id":"XK"},"geometry":{"type":"Polygon","coordinates":[[[20.01872,42.74965],[20.02824,42.7059],[20.09785,42.66312],[20.07761,42.55582],[20.16283,42.50627],[20.22591,42.41572],[20.24399,42.32168],[20.34479,42.32656],[20.3819,42.3029],[20.48857,42.25444],[20.56955,42.12097],[20.55633,42.08173],[20.59434,42.03879],[20.63069,41.94913],[20.57946,41.91593],[20.59524,41.8818],[20.68523,41.85318],[20.76786,41.91839],[20.75464,42.05229],[21.11491,42.20794],[21.16614,42.19815],[21.22728,42.08909],[21.31983,42.10993],[21.29913,42.13954],[21.30496,42.1418],[21.3872,42.24618],[21.44187,42.23493],[21.43882,42.2789],[21.50823,42.27156],[21.52145,42.24465],[21.58992,42.25915],[21.56772,42.30946],[21.52496,42.32967],[21.53467,42.36809],[21.57021,42.3647],[21.58118,42.38067],[21.62126,42.37699],[21.6428,42.41648],[21.62358,42.4531],[21.7035,42.51899],[21.70331,42.54568],[21.7458,42.5551],[21.73825,42.60168],[21.75672,42.62695],[21.79155,42.65296],[21.75025,42.70125],[21.65662,42.67208],[21.63353,42.69763],[21.58616,42.70363],[21.59154,42.72643],[21.47498,42.74695],[21.46711,42.73768],[21.38874,42.75602],[21.44047,42.87276],[21.36941,42.87397],[21.32974,42.90424],[21.27785,42.89539],[21.23534,42.95523],[21.23877,43.00848],[21.2041,43.02277],[21.16653,42.9983],[21.14465,43.11089],[21.08952,43.13471],[21.05378,43.10707],[21.00749,43.13984],[20.96287,43.12416],[20.83727,43.17842],[20.85016,43.20342],[20.88466,43.21956],[20.82145,43.26769],[20.72957,43.24651],[20.68688,43.21335],[20.59584,43.20398],[20.6579,43.15216],[20.69515,43.09641],[20.6615,43.07565],[20.64279,43.00477],[20.59859,43.02171],[20.54623,42.96094],[20.52954,42.97579],[20.50653,42.96282],[20.48392,42.93173],[20.53484,42.8885],[20.43734,42.83157],[20.40594,42.84853],[20.35692,42.8335],[20.2714,42.82159],[20.25303,42.75671],[20.1466,42.75652],[20.04898,42.77701],[20.01872,42.74965]]]}},{"type":"Feature","properties":{"id":"KW"},"geometry":{"type":"Polygon","coordinates":[[[46.5527,29.10283],[47.46202,29.0014],[47.58376,28.83382],[47.59863,28.66798],[47.70561,28.5221],[48.42991,28.53628],[49.00421,28.81495],[48.59531,29.66815],[48.40479,29.85763],[48.17332,30.02448],[48.06782,30.02906],[48.00029,29.97263],[47.68341,30.10474],[47.37192,30.10421],[47.15166,30.01044],[46.89695,29.50584],[46.5527,29.10283]]]}},{"type":"Feature","properties":{"id":"LA"},"geometry":{"type":"Polygon","coordinates":[[[100.08404,20.36626],[100.09755,20.31315],[100.09205,20.26678],[100.11785,20.24787],[100.17428,20.24633],[100.16758,20.30108],[100.22076,20.31598],[100.25899,20.39789],[100.33178,20.39926],[100.37439,20.35156],[100.38499,20.31267],[100.40611,20.282],[100.41392,20.25567],[100.45486,20.22837],[100.4537,20.19971],[100.47567,20.19133],[100.51623,20.14411],[100.55218,20.17741],[100.58808,20.15791],[100.5094,19.87904],[100.40293,19.75515],[100.44344,19.70829],[100.43005,19.67273],[100.49604,19.53504],[100.58219,19.49164],[100.64606,19.55884],[100.77231,19.48324],[100.90302,19.61901],[101.08928,19.59748],[101.26545,19.59242],[101.26991,19.48324],[101.21347,19.46223],[101.20604,19.35296],[101.24911,19.33334],[101.261,19.12717],[101.35606,19.04716],[101.25803,18.89545],[101.22832,18.73377],[101.27585,18.68875],[101.05035,18.42603],[101.13258,18.35876],[101.18227,18.34367],[101.15108,18.25624],[101.19118,18.2125],[101.1793,18.0544],[101.02185,17.87637],[100.96541,17.57926],[101.07679,17.50451],[101.15009,17.47021],[101.18279,17.49878],[101.17429,17.52407],[101.27506,17.61146],[101.28072,17.60852],[101.33145,17.6544],[101.3784,17.68254],[101.38106,17.69734],[101.399,17.69407],[101.42586,17.7204],[101.39625,17.72816],[101.40561,17.73601],[101.4299,17.72473],[101.45582,17.74639],[101.50062,17.74288],[101.57718,17.78587],[101.55255,17.80818],[101.55169,17.82175],[101.57546,17.86938],[101.59847,17.84724],[101.61529,17.89168],[101.66078,17.89985],[101.73339,17.92312],[101.72824,17.95154],[101.78154,18.07332],[101.89312,18.02983],[101.95527,18.09698],[102.03251,18.15792],[102.0459,18.19902],[102.08684,18.2203],[102.1628,18.20481],[102.18486,18.15058],[102.29627,18.05447],[102.3421,18.04729],[102.43266,17.98306],[102.51565,17.96901],[102.52793,17.96305],[102.55951,17.96771],[102.59234,17.96127],[102.61196,17.94729],[102.61453,17.9112],[102.5896,17.84889],[102.60045,17.83147],[102.63607,17.8318],[102.67607,17.80279],[102.69847,17.81766],[102.67543,17.84529],[102.688,17.87224],[102.75607,17.89225],[102.78885,17.93594],[102.81924,17.94092],[102.85743,17.97334],[102.90301,17.98297],[102.94867,18.00608],[102.9912,17.9949],[103.02137,17.96885],[103.0436,17.98371],[103.0775,18.0326],[103.07343,18.12351],[103.15166,18.17725],[103.15132,18.23367],[103.17054,18.25967],[103.29903,18.30458],[103.27946,18.33016],[103.23706,18.34132],[103.24693,18.38026],[103.30977,18.4341],[103.41044,18.4486],[103.45988,18.42619],[103.52588,18.42595],[103.62459,18.39582],[103.699,18.34125],[103.81273,18.3445],[103.85856,18.28282],[103.93916,18.33914],[103.97725,18.33631],[104.06533,18.21656],[104.10927,18.10826],[104.21776,17.99335],[104.2757,17.86139],[104.3514,17.82812],[104.45972,17.66152],[104.70725,17.52276],[104.80061,17.39367],[104.80716,17.19025],[104.73918,17.01476],[104.7373,16.91125],[104.76442,16.84752],[104.7397,16.81005],[104.76562,16.69605],[104.73349,16.565],[104.76013,16.50884],[104.85076,16.44695],[104.88057,16.37311],[105.01453,16.24664],[105.0468,16.11245],[105.22327,16.04779],[105.41484,16.01644],[105.42686,15.98987],[105.38508,15.987],[105.34446,15.92369],[105.38343,15.83982],[105.4339,15.75558],[105.46573,15.74742],[105.51458,15.77309],[105.61756,15.68792],[105.63783,15.63361],[105.59921,15.52282],[105.59852,15.45847],[105.58032,15.40552],[105.47635,15.3796],[105.4692,15.33709],[105.50662,15.32054],[105.58043,15.32724],[105.46661,15.13132],[105.61162,15.00037],[105.5121,14.80802],[105.53864,14.55731],[105.43783,14.43865],[105.19958,14.34173],[105.2759,14.17496],[105.36775,14.09948],[105.44869,14.10703],[105.5561,14.15684],[105.78769,14.0828],[105.78632,14.02601],[105.90734,13.93639],[106.10405,13.9137],[106.10094,13.98471],[106.16632,14.01794],[106.18656,14.06324],[106.11962,14.11307],[106.10872,14.18401],[106.04801,14.20363],[105.99509,14.32734],[106.00131,14.36957],[106.21302,14.36203],[106.25194,14.48415],[106.32355,14.44043],[106.40916,14.45249],[106.43874,14.52032],[106.47766,14.50977],[106.45898,14.55045],[106.50723,14.58963],[106.54148,14.59565],[106.57106,14.50525],[106.59908,14.50977],[106.63333,14.44194],[106.73762,14.42687],[106.80767,14.31226],[106.8497,14.29416],[106.90574,14.33639],[106.9649,14.3198],[106.98825,14.36806],[107.04585,14.41782],[107.03962,14.45099],[107.09722,14.3937],[107.17038,14.41782],[107.21241,14.48716],[107.256,14.48716],[107.26534,14.54292],[107.29803,14.58963],[107.3276,14.58812],[107.37897,14.54443],[107.44435,14.52785],[107.47238,14.61523],[107.54361,14.69092],[107.51579,14.79282],[107.59285,14.87795],[107.48277,14.93751],[107.46516,15.00982],[107.61486,15.0566],[107.61926,15.13949],[107.58844,15.20111],[107.62587,15.2266],[107.60605,15.37524],[107.62367,15.42193],[107.53341,15.40496],[107.50699,15.48771],[107.3815,15.49832],[107.34408,15.62345],[107.27583,15.62769],[107.27143,15.71459],[107.21859,15.74638],[107.21419,15.83747],[107.34188,15.89464],[107.39471,15.88829],[107.46296,16.01106],[107.44975,16.08511],[107.33968,16.05549],[107.25822,16.13587],[107.14595,16.17816],[107.15035,16.26271],[107.09091,16.3092],[107.02597,16.31132],[106.97385,16.30204],[106.96638,16.34938],[106.88067,16.43594],[106.88727,16.52671],[106.84104,16.55415],[106.74418,16.41904],[106.65832,16.47816],[106.64823,16.54324],[106.65081,16.59144],[106.58267,16.6012],[106.59013,16.62259],[106.55691,16.6477],[106.55265,16.86831],[106.51485,16.89243],[106.51963,16.92097],[106.54824,16.92729],[106.55045,17.0031],[106.50862,16.9673],[106.43383,17.00491],[106.31929,17.20509],[106.29287,17.3018],[106.24444,17.24714],[106.18991,17.28227],[106.09019,17.36399],[105.85744,17.63221],[105.76612,17.67147],[105.60381,17.89356],[105.64784,17.96687],[105.46292,18.22008],[105.38366,18.15315],[105.15942,18.38691],[105.10408,18.43533],[105.1327,18.58355],[105.19654,18.64196],[105.12829,18.70453],[104.64617,18.85668],[104.5361,18.97747],[103.87125,19.31854],[104.06058,19.43484],[104.10832,19.51575],[104.05617,19.61743],[104.06498,19.66926],[104.2,19.69221],[104.41281,19.70035],[104.53169,19.61743],[104.64837,19.62365],[104.68359,19.72729],[104.8355,19.80395],[104.8465,19.91783],[104.99221,20.09567],[104.91695,20.15567],[104.86852,20.14121],[104.61035,20.2452],[104.62142,20.29665],[104.62195,20.36633],[104.72102,20.40554],[104.65773,20.47558],[104.47886,20.37459],[104.40917,20.38252],[104.38024,20.4751],[104.48907,20.5325],[104.64597,20.65848],[104.55516,20.71902],[104.52341,20.69743],[104.49053,20.72609],[104.48161,20.7659],[104.4465,20.79744],[104.42873,20.7907],[104.32763,20.858],[104.29286,20.92079],[104.25853,20.89818],[104.21888,20.93827],[104.13579,20.94789],[104.10541,20.97386],[103.98053,20.9078],[103.80912,20.84982],[103.77428,20.73074],[103.73539,20.73123],[103.73478,20.6669],[103.68235,20.66177],[103.46477,20.82672],[103.4004,20.77986],[103.232,20.83442],[103.21497,20.89832],[103.12055,20.89994],[103.03469,21.05821],[102.97745,21.05821],[102.89825,21.24707],[102.80794,21.25736],[102.88939,21.3107],[102.94223,21.46034],[102.86297,21.4255],[102.98846,21.58936],[102.97965,21.74076],[102.86077,21.71213],[102.85637,21.84501],[102.81894,21.83888],[102.82115,21.73667],[102.74189,21.66713],[102.67145,21.65894],[102.62301,21.91447],[102.49092,21.99002],[102.51734,22.02676],[102.18712,22.30403],[102.14099,22.40092],[102.1245,22.43372],[102.03633,22.46164],[101.98487,22.42766],[101.91344,22.44417],[101.90714,22.38688],[101.86828,22.38397],[101.7685,22.50337],[101.68973,22.46843],[101.62074,22.27325],[101.56581,22.27428],[101.54882,22.23586],[101.60675,22.13513],[101.57525,22.13026],[101.62566,21.96574],[101.7791,21.83019],[101.74555,21.72852],[101.83257,21.61562],[101.80001,21.57461],[101.7475,21.5873],[101.7727,21.51794],[101.75142,21.45195],[101.74014,21.30967],[101.84412,21.25291],[101.83887,21.20983],[101.79025,21.20369],[101.76408,21.14204],[101.70548,21.14911],[101.66904,21.20272],[101.60886,21.17947],[101.59491,21.18621],[101.6068,21.23329],[101.54563,21.25668],[101.28767,21.1736],[101.21661,21.23234],[101.2504,21.29478],[101.19009,21.32487],[101.14013,21.40265],[101.19953,21.43709],[101.21618,21.55879],[101.15156,21.56129],[101.16198,21.52808],[101.08846,21.46057],[101.0555,21.45115],[100.9998,21.38674],[100.95851,21.38259],[100.89543,21.35102],[100.84994,21.3064],[100.80173,21.2934],[100.74059,21.31312],[100.72531,21.3124],[100.68351,21.14807],[100.64815,21.10652],[100.63734,21.06231],[100.54678,21.02202],[100.50579,20.88108],[100.54112,20.87084],[100.64446,20.89898],[100.64909,20.88118],[100.60086,20.83571],[100.51628,20.81632],[100.37422,20.83553],[100.28303,20.77393],[100.1893,20.67888],[100.1184,20.43698],[100.12475,20.41044],[100.08404,20.36626]]]}},{"type":"Feature","properties":{"id":"LB"},"geometry":{"type":"Polygon","coordinates":[[[34.78515,33.20368],[35.10645,33.09318],[35.15487,33.09039],[35.17787,33.09413],[35.1935,33.08622],[35.21461,33.09973],[35.22937,33.09556],[35.31429,33.10515],[35.35223,33.05617],[35.43059,33.06659],[35.448,33.09264],[35.50272,33.09056],[35.50335,33.114],[35.52094,33.11778],[35.54228,33.19865],[35.5362,33.23196],[35.54808,33.236],[35.5466,33.25437],[35.55555,33.25844],[35.56523,33.28969],[35.58326,33.28381],[35.58502,33.26653],[35.62283,33.24226],[35.61982,33.27156],[35.64316,33.28045],[35.65372,33.27679],[35.77477,33.33609],[35.81324,33.36354],[35.82577,33.40479],[35.88668,33.43183],[35.94816,33.47886],[35.94185,33.52801],[36.06425,33.57508],[35.9341,33.6596],[36.06897,33.82728],[36.15488,33.84974],[36.39804,33.83078],[36.38263,33.86579],[36.27651,33.9128],[36.41078,34.05253],[36.50576,34.05982],[36.5128,34.09916],[36.62537,34.20251],[36.59195,34.2316],[36.58121,34.27558],[36.60778,34.31009],[36.56091,34.32036],[36.52919,34.37007],[36.57279,34.40499],[36.48611,34.45773],[36.46499,34.46276],[36.44499,34.50372],[36.34745,34.5002],[36.33822,34.52232],[36.40328,34.55463],[36.40731,34.61399],[36.45152,34.58213],[36.46285,34.64087],[36.42452,34.62392],[36.40143,34.63712],[36.39083,34.63066],[36.37083,34.64083],[36.34938,34.66258],[36.35513,34.6834],[36.32399,34.69334],[36.30277,34.66787],[36.30569,34.64733],[36.29165,34.62991],[36.24286,34.63426],[36.22406,34.62692],[36.19548,34.63779],[36.17239,34.62854],[36.12497,34.64228],[36.11407,34.63387],[36.0903,34.62932],[36.07931,34.63412],[36.07386,34.62826],[36.06871,34.63345],[36.03412,34.62861],[35.98412,34.6511],[35.97653,34.63394],[35.48515,34.70851],[34.78515,33.20368]]]}},{"type":"Feature","properties":{"id":"LR"},"geometry":{"type":"Polygon","coordinates":[[[-12.15048,6.15992],[-7.52774,3.7105],[-7.53026,4.36335],[-7.56975,4.39775],[-7.53966,4.43798],[-7.56563,4.46057],[-7.55413,4.49916],[-7.55962,4.541],[-7.59349,4.8909],[-7.53876,4.94294],[-7.55369,5.08667],[-7.48901,5.14118],[-7.46165,5.26256],[-7.36463,5.32944],[-7.43428,5.42355],[-7.37209,5.61173],[-7.43926,5.74787],[-7.43677,5.84687],[-7.46057,5.86193],[-7.48155,5.80974],[-7.67309,5.94337],[-7.70294,5.90625],[-7.78254,5.99037],[-7.79747,6.07696],[-7.8497,6.08932],[-7.83478,6.20309],[-7.90692,6.27728],[-8.00642,6.31684],[-8.17557,6.28222],[-8.3298,6.36381],[-8.38453,6.35887],[-8.45397,6.50686],[-8.48453,6.42874],[-8.59645,6.50277],[-8.5338,6.59759],[-8.45449,6.64909],[-8.31819,6.8124],[-8.33261,6.89081],[-8.31235,6.90921],[-8.32162,6.96783],[-8.28746,6.9922],[-8.29931,7.13359],[-8.28266,7.17175],[-8.36351,7.25341],[-8.42634,7.5308],[-8.47114,7.55676],[-8.55603,7.62388],[-8.55371,7.69576],[-8.67576,7.69576],[-8.70666,7.63273],[-8.71061,7.5131],[-8.82502,7.38357],[-8.83858,7.27597],[-8.87557,7.25562],[-8.92596,7.28831],[-9.09107,7.1985],[-9.20259,7.32552],[-9.20798,7.38109],[-9.305,7.42056],[-9.41943,7.41809],[-9.48171,7.36672],[-9.37465,7.62032],[-9.35724,7.74111],[-9.44789,7.91286],[-9.41445,8.02448],[-9.50898,8.18455],[-9.47415,8.35195],[-9.55089,8.38084],[-9.65852,8.50038],[-9.77182,8.55301],[-9.82915,8.5014],[-9.91001,8.50089],[-10.05798,8.42279],[-10.05375,8.50697],[-10.15926,8.52737],[-10.16158,8.51642],[-10.19835,8.49389],[-10.20775,8.47984],[-10.27822,8.48816],[-10.2729,8.4435],[-10.30084,8.30008],[-10.31635,8.28554],[-10.29766,8.21072],[-10.35315,8.15049],[-10.37572,8.16552],[-10.45023,8.15627],[-10.51554,8.1393],[-10.57523,8.04829],[-10.60492,8.04072],[-10.60422,7.7739],[-10.70214,7.73302],[-10.79475,7.59019],[-10.92023,7.49668],[-10.94152,7.50834],[-11.07842,7.39293],[-11.10305,7.39115],[-11.14511,7.33965],[-11.14374,7.32194],[-11.25476,7.2285],[-11.27218,7.23931],[-11.30501,7.21364],[-11.32312,7.17064],[-11.35231,7.1399],[-11.33342,7.0784],[-11.37539,7.07661],[-11.37102,7.02278],[-11.39797,6.98819],[-11.41144,6.99032],[-11.45805,6.92702],[-11.50429,6.92704],[-12.15048,6.15992]]]}},{"type":"Feature","properties":{"id":"LY"},"geometry":{"type":"Polygon","coordinates":[[[9.3876,30.16738],[9.78136,29.40961],[9.90417,28.77066],[9.90255,26.50908],[9.51318,26.38471],[9.39863,26.1938],[10.03146,25.35635],[10.02432,24.98124],[10.33159,24.5465],[10.85323,24.5595],[11.41061,24.21456],[11.62498,24.26669],[11.96886,23.51735],[13.5631,23.16574],[14.22918,22.61719],[14.99751,23.00539],[16.00389,23.44852],[23.99539,19.49944],[23.99715,20.00038],[24.99794,19.99661],[24.99885,21.99535],[24.99968,29.24574],[24.71117,30.17441],[25.01077,30.73861],[24.82566,31.38031],[25.07251,31.55396],[25.09551,31.64052],[25.63787,31.9359],[22.5213,33.45682],[11.66543,33.34642],[11.56255,33.16754],[11.55852,33.1409],[11.52671,33.07888],[11.48483,32.64775],[11.61254,32.51381],[11.53898,32.4138],[10.91766,32.14247],[10.78479,31.98856],[10.65948,31.97429],[10.51803,31.73429],[10.31364,31.72648],[10.12239,31.42098],[10.29516,30.90337],[9.88152,30.34074],[9.55749,30.22843],[9.3876,30.16738]]]}},{"type":"Feature","properties":{"id":"LC"},"geometry":{"type":"Polygon","coordinates":[[[-61.43129,13.68336],[-60.70539,13.41452],[-60.5958,14.23076],[-61.26561,14.25664],[-61.43129,13.68336]]]}},{"type":"Feature","properties":{"id":"LI"},"geometry":{"type":"Polygon","coordinates":[[[9.47139,47.06402],[9.47548,47.05257],[9.54041,47.06495],[9.55721,47.04762],[9.60717,47.06091],[9.61216,47.07732],[9.63395,47.08443],[9.62623,47.14685],[9.56539,47.17124],[9.58264,47.20673],[9.56826,47.22016],[9.55214,47.22395],[9.56766,47.24281],[9.53116,47.27029],[9.52406,47.24959],[9.50318,47.22153],[9.4891,47.19346],[9.48876,47.16643],[9.51044,47.13727],[9.52089,47.10019],[9.51362,47.08505],[9.47139,47.06402]]]}},{"type":"Feature","properties":{"id":"LS"},"geometry":{"type":"Polygon","coordinates":[[[27.00177,-29.65352],[27.02443,-29.66679],[27.0967,-29.72998],[27.22719,-30.00718],[27.29603,-30.05473],[27.32555,-30.14785],[27.40479,-30.14275],[27.41084,-30.15143],[27.37293,-30.19401],[27.36649,-30.27246],[27.38108,-30.33456],[27.44041,-30.32176],[27.56901,-30.42504],[27.56781,-30.44562],[27.60572,-30.44941],[27.62137,-30.50509],[27.6521,-30.51707],[27.67134,-30.5342],[27.69069,-30.54093],[27.69467,-30.55862],[27.71734,-30.57146],[27.74382,-30.60589],[28.12073,-30.68072],[28.25752,-30.38827],[28.24653,-30.27211],[28.399,-30.1592],[28.66719,-30.14067],[28.68627,-30.12885],[28.80222,-30.10579],[28.9338,-30.05072],[29.16548,-29.91706],[29.12553,-29.76266],[29.28545,-29.58456],[29.30809,-29.4931],[29.44883,-29.3772],[29.40524,-29.21246],[29.33023,-29.10177],[28.96751,-28.88977],[28.91876,-28.77126],[28.76255,-28.68893],[28.68043,-28.58744],[28.65091,-28.57025],[28.40612,-28.6215],[28.34893,-28.6957],[28.2348,-28.69471],[28.1317,-28.7293],[28.02503,-28.85991],[27.98675,-28.8787],[27.9392,-28.84864],[27.88933,-28.88156],[27.8907,-28.91612],[27.75458,-28.89839],[27.76442,-28.93485],[27.73489,-28.94457],[27.65945,-29.05166],[27.61516,-29.1574],[27.5158,-29.2261],[27.54761,-29.25184],[27.49405,-29.28755],[27.46624,-29.29403],[27.45607,-29.29672],[27.45311,-29.30039],[27.47294,-29.32004],[27.4365,-29.33336],[27.33464,-29.48161],[27.30471,-29.49594],[27.30257,-29.52238],[27.06799,-29.60599],[27.00177,-29.65352]]]}},{"type":"Feature","properties":{"id":"LT"},"geometry":{"type":"Polygon","coordinates":[[[20.60454,55.40986],[20.95181,55.27994],[21.26425,55.24456],[21.35465,55.28427],[21.38892,55.29241],[21.46766,55.21115],[21.51095,55.18507],[21.57414,55.1991],[21.64954,55.1791],[21.85521,55.09493],[21.87807,55.09413],[21.91291,55.08215],[21.96505,55.07353],[21.99543,55.08691],[22.03984,55.07888],[22.02582,55.05078],[22.06087,55.02935],[22.11697,55.02131],[22.14267,55.05345],[22.31562,55.0655],[22.47688,55.04408],[22.58907,55.07085],[22.60075,55.01863],[22.65451,54.97037],[22.68723,54.9811],[22.76422,54.92521],[22.85083,54.88711],[22.87317,54.79492],[22.73631,54.72952],[22.73397,54.66604],[22.75467,54.6483],[22.74225,54.64339],[22.7522,54.63525],[22.68021,54.58486],[22.71293,54.56454],[22.67788,54.532],[22.70208,54.45312],[22.7253,54.41732],[22.79705,54.36264],[22.83697,54.40644],[22.88683,54.40983],[22.88821,54.40124],[23.00584,54.38514],[22.99649,54.35927],[23.05695,54.347],[23.04323,54.31567],[23.09575,54.29829],[23.13905,54.31567],[23.16012,54.31021],[23.15566,54.29759],[23.19694,54.28847],[23.24656,54.25701],[23.34122,54.25163],[23.39525,54.21672],[23.42418,54.17911],[23.45223,54.17775],[23.49196,54.14764],[23.53065,54.04558],[23.48456,53.98955],[23.51284,53.95052],[23.61677,53.92691],[23.71726,53.93379],[23.7854,53.90059],[23.81818,53.90888],[23.81309,53.94205],[23.95098,53.9613],[23.98837,53.92554],[24.19638,53.96405],[24.34128,53.90076],[24.44411,53.90076],[24.61126,54.001],[24.70636,54.02128],[24.68713,53.96446],[24.73949,53.96668],[24.85311,54.02862],[24.77131,54.11091],[24.83064,54.13222],[24.81519,54.14293],[24.96514,54.17499],[24.989,54.14433],[25.07105,54.13408],[25.16122,54.19862],[25.19662,54.21632],[25.22066,54.2595],[25.29275,54.26241],[25.36331,54.26517],[25.43437,54.29528],[25.47944,54.29914],[25.5039,54.31011],[25.59247,54.22796],[25.54673,54.22841],[25.56372,54.20828],[25.51094,54.17615],[25.54724,54.14925],[25.64875,54.1259],[25.71084,54.16704],[25.78563,54.15747],[25.75864,54.22379],[25.78553,54.23327],[25.68513,54.31727],[25.55256,54.31567],[25.55102,54.32738],[25.5376,54.33158],[25.63371,54.42075],[25.62203,54.4656],[25.64813,54.48704],[25.68045,54.5321],[25.75977,54.57252],[25.73598,54.79355],[25.81675,54.87448],[25.85932,54.90587],[25.88627,54.93044],[25.99129,54.95705],[26.0612,54.94168],[26.12239,54.9849],[26.20397,54.99729],[26.24642,55.04685],[26.24067,55.06524],[26.26941,55.08032],[26.22908,55.10656],[26.30628,55.12536],[26.35121,55.1525],[26.46249,55.12814],[26.51481,55.16051],[26.54211,55.14312],[26.59069,55.15391],[26.6191,55.14665],[26.69243,55.16718],[26.68075,55.19787],[26.72983,55.21788],[26.73017,55.24226],[26.835,55.28182],[26.83266,55.30444],[26.80929,55.31642],[26.6714,55.33902],[26.56631,55.32221],[26.44168,55.34613],[26.55137,55.38915],[26.55601,55.43826],[26.53627,55.5052],[26.63167,55.57887],[26.63231,55.67968],[26.58248,55.6754],[26.46661,55.70375],[26.3828,55.71144],[26.35002,55.74247],[26.27054,55.76701],[26.22642,55.84043],[26.18509,55.86813],[26.03815,55.95884],[25.90047,56.0013],[25.85893,56.00188],[25.81773,56.05444],[25.69246,56.08892],[25.68588,56.14725],[25.59333,56.13426],[25.53621,56.16663],[25.42116,56.15176],[25.23099,56.19147],[25.09325,56.1878],[25.05762,56.26742],[24.90978,56.44731],[24.87253,56.4511],[24.84609,56.41347],[24.80833,56.42078],[24.71657,56.40169],[24.65786,56.37556],[24.64671,56.37827],[24.63186,56.37599],[24.57353,56.31525],[24.58143,56.29125],[24.42746,56.26522],[24.32334,56.30226],[24.13139,56.24881],[24.01765,56.32938],[23.75896,56.36819],[23.49803,56.34307],[23.40486,56.37689],[23.31606,56.3827],[23.17312,56.36795],[23.10287,56.30301],[22.97447,56.41295],[22.83048,56.367],[22.68247,56.3604],[22.56441,56.39305],[22.3361,56.4016],[22.09728,56.42851],[22.00548,56.41508],[21.74558,56.33181],[21.57888,56.31406],[21.49736,56.29106],[21.23022,56.15931],[21.2067,56.08343],[20.68447,56.04073],[20.60454,55.40986]]]}},{"type":"Feature","properties":{"id":"LU"},"geometry":{"type":"Polygon","coordinates":[[[5.73621,49.89796],[5.78415,49.87922],[5.75438,49.87146],[5.75861,49.85631],[5.74567,49.85368],[5.76044,49.84545],[5.74885,49.84542],[5.74975,49.83933],[5.74108,49.83922],[5.7404,49.83452],[5.74844,49.82435],[5.74364,49.82058],[5.74953,49.81428],[5.75409,49.79239],[5.78871,49.7962],[5.82245,49.75048],[5.83149,49.74729],[5.82562,49.72395],[5.84193,49.72161],[5.86503,49.72739],[5.88677,49.70951],[5.86527,49.69291],[5.86175,49.67862],[5.9069,49.66377],[5.90164,49.6511],[5.90599,49.63853],[5.88552,49.63507],[5.88393,49.62802],[5.87609,49.62047],[5.8762,49.60898],[5.84826,49.5969],[5.84971,49.58674],[5.86986,49.58756],[5.87256,49.57539],[5.8424,49.56082],[5.84692,49.55663],[5.84143,49.5533],[5.81838,49.54777],[5.80871,49.5425],[5.81664,49.53775],[5.83648,49.5425],[5.84466,49.53027],[5.83467,49.52717],[5.83389,49.52152],[5.86571,49.50015],[5.94128,49.50034],[5.94224,49.49608],[5.96725,49.49041],[5.97693,49.45513],[6.02845,49.45561],[6.02578,49.45147],[6.02693,49.44826],[6.04176,49.44801],[6.05553,49.46663],[6.07887,49.46399],[6.08373,49.45594],[6.10072,49.45268],[6.09845,49.46351],[6.10325,49.4707],[6.12346,49.4735],[6.12814,49.49365],[6.14321,49.48796],[6.16115,49.49297],[6.15366,49.50226],[6.17386,49.50934],[6.19543,49.50536],[6.2409,49.51408],[6.25346,49.50399],[6.2745,49.50433],[6.28078,49.50093],[6.28612,49.48534],[6.3687,49.4593],[6.36778,49.46937],[6.36907,49.48931],[6.36788,49.50377],[6.35666,49.52931],[6.38072,49.55171],[6.38228,49.55855],[6.35825,49.57053],[6.36676,49.57813],[6.38024,49.57593],[6.38342,49.5799],[6.37464,49.58886],[6.385,49.59946],[6.39822,49.60081],[6.41861,49.61723],[6.4413,49.65722],[6.43768,49.66021],[6.42726,49.66078],[6.42937,49.66857],[6.44654,49.67799],[6.46048,49.69092],[6.48014,49.69767],[6.49785,49.71118],[6.50647,49.71353],[6.5042,49.71808],[6.49694,49.72205],[6.49535,49.72645],[6.50261,49.72718],[6.51397,49.72058],[6.51805,49.72425],[6.50193,49.73291],[6.50174,49.75292],[6.51646,49.75961],[6.51828,49.76855],[6.51056,49.77515],[6.51669,49.78336],[6.50534,49.78952],[6.52169,49.79787],[6.53122,49.80666],[6.52121,49.81338],[6.51215,49.80124],[6.50647,49.80916],[6.48718,49.81267],[6.47111,49.82263],[6.45425,49.81164],[6.44131,49.81443],[6.42905,49.81091],[6.42521,49.81591],[6.40022,49.82029],[6.36576,49.85032],[6.34267,49.84974],[6.33585,49.83785],[6.32098,49.83728],[6.32303,49.85133],[6.30963,49.87021],[6.29692,49.86685],[6.28874,49.87592],[6.26146,49.88203],[6.23496,49.89972],[6.22926,49.92096],[6.21882,49.92403],[6.22608,49.929],[6.22094,49.94955],[6.19856,49.95053],[6.19089,49.96991],[6.18045,49.96611],[6.18554,49.95622],[6.17872,49.9537],[6.16466,49.97086],[6.1701,49.98518],[6.14147,49.99563],[6.14948,50.00908],[6.13806,50.01056],[6.1295,50.01849],[6.13273,50.02019],[6.13794,50.01466],[6.14666,50.02207],[6.13044,50.02929],[6.13458,50.04141],[6.11274,50.05916],[6.12055,50.09171],[6.1379,50.12964],[6.1137,50.13668],[6.12028,50.16374],[6.08577,50.17246],[6.06406,50.15344],[6.03093,50.16362],[6.02488,50.18283],[5.96453,50.17259],[5.95929,50.13295],[5.89488,50.11476],[5.8857,50.07824],[5.85474,50.06342],[5.86904,50.04614],[5.8551,50.02683],[5.81866,50.01286],[5.82331,49.99662],[5.83968,49.9892],[5.83467,49.97823],[5.81163,49.97142],[5.80833,49.96451],[5.77291,49.96056],[5.77314,49.93646],[5.73621,49.89796]]]}},{"type":"Feature","properties":{"id":"LV"},"geometry":{"type":"Polygon","coordinates":[[[19.64795,57.06466],[20.68447,56.04073],[21.2067,56.08343],[21.23022,56.15931],[21.49736,56.29106],[21.57888,56.31406],[21.74558,56.33181],[22.00548,56.41508],[22.09728,56.42851],[22.3361,56.4016],[22.56441,56.39305],[22.68247,56.3604],[22.83048,56.367],[22.97447,56.41295],[23.10287,56.30301],[23.17312,56.36795],[23.31606,56.3827],[23.40486,56.37689],[23.49803,56.34307],[23.75896,56.36819],[24.01765,56.32938],[24.13139,56.24881],[24.32334,56.30226],[24.42746,56.26522],[24.58143,56.29125],[24.57353,56.31525],[24.63186,56.37599],[24.64671,56.37827],[24.65786,56.37556],[24.71657,56.40169],[24.80833,56.42078],[24.84609,56.41347],[24.87253,56.4511],[24.90978,56.44731],[25.05762,56.26742],[25.09325,56.1878],[25.23099,56.19147],[25.42116,56.15176],[25.53621,56.16663],[25.59333,56.13426],[25.68588,56.14725],[25.69246,56.08892],[25.81773,56.05444],[25.85893,56.00188],[25.90047,56.0013],[26.03815,55.95884],[26.18509,55.86813],[26.22642,55.84043],[26.27054,55.76701],[26.35002,55.74247],[26.3828,55.71144],[26.46661,55.70375],[26.58248,55.6754],[26.63231,55.67968],[26.64888,55.70515],[26.71802,55.70645],[26.77677,55.67806],[26.87448,55.7172],[26.97418,55.81411],[27.1559,55.85032],[27.27804,55.78299],[27.35956,55.81141],[27.42977,55.79443],[27.55508,55.784],[27.61683,55.78558],[27.63988,55.9265],[27.80055,55.98378],[27.97865,56.11849],[28.15217,56.16964],[28.24447,56.28224],[28.16599,56.37806],[28.19057,56.44637],[28.10069,56.524],[28.13526,56.57989],[28.04768,56.59004],[27.86922,56.88031],[27.64537,56.83188],[27.87265,57.29314],[27.52453,57.42826],[27.56832,57.53728],[27.34698,57.52242],[26.90364,57.62823],[26.54675,57.51813],[26.46527,57.56885],[26.29253,57.59244],[26.1866,57.6849],[26.2029,57.7206],[26.08098,57.76619],[26.0543,57.76105],[26.03332,57.7718],[26.02415,57.76865],[26.02069,57.77169],[26.0266,57.77441],[26.027,57.78158],[26.02456,57.78342],[26.0324,57.79037],[26.05949,57.84744],[25.70217,57.90536],[25.29581,58.08288],[25.28237,57.98539],[25.19484,58.0831],[24.3579,57.87471],[24.26221,57.91787],[23.20055,57.56697],[22.80496,57.87798],[19.84909,57.57876],[19.64795,57.06466]]]}},{"type":"Feature","properties":{"id":"MC"},"geometry":{"type":"Polygon","coordinates":[[[7.40903,43.7296],[7.41855,43.72479],[7.50102,43.51859],[7.53358,43.53609],[7.45448,43.7432],[7.4379,43.74963],[7.4389,43.75151],[7.43708,43.75197],[7.43624,43.75014],[7.43013,43.74895],[7.42809,43.74396],[7.42443,43.74087],[7.42299,43.74176],[7.42062,43.73977],[7.41233,43.73439],[7.41298,43.73311],[7.41291,43.73168],[7.41113,43.73156],[7.40903,43.7296]]]}},{"type":"Feature","properties":{"id":"MD"},"geometry":{"type":"Polygon","coordinates":[[[26.62823,48.25804],[26.81161,48.25049],[26.83101,48.2281],[26.88423,48.20179],[26.94265,48.1969],[26.98042,48.15752],[26.96119,48.13003],[27.04873,48.12338],[27.02985,48.09083],[27.12352,48.013],[27.14695,47.98147],[27.1618,47.92391],[27.28554,47.73479],[27.25519,47.71366],[27.32202,47.64009],[27.42055,47.58407],[27.47804,47.48542],[27.56864,47.4643],[27.57293,47.3851],[27.60709,47.32404],[27.69344,47.28749],[27.72708,47.29972],[27.75893,47.23868],[27.78768,47.20335],[27.78605,47.19111],[27.81755,47.14209],[27.92175,47.06801],[28.08259,46.99032],[28.11573,46.82978],[28.16087,46.78783],[28.24825,46.64177],[28.22281,46.50481],[28.25769,46.43334],[28.18902,46.35283],[28.19864,46.31869],[28.10937,46.22852],[28.13684,46.18099],[28.08612,46.01105],[28.13111,45.92819],[28.16568,45.6421],[28.08927,45.6051],[28.18741,45.47358],[28.21139,45.46895],[28.30201,45.54744],[28.41836,45.51715],[28.43072,45.48538],[28.51449,45.49982],[28.49252,45.56716],[28.54196,45.58062],[28.51587,45.6613],[28.47879,45.66994],[28.52823,45.73803],[28.58402,45.72475],[28.60977,45.76524],[28.71036,45.78021],[28.69852,45.81753],[28.78589,45.83286],[28.76946,45.88515],[28.74383,45.96664],[28.81021,45.97709],[28.98004,46.00385],[29.00613,46.04962],[28.95137,46.09394],[29.06656,46.19716],[28.99799,46.23495],[28.95275,46.25988],[28.98478,46.31803],[29.004,46.31495],[28.9306,46.45699],[29.01241,46.46177],[29.02409,46.49582],[29.23547,46.55435],[29.24886,46.37912],[29.35357,46.49505],[29.49623,46.45725],[29.5939,46.35472],[29.6763,46.36041],[29.66359,46.4215],[29.75732,46.46186],[29.8744,46.35522],[29.94114,46.40114],[30.09103,46.38694],[30.16794,46.40967],[30.02511,46.45132],[29.88916,46.54302],[29.94409,46.56002],[29.9743,46.75325],[29.94522,46.80055],[29.98632,46.81838],[29.88195,46.88589],[29.75458,46.8604],[29.72986,46.92234],[29.57056,46.94766],[29.62137,47.05069],[29.61038,47.09932],[29.53044,47.07851],[29.49732,47.12878],[29.57696,47.13581],[29.55236,47.25115],[29.59905,47.25721],[29.57399,47.37022],[29.48678,47.36043],[29.47854,47.30366],[29.39889,47.30179],[29.38156,47.37812],[29.30499,47.44202],[29.24543,47.42727],[29.18277,47.44805],[29.11743,47.55001],[29.22414,47.60012],[29.22191,47.7384],[29.27255,47.79953],[29.20663,47.80367],[29.27942,47.88952],[29.19977,47.88837],[29.17453,47.99348],[28.92408,47.96257],[28.8414,48.03392],[28.85232,48.12506],[28.69896,48.13106],[28.53921,48.17453],[28.51164,48.12396],[28.48428,48.0737],[28.42403,48.11866],[28.42626,48.13407],[28.44111,48.14908],[28.38712,48.17567],[28.34009,48.13147],[28.30609,48.14018],[28.30586,48.1597],[28.34912,48.1787],[28.36996,48.20543],[28.35519,48.24957],[28.32508,48.23384],[28.2856,48.23202],[28.20662,48.20488],[28.18508,48.22198],[28.18722,48.25977],[28.0765,48.23347],[28.09873,48.3124],[28.04527,48.32661],[27.96226,48.32469],[27.88391,48.36699],[27.87533,48.4037],[27.81789,48.42151],[27.80699,48.43352],[27.7833,48.44804],[27.74545,48.45886],[27.6676,48.44024],[27.58735,48.46148],[27.60396,48.48799],[27.58332,48.49243],[27.46891,48.45038],[27.44813,48.41222],[27.37741,48.41026],[27.37312,48.44189],[27.32159,48.4434],[27.27855,48.37534],[27.18326,48.38783],[27.12833,48.37495],[27.08078,48.43214],[27.0231,48.42485],[27.04662,48.37426],[26.93384,48.36558],[26.83753,48.41803],[26.70879,48.40527],[26.82809,48.31629],[26.79239,48.29071],[26.6839,48.35828],[26.62823,48.25804]]]}},{"type":"Feature","properties":{"id":"MG"},"geometry":{"type":"Polygon","coordinates":[[[40.40841,-23.17181],[42.93867,-25.64228],[47.18248,-26.33102],[51.94557,-12.74579],[48.86266,-10.8109],[47.29063,-12.45583],[43.72277,-16.09877],[40.40841,-23.17181]]]}},{"type":"Feature","properties":{"id":"MK"},"geometry":{"type":"Polygon","coordinates":[[[20.45331,41.51436],[20.49456,41.49173],[20.51301,41.442],[20.56237,41.40546],[20.52503,41.34279],[20.49756,41.33789],[20.51731,41.23147],[20.59715,41.13644],[20.58546,41.11179],[20.59832,41.09066],[20.67394,41.08015],[20.7316,40.91069],[20.81567,40.89662],[20.84587,40.9375],[20.94989,40.92025],[20.97693,40.90103],[20.97887,40.85475],[21.15262,40.85546],[21.21105,40.8855],[21.25779,40.86165],[21.35595,40.87578],[21.41555,40.9173],[21.53007,40.90759],[21.57079,40.86445],[21.66778,40.90066],[21.69601,40.9429],[21.76237,40.92596],[21.91085,41.05683],[21.90869,41.09191],[22.06527,41.15617],[22.1424,41.12449],[22.17629,41.15969],[22.25452,41.1656],[22.4224,41.11809],[22.55965,41.13128],[22.58295,41.11568],[22.62852,41.14385],[22.65306,41.18168],[22.71266,41.13945],[22.7477,41.16392],[22.76882,41.32384],[22.81199,41.3398],[22.93334,41.34104],[22.96451,41.35626],[22.95275,41.62436],[23.03342,41.71034],[23.01239,41.76527],[22.96682,41.77137],[22.89979,41.89103],[22.86749,42.02275],[22.67701,42.06614],[22.51224,42.15457],[22.50289,42.19527],[22.48678,42.19965],[22.38421,42.30296],[22.34773,42.31725],[22.29275,42.34913],[22.29605,42.37477],[22.17504,42.3225],[22.02908,42.29848],[21.94405,42.34669],[21.91595,42.30392],[21.84654,42.3247],[21.77176,42.2648],[21.70111,42.23789],[21.58992,42.25915],[21.52145,42.24465],[21.50823,42.27156],[21.43882,42.2789],[21.44187,42.23493],[21.3872,42.24618],[21.30496,42.1418],[21.29913,42.13954],[21.31983,42.10993],[21.22728,42.08909],[21.16614,42.19815],[21.11491,42.20794],[20.75464,42.05229],[20.76786,41.91839],[20.68523,41.85318],[20.59524,41.8818],[20.55976,41.87068],[20.57144,41.7897],[20.53405,41.78099],[20.51301,41.72433],[20.52937,41.69292],[20.51769,41.65975],[20.55508,41.58113],[20.52103,41.56473],[20.45809,41.5549],[20.45331,41.51436]]]}},{"type":"Feature","properties":{"id":"ML"},"geometry":{"type":"Polygon","coordinates":[[[-12.23936,14.76324],[-12.20237,14.69876],[-12.16873,14.6888],[-12.17645,14.66356],[-12.14332,14.65152],[-12.15036,14.62046],[-12.19688,14.60584],[-12.18375,14.55542],[-12.21473,14.55284],[-12.22186,14.49028],[-12.1986,14.47897],[-12.20701,14.46036],[-12.20117,14.40658],[-12.11912,14.36617],[-12.09457,14.30796],[-12.02796,14.29033],[-11.99192,14.20481],[-12.00187,13.98404],[-11.93407,13.92473],[-12.05783,13.72671],[-12.03706,13.62496],[-11.8654,13.45724],[-11.88583,13.39847],[-11.82884,13.30477],[-11.63383,13.39646],[-11.40861,13.02663],[-11.40913,12.9752],[-11.34475,12.93613],[-11.40295,12.92476],[-11.37943,12.73663],[-11.42389,12.72893],[-11.41307,12.64452],[-11.43436,12.55657],[-11.37908,12.50663],[-11.36793,12.40153],[-11.50006,12.17826],[-11.256,11.99046],[-11.08709,12.1318],[-11.01722,12.2553],[-10.80355,12.1053],[-10.70514,11.90464],[-10.53176,12.00909],[-10.5043,12.12358],[-10.44267,12.12224],[-10.33521,12.22309],[-10.10519,12.16973],[-9.7229,12.01212],[-9.63938,12.18312],[-9.32097,12.29009],[-9.38067,12.48446],[-9.13689,12.50875],[-8.93531,12.32792],[-8.96278,12.28432],[-8.8797,12.042],[-8.78133,11.94293],[-8.7985,11.82879],[-8.82459,11.82366],[-8.83438,11.65728],[-8.69602,11.63777],[-8.5786,11.47262],[-8.3218,11.3619],[-8.4835,11.27976],[-8.47457,11.23498],[-8.70082,10.94321],[-8.34686,11.07902],[-8.2667,10.91762],[-8.32614,10.69273],[-8.22711,10.41722],[-8.10207,10.44649],[-7.9578,10.2703],[-7.97971,10.17117],[-7.92107,10.15577],[-7.63048,10.46334],[-7.54462,10.40921],[-7.52261,10.4655],[-7.44555,10.44602],[-7.3707,10.24677],[-7.13331,10.24877],[-7.0603,10.14711],[-7.00966,10.15794],[-6.97444,10.21644],[-7.01186,10.25111],[-6.93921,10.35291],[-6.66423,10.34092],[-6.64913,10.67275],[-6.52974,10.59104],[-6.42847,10.5694],[-6.40502,10.71458],[-6.325,10.68624],[-6.24795,10.74248],[-6.21757,10.53979],[-6.1731,10.46983],[-6.18851,10.24244],[-5.99478,10.19694],[-5.78124,10.43952],[-5.65135,10.46767],[-5.51058,10.43177],[-5.46643,10.56074],[-5.47083,10.75329],[-5.41579,10.84628],[-5.49284,11.07538],[-5.32905,11.12636],[-5.32553,11.21578],[-5.25949,11.24816],[-5.25509,11.36905],[-5.20665,11.43811],[-5.22867,11.60421],[-5.29251,11.61715],[-5.26389,11.75728],[-5.40258,11.8327],[-5.26389,11.84778],[-5.07897,11.97918],[-4.72893,12.01579],[-4.70692,12.06746],[-4.62987,12.06531],[-4.62747,12.12207],[-4.54387,12.14087],[-4.57703,12.19875],[-4.47109,12.28717],[-4.44637,12.33564],[-4.39676,12.30864],[-4.43985,12.4064],[-4.38577,12.54333],[-4.47356,12.71252],[-4.23385,12.72591],[-4.21819,12.95722],[-4.34477,13.12927],[-4.23969,13.18613],[-4.25617,13.23794],[-4.1645,13.27837],[-3.96039,13.46892],[-3.95524,13.50114],[-3.89362,13.43837],[-3.95988,13.40765],[-3.95095,13.38394],[-3.78582,13.36139],[-3.54454,13.1781],[-3.4313,13.1588],[-3.43507,13.27272],[-3.23599,13.29035],[-3.27804,13.55522],[-3.24783,13.58792],[-3.25641,13.71337],[-2.88189,13.64921],[-2.90831,13.81174],[-2.84667,14.05532],[-2.6707,14.13957],[-2.47398,14.29965],[-2.29185,14.24741],[-2.10223,14.14878],[-1.9992,14.19011],[-1.97945,14.47709],[-1.68083,14.50023],[-1.32166,14.72774],[-1.05875,14.7921],[-0.72004,15.08655],[-0.24673,15.07805],[0.06588,14.96961],[0.23859,15.00135],[0.70037,14.94412],[0.72632,14.95898],[0.96711,14.98275],[1.31275,15.27978],[3.01806,15.34571],[3.03134,15.42221],[3.50368,15.35934],[4.19893,16.39923],[4.21787,17.00118],[4.26762,17.00432],[4.26651,19.14224],[3.36082,18.9745],[3.12501,19.1366],[3.24648,19.81703],[1.20992,20.73533],[1.15698,21.12843],[-4.83423,24.99935],[-6.57191,25.0002],[-5.60725,16.49919],[-5.33435,16.33354],[-5.50165,15.50061],[-9.32979,15.50032],[-9.31106,15.69412],[-9.33314,15.7044],[-9.44673,15.60553],[-9.40447,15.4396],[-9.68444,15.42786],[-10.07446,15.3706],[-10.27496,15.43349],[-10.38826,15.4411],[-10.63613,15.42257],[-10.70892,15.44342],[-10.89569,15.10891],[-11.00143,15.23615],[-11.43625,15.64849],[-11.63469,15.52415],[-11.69992,15.53887],[-11.84532,15.09532],[-11.79725,14.90331],[-11.97063,14.76326],[-12.08427,14.73338],[-12.14143,14.7787],[-12.17165,14.76077],[-12.23936,14.76324]]]}},{"type":"Feature","properties":{"id":"MM"},"geometry":{"type":"Polygon","coordinates":[[[92.17288,21.17918],[92.17529,21.16312],[92.20001,21.16052],[92.19752,21.13722],[92.2582,21.07713],[92.27073,20.96176],[92.37665,20.72172],[92.39837,20.38919],[92.61282,13.95915],[94.64499,13.56452],[96.72099,15.25813],[97.63455,9.60854],[98.21525,9.56576],[98.33094,9.91973],[98.47298,9.95782],[98.52291,9.92389],[98.55174,9.92804],[98.7391,10.31488],[98.75507,10.39107],[98.81721,10.50064],[98.81481,10.54688],[98.80133,10.56139],[98.77275,10.62548],[98.78374,10.67806],[98.8621,10.77858],[99.0111,10.85294],[98.99701,10.92962],[99.02337,10.97217],[99.06938,10.94857],[99.32756,11.28545],[99.31573,11.32081],[99.39485,11.3925],[99.47598,11.62434],[99.5672,11.62732],[99.64108,11.78948],[99.64891,11.82699],[99.53424,12.02317],[99.56445,12.14805],[99.47519,12.1353],[99.409,12.60603],[99.29254,12.68921],[99.18905,12.84799],[99.18748,12.9898],[99.10646,13.05804],[99.12225,13.19847],[99.20617,13.20575],[99.16695,13.72621],[98.97356,14.04868],[98.56762,14.37701],[98.24874,14.83013],[98.18821,15.13125],[98.22,15.21327],[98.30446,15.30667],[98.40522,15.25268],[98.41906,15.27103],[98.39351,15.34177],[98.4866,15.39154],[98.56027,15.33471],[98.58015,15.3759],[98.58598,15.46821],[98.541,15.65406],[98.59853,15.87197],[98.57019,16.04578],[98.69585,16.13353],[98.8376,16.11706],[98.92656,16.36425],[98.84485,16.42354],[98.68074,16.27068],[98.6543,16.37301],[98.63817,16.47424],[98.57912,16.55983],[98.5853,16.58519],[98.56847,16.6162],[98.57422,16.6204],[98.56796,16.63117],[98.51062,16.64351],[98.51833,16.676],[98.51472,16.68521],[98.51579,16.69433],[98.51043,16.70107],[98.49713,16.69022],[98.49693,16.70254],[98.5032,16.7129],[98.46994,16.73613],[98.52736,16.79772],[98.52049,16.80823],[98.54804,16.81325],[98.53792,16.82927],[98.49603,16.8446],[98.53551,16.89835],[98.49801,16.95779],[98.47346,16.99556],[98.43629,17.03676],[98.39441,17.06266],[98.34566,17.04822],[98.31278,17.08123],[98.32377,17.11117],[98.28746,17.13258],[98.2394,17.22992],[98.22687,17.22008],[98.12344,17.32245],[98.11237,17.31893],[98.10439,17.33847],[98.1146,17.37767],[98.04825,17.42746],[98.05615,17.4413],[98.03667,17.45203],[97.98946,17.51195],[97.97281,17.50893],[97.95727,17.52726],[97.92345,17.54101],[97.9226,17.55018],[97.90955,17.56597],[97.90303,17.58659],[97.75789,17.73429],[97.66674,17.86848],[97.73723,17.97912],[97.60841,18.23846],[97.64116,18.29778],[97.56219,18.33885],[97.50383,18.26844],[97.34522,18.54596],[97.36444,18.57138],[97.5258,18.4939],[97.76752,18.58097],[97.73836,18.88478],[97.66487,18.9371],[97.73654,18.9812],[97.73797,19.04261],[97.83479,19.09972],[97.84024,19.22217],[97.78606,19.26769],[97.84186,19.29526],[97.78769,19.39429],[97.88423,19.5041],[97.84715,19.55782],[98.04364,19.65755],[98.03478,19.80756],[98.13829,19.78541],[98.24884,19.67876],[98.51182,19.71303],[98.56065,19.67807],[98.80125,19.80708],[98.92227,19.76298],[99.01874,19.79481],[99.02595,19.92542],[99.04226,19.93155],[99.0735,20.10298],[99.20328,20.12877],[99.416,20.08614],[99.52943,20.14811],[99.5569,20.20676],[99.46077,20.36198],[99.46008,20.39673],[99.68255,20.32077],[99.73199,20.34655],[99.81096,20.33687],[99.86383,20.44371],[99.88211,20.44488],[99.88451,20.44596],[99.89168,20.44548],[99.89301,20.44311],[99.89692,20.44789],[99.90499,20.4487],[99.91616,20.44986],[99.95721,20.46301],[100.08404,20.36626],[100.12475,20.41044],[100.1184,20.43698],[100.1893,20.67888],[100.28303,20.77393],[100.37422,20.83553],[100.51628,20.81632],[100.60086,20.83571],[100.64909,20.88118],[100.64446,20.89898],[100.54112,20.87084],[100.50579,20.88108],[100.54678,21.02202],[100.63734,21.06231],[100.64815,21.10652],[100.68351,21.14807],[100.72531,21.3124],[100.74059,21.31312],[100.80173,21.2934],[100.84994,21.3064],[100.89543,21.35102],[100.95851,21.38259],[100.9998,21.38674],[101.0555,21.45115],[101.08846,21.46057],[101.16198,21.52808],[101.15156,21.56129],[101.16682,21.6437],[101.11627,21.69252],[101.11744,21.77659],[101.08228,21.77081],[101.00383,21.70974],[100.8847,21.6855],[100.71999,21.51272],[100.57861,21.45637],[100.4811,21.46148],[100.43091,21.54243],[100.35201,21.53176],[100.29624,21.48014],[100.242,21.46752],[100.18447,21.51898],[100.16887,21.48645],[100.12493,21.51185],[100.10757,21.59945],[100.17486,21.65306],[100.11918,21.70608],[100.06965,21.69284],[100.04931,21.66763],[99.98654,21.71064],[99.94331,21.82548],[99.99084,21.97053],[99.96906,22.05971],[99.85267,22.03186],[99.72564,22.06686],[99.70762,22.04332],[99.65423,22.09295],[99.47585,22.13345],[99.4315,22.10544],[99.32121,22.10051],[99.27246,22.10281],[99.15573,22.16197],[99.20285,22.17218],[99.1822,22.17576],[99.17362,22.17969],[99.1783,22.18398],[99.22903,22.25541],[99.28771,22.4105],[99.37972,22.50188],[99.3861,22.57755],[99.31537,22.73977],[99.37957,22.76715],[99.39949,22.82856],[99.45931,22.84991],[99.43305,22.94385],[99.54218,22.90014],[99.56531,22.93317],[99.51939,22.99821],[99.52214,23.08218],[99.41888,23.08668],[99.33803,23.13341],[99.30353,23.10404],[99.25014,23.08225],[99.19881,23.11004],[99.105,23.10152],[99.04601,23.12215],[99.05975,23.16382],[98.88519,23.18423],[98.92515,23.29535],[98.93958,23.31414],[98.87573,23.33038],[98.92104,23.36946],[98.91334,23.41883],[98.88021,23.49017],[98.8276,23.47867],[98.80294,23.5345],[98.88656,23.59734],[98.8179,23.70253],[98.82933,23.72921],[98.79607,23.77947],[98.68209,23.80492],[98.67797,23.9644],[98.86961,24.07808],[98.86776,24.08572],[98.89632,24.10612],[98.87998,24.15624],[98.85319,24.13042],[98.71713,24.12873],[98.59256,24.08371],[98.54667,24.12944],[98.36299,24.11354],[98.3587,24.09943],[98.21537,24.11369],[98.09529,24.08901],[98.07602,24.07868],[98.07007,24.08204],[98.06271,24.07825],[98.05671,24.07961],[98.05274,24.07375],[98.04709,24.07616],[98.01753,24.05724],[97.99583,24.04932],[97.98508,24.03556],[97.93951,24.01953],[97.90792,24.02043],[97.88556,24.00291],[97.88414,23.99405],[97.88814,23.98605],[97.8942,23.98357],[97.89693,23.98399],[97.89715,23.9797],[97.89541,23.97778],[97.88749,23.97456],[97.86545,23.97723],[97.84328,23.97603],[97.83835,23.96272],[97.82368,23.97252],[97.8266,23.95409],[97.80964,23.96229],[97.80608,23.95268],[97.79416,23.95663],[97.79456,23.94836],[97.76827,23.93479],[97.76415,23.91189],[97.72302,23.89288],[97.64511,23.84407],[97.63395,23.87955],[97.51653,23.9395],[97.62639,24.00907],[97.63309,24.04983],[97.72903,24.12606],[97.73454,24.15192],[97.75305,24.16902],[97.7439,24.1843],[97.72799,24.18883],[97.726,24.20293],[97.72998,24.2302],[97.76799,24.26365],[97.73368,24.29672],[97.66723,24.30027],[97.65624,24.33781],[97.72047,24.35945],[97.67146,24.45472],[97.60029,24.4401],[97.52757,24.43748],[97.56286,24.54535],[97.57026,24.72297],[97.54675,24.74202],[97.5542,24.74943],[97.56383,24.75535],[97.56648,24.76475],[97.64354,24.79171],[97.70236,24.84356],[97.73127,24.83015],[97.76481,24.8289],[97.79949,24.85655],[97.76046,24.88223],[97.72903,24.91332],[97.71729,24.98193],[97.72216,25.08508],[97.75926,25.09679],[97.83857,25.27186],[97.92541,25.20815],[98.14925,25.41547],[98.12591,25.50722],[98.18084,25.56298],[98.16848,25.62739],[98.25774,25.6051],[98.31268,25.55307],[98.40606,25.61129],[98.54064,25.85129],[98.63128,25.79937],[98.70818,25.86241],[98.60763,26.01512],[98.57085,26.11547],[98.63128,26.15492],[98.66884,26.09165],[98.7329,26.17218],[98.67797,26.24487],[98.72741,26.36183],[98.77547,26.61994],[98.7333,26.85615],[98.69582,27.56499],[98.43353,27.67086],[98.42529,27.55404],[98.32641,27.51385],[98.13829,27.96302],[98.15337,28.12114],[98.08404,28.19913],[98.01589,28.2073],[98.00474,28.27641],[97.90191,28.37327],[97.79763,28.3278],[97.70705,28.5056],[97.56835,28.55628],[97.50518,28.49716],[97.47085,28.2688],[97.41729,28.29783],[97.34547,28.21385],[97.31292,28.06784],[97.35412,28.06663],[97.38845,28.01329],[97.35824,27.87256],[97.29919,27.92233],[96.90696,27.61236],[96.91431,27.45752],[97.17422,27.14052],[97.14675,27.09041],[96.84531,27.18939],[96.88445,27.25046],[96.73888,27.36638],[96.55761,27.29928],[96.40779,27.29818],[96.15591,27.24572],[96.04949,27.19428],[95.93002,27.04149],[95.81603,27.01335],[95.437,26.7083],[95.30339,26.65372],[95.23618,26.68266],[95.05798,26.45408],[95.12801,26.38397],[95.12555,26.28318],[95.11428,26.1019],[95.18556,26.07338],[95.03585,25.93056],[95.04272,25.72382],[94.81421,25.49204],[94.68069,25.45815],[94.57458,25.20318],[94.74212,25.13606],[94.73937,25.00545],[94.60204,24.70889],[94.54696,24.70816],[94.54919,24.63687],[94.46765,24.57366],[94.35333,24.33364],[94.3365,24.32895],[94.32362,24.27692],[94.30518,24.23984],[94.28844,24.23092],[94.2366,24.03329],[94.14149,23.83427],[93.92089,23.95812],[93.80279,23.92549],[93.75952,24.0003],[93.62871,24.00922],[93.50616,23.94432],[93.46633,23.97067],[93.41415,24.07854],[93.34735,24.10151],[93.32351,24.04468],[93.36059,23.93176],[93.3908,23.92925],[93.3908,23.7622],[93.43475,23.68299],[93.38805,23.4728],[93.39981,23.38828],[93.38781,23.36139],[93.36862,23.35426],[93.38478,23.13698],[93.2878,23.00464],[93.12988,23.05772],[93.134,22.92498],[93.09417,22.69459],[93.134,22.59573],[93.11477,22.54374],[93.13537,22.45873],[93.18206,22.43716],[93.19991,22.25425],[93.14224,22.24535],[93.15734,22.18687],[93.04885,22.20595],[92.99255,22.05965],[92.99804,21.98964],[92.93899,22.02656],[92.90588,21.93826],[92.86208,22.05456],[92.70416,22.16017],[92.67532,22.03547],[92.60949,21.97638],[92.62187,21.87037],[92.59775,21.6092],[92.68152,21.28454],[92.60187,21.24615],[92.55105,21.3856],[92.43158,21.37025],[92.38317,21.48709],[92.27056,21.43101],[92.25777,21.36269],[92.19932,21.32951],[92.21966,21.23346],[92.19771,21.20075],[92.19477,21.20165],[92.17288,21.17918]]]}},{"type":"Feature","properties":{"id":"ME"},"geometry":{"type":"Polygon","coordinates":[[[18.43588,42.48556],[18.52152,42.42302],[18.54128,42.39171],[18.45131,42.21682],[19.26406,41.74971],[19.37284,41.84597],[19.37842,41.88598],[19.34203,41.90591],[19.36949,41.9229],[19.34924,41.95751],[19.38571,41.96383],[19.36867,42.02564],[19.37996,42.07357],[19.40185,42.1028],[19.2834,42.18096],[19.42142,42.32745],[19.42537,42.37287],[19.4836,42.40831],[19.65972,42.62774],[19.73244,42.66299],[19.77375,42.58517],[19.74731,42.57422],[19.76549,42.50237],[19.82333,42.46581],[19.9324,42.51699],[20.00842,42.5109],[20.01834,42.54622],[20.07761,42.55582],[20.09785,42.66312],[20.02824,42.7059],[20.01872,42.74965],[20.04898,42.77701],[20.1466,42.75652],[20.25303,42.75671],[20.2714,42.82159],[20.35692,42.8335],[20.34528,42.90676],[20.15785,42.97576],[20.14896,42.99058],[20.12325,42.96237],[20.05373,43.00941],[20.04729,43.02732],[19.98887,43.0538],[19.96549,43.11098],[19.92576,43.08539],[19.86688,43.11163],[19.84139,43.09665],[19.7838,43.13418],[19.7723,43.16127],[19.64063,43.19027],[19.62661,43.2286],[19.54407,43.24776],[19.52639,43.32005],[19.48171,43.32644],[19.46142,43.34559],[19.44683,43.38883],[19.22229,43.47926],[19.22807,43.5264],[19.15685,43.53943],[19.13933,43.5282],[19.04934,43.50384],[19.01078,43.55806],[18.91021,43.50087],[18.95469,43.49367],[18.96053,43.45042],[19.01078,43.43854],[19.04071,43.397],[19.08393,43.31949],[19.08093,43.29969],[19.03724,43.29042],[19.01046,43.24911],[18.95001,43.29327],[18.95999,43.3306],[18.90911,43.36383],[18.83912,43.34795],[18.84794,43.33735],[18.85342,43.32426],[18.76538,43.29838],[18.69409,43.25014],[18.71747,43.2286],[18.66808,43.20473],[18.64735,43.14766],[18.66254,43.03928],[18.52232,43.01451],[18.49076,42.95553],[18.49661,42.89306],[18.4935,42.86433],[18.47633,42.85829],[18.45921,42.81682],[18.47324,42.74992],[18.56789,42.72074],[18.55221,42.69045],[18.54603,42.69171],[18.54841,42.68328],[18.57373,42.64429],[18.52232,42.62279],[18.55504,42.58409],[18.53751,42.57376],[18.49778,42.58409],[18.43735,42.55921],[18.44307,42.51077],[18.43588,42.48556]]]}},{"type":"Feature","properties":{"id":"MN"},"geometry":{"type":"Polygon","coordinates":[[[87.73822,48.89582],[88.0788,48.71436],[87.96361,48.58478],[88.58939,48.34531],[88.58316,48.21893],[88.8011,48.11302],[88.93186,48.10263],[89.0711,47.98528],[89.55453,48.0423],[89.76624,47.82745],[90.06512,47.88177],[90.10871,47.7375],[90.33598,47.68303],[90.48854,47.41826],[90.48542,47.30438],[90.76108,46.99399],[90.84035,46.99525],[91.03649,46.72916],[91.0147,46.58171],[91.07696,46.57315],[90.89639,46.30711],[90.99672,46.14207],[91.03026,46.04194],[90.70907,45.73437],[90.65114,45.49314],[90.89169,45.19667],[91.64048,45.07408],[93.51161,44.95964],[94.10003,44.71016],[94.71959,44.35284],[95.01191,44.25274],[95.39772,44.2805],[95.32891,44.02407],[95.52594,43.99353],[95.89543,43.2528],[96.35658,42.90363],[96.37926,42.72055],[97.2134,42.79942],[99.50671,42.56535],[100.33297,42.68231],[100.84979,42.67087],[101.28833,42.58524],[101.80515,42.50074],[102.07645,42.22519],[102.42826,42.15137],[102.72403,42.14675],[103.3685,41.89696],[103.92804,41.78246],[104.52258,41.8706],[104.51667,41.66113],[104.91272,41.64619],[105.01119,41.58382],[105.24708,41.7442],[106.76517,42.28741],[107.24774,42.36107],[107.29755,42.41395],[107.49681,42.46221],[107.57258,42.40898],[108.23156,42.45532],[108.84489,42.40246],[109.00679,42.45302],[109.452,42.44842],[109.89402,42.63111],[110.08401,42.6411],[110.4327,42.78293],[111.0149,43.3289],[111.59087,43.51207],[111.79758,43.6637],[111.93776,43.68709],[111.96289,43.81596],[111.40498,44.3461],[111.76275,44.98032],[111.98695,45.09074],[112.4164,45.06858],[112.74662,44.86297],[113.63821,44.74326],[113.909,44.91444],[114.08071,44.92847],[114.5166,45.27189],[114.54801,45.38337],[114.74612,45.43585],[114.94546,45.37377],[115.35757,45.39106],[115.69688,45.45761],[115.91898,45.6227],[116.16989,45.68603],[116.27366,45.78637],[116.24012,45.8778],[116.26678,45.96479],[116.58612,46.30211],[116.75551,46.33083],[116.83166,46.38637],[117.07252,46.35818],[117.36609,46.36335],[117.41782,46.57862],[117.60748,46.59771],[117.69554,46.50991],[118.30534,46.73519],[118.78747,46.68689],[118.8337,46.77742],[118.89974,46.77139],[118.92616,46.72765],[119.00541,46.74273],[119.10448,46.65516],[119.24978,46.64761],[119.30261,46.6083],[119.37306,46.61132],[119.42827,46.63783],[119.65265,46.62342],[119.68127,46.59015],[119.77373,46.62947],[119.80455,46.67631],[119.89261,46.66423],[119.91242,46.90091],[119.85518,46.92196],[119.71209,47.19192],[119.62403,47.24575],[119.56019,47.24874],[119.54918,47.29505],[119.31964,47.42617],[119.35892,47.48104],[119.13995,47.53997],[119.12343,47.66458],[118.7564,47.76947],[118.55766,47.99277],[118.29654,48.00246],[118.22677,48.03853],[118.11009,48.04],[118.03676,48.00982],[117.80196,48.01661],[117.50181,47.77216],[117.37875,47.63627],[117.08918,47.82242],[116.87527,47.88836],[116.67405,47.89039],[116.4465,47.83662],[116.2527,47.87766],[116.08431,47.80693],[115.94296,47.67741],[115.57128,47.91988],[115.52082,48.15367],[115.811,48.25699],[115.78876,48.51781],[116.06565,48.81716],[116.03781,48.87014],[116.71193,49.83813],[116.62502,49.92919],[116.22402,50.04477],[115.73602,49.87688],[115.26068,49.97367],[114.9703,50.19254],[114.325,50.28098],[113.20216,49.83356],[113.02647,49.60772],[110.64493,49.1816],[110.39891,49.25083],[110.24373,49.16676],[109.51325,49.22859],[109.18017,49.34709],[108.53969,49.32325],[108.27937,49.53167],[107.95387,49.66659],[107.96116,49.93191],[107.77379,49.94578],[107.36407,49.97612],[107.1174,50.04239],[107.00007,50.1977],[106.80326,50.30177],[106.51122,50.34408],[106.49628,50.32436],[106.47156,50.31909],[106.07865,50.33474],[106.05562,50.40582],[105.32528,50.4648],[103.70343,50.13952],[102.71178,50.38873],[102.32194,50.67982],[102.14032,51.35566],[101.5044,51.50467],[101.39085,51.45753],[100.61116,51.73028],[99.89203,51.74903],[99.75578,51.90108],[99.27888,51.96876],[98.87768,52.14563],[98.74142,51.8637],[98.33222,51.71832],[98.22053,51.46579],[98.05257,51.46696],[97.83305,51.00248],[98.01472,50.86652],[97.9693,50.78044],[98.06393,50.61262],[98.31373,50.4996],[98.29481,50.33561],[97.85197,49.91339],[97.76871,49.99861],[97.56432,49.92801],[97.56811,49.84265],[97.24639,49.74737],[96.97388,49.88413],[95.80056,50.04239],[95.74757,49.97915],[95.02465,49.96941],[94.97166,50.04725],[94.6121,50.04239],[94.49477,50.17832],[94.39258,50.22193],[94.30823,50.57498],[92.99595,50.63183],[93.01109,50.79001],[92.44714,50.78762],[92.07173,50.69585],[91.86048,50.73734],[89.59711,49.90851],[89.70687,49.72535],[88.82499,49.44808],[88.42449,49.48821],[88.17223,49.46934],[88.15543,49.30314],[87.98977,49.18147],[87.81333,49.17354],[87.88171,48.95853],[87.73822,48.89582]]]}},{"type":"Feature","properties":{"id":"MZ"},"geometry":{"type":"Polygon","coordinates":[[[30.21995,-14.98259],[30.41902,-15.62269],[30.42568,-15.9962],[30.91597,-15.99924],[30.97761,-16.05848],[31.1331,-15.98459],[31.30563,-16.01193],[31.42451,-16.15154],[31.71169,-16.20165],[31.90223,-16.34388],[31.91324,-16.41569],[32.02772,-16.43892],[32.28529,-16.43892],[32.42838,-16.4727],[32.71017,-16.59932],[32.70441,-16.68043],[32.80569,-16.70788],[32.9861,-16.70427],[32.91051,-16.89446],[32.84113,-16.92259],[32.98902,-17.1831],[32.99829,-17.30573],[33.04944,-17.33507],[32.96554,-17.48964],[32.98536,-17.55891],[33.0492,-17.60298],[32.94133,-17.99705],[33.05511,-18.35257],[33.02278,-18.4696],[32.88629,-18.51344],[32.88629,-18.58023],[32.95323,-18.68641],[32.9017,-18.7992],[32.82465,-18.77419],[32.70137,-18.84712],[32.73439,-18.92628],[32.69977,-18.94054],[32.71882,-19.02528],[32.84006,-19.0262],[32.87088,-19.09279],[32.85107,-19.29238],[32.77633,-19.36313],[32.78282,-19.47513],[32.84446,-19.48343],[32.84666,-19.68462],[32.95013,-19.67219],[33.06461,-19.77787],[33.02747,-20.03174],[32.93907,-20.04077],[32.86388,-20.1599],[32.86766,-20.26944],[32.66767,-20.55597],[32.56656,-20.55436],[32.48122,-20.63319],[32.51644,-20.91929],[32.37115,-21.133],[32.4785,-21.32791],[32.41001,-21.31656],[31.37597,-22.38188],[31.30743,-22.42308],[31.56234,-23.19622],[31.5596,-23.48025],[31.69109,-23.62596],[31.69589,-23.72155],[31.76937,-23.88772],[31.88095,-23.95268],[31.9091,-24.1812],[31.9835,-24.29983],[32.03196,-25.10785],[32.01676,-25.38117],[31.97896,-25.45776],[32.00631,-25.65044],[31.92987,-25.84037],[31.97537,-25.95271],[32.00272,-25.99614],[32.01072,-25.99353],[32.08633,-26.01178],[32.10435,-26.15656],[32.07424,-26.30603],[32.07763,-26.4053],[32.13409,-26.5317],[32.13315,-26.84345],[32.1861,-26.86436],[32.21847,-26.834],[32.34741,-26.86425],[32.74646,-26.86726],[32.89816,-26.8579],[33.10054,-26.92273],[39.10324,-21.48967],[41.06663,-17.08802],[40.74206,-10.25691],[40.44265,-10.4618],[40.01083,-10.80632],[39.58249,-10.96043],[39.24395,-11.17433],[38.88996,-11.16978],[38.47892,-11.42248],[38.21598,-11.27289],[37.93618,-11.26228],[37.8388,-11.3123],[37.76614,-11.53352],[37.3936,-11.68949],[36.80309,-11.56836],[36.62068,-11.72884],[36.19094,-11.70008],[36.19094,-11.57593],[35.82767,-11.41081],[35.63599,-11.55927],[34.96296,-11.57354],[34.7892,-12.06985],[34.27137,-12.48656],[34.86579,-13.48178],[35.47989,-14.15594],[35.5299,-14.27714],[35.86945,-14.67481],[35.87212,-14.89478],[35.91812,-14.89514],[35.78799,-15.17428],[35.85303,-15.41913],[35.80487,-16.03907],[35.70107,-16.10147],[35.67054,-16.09843],[35.54849,-16.13388],[35.52309,-16.16323],[35.43355,-16.11371],[35.30157,-16.2211],[35.25828,-16.4792],[35.14235,-16.56812],[35.27219,-16.69402],[35.30929,-16.82871],[35.27065,-16.93817],[35.30748,-17.12881],[35.09685,-17.12947],[35.04805,-17.00027],[35.17017,-16.93521],[35.13771,-16.81687],[35.02716,-16.83707],[34.71954,-16.49831],[34.40344,-16.20923],[34.43126,-16.04737],[34.25195,-15.90321],[34.28412,-15.78399],[34.36111,-15.73806],[34.44981,-15.60864],[34.43126,-15.44778],[34.60212,-15.28194],[34.56994,-15.11935],[34.59139,-15.09864],[34.57303,-15.06657],[34.6041,-15.04153],[34.60624,-15.02388],[34.61955,-15.01277],[34.62195,-15.00232],[34.61285,-14.99188],[34.61534,-14.98052],[34.59629,-14.96186],[34.57963,-14.90796],[34.56582,-14.89875],[34.57749,-14.87469],[34.57217,-14.82799],[34.58354,-14.80952],[34.56655,-14.76973],[34.54706,-14.75222],[34.54286,-14.73101],[34.52057,-14.68263],[34.53516,-14.67782],[34.55208,-14.64674],[34.54552,-14.63719],[34.54397,-14.60613],[34.53526,-14.58636],[34.52955,-14.58325],[34.51239,-14.55239],[34.49762,-14.55355],[34.49179,-14.53789],[34.47628,-14.53363],[34.45053,-14.49873],[34.44823,-14.47997],[34.42059,-14.43318],[34.39724,-14.41098],[34.39312,-14.39502],[34.37733,-14.39685],[34.37149,-14.38845],[34.35828,-14.38592],[34.35004,-14.39107],[34.34733,-14.3976],[34.31446,-14.40533],[34.30167,-14.3986],[34.27674,-14.41913],[34.20704,-14.44182],[34.18733,-14.43823],[34.08456,-14.45811],[34.08679,-14.48936],[33.91273,-14.47839],[33.81643,-14.55002],[33.72236,-14.49618],[33.66931,-14.61913],[33.38178,-14.21246],[33.34693,-14.22095],[33.30101,-14.14722],[33.30368,-14.10735],[33.32136,-14.08488],[33.30471,-14.0589],[33.29835,-14.03217],[33.26634,-14.02743],[33.24128,-13.99861],[32.69325,-14.18551],[32.45429,-14.28368],[32.18959,-14.3394],[31.95304,-14.41373],[31.4978,-14.60916],[30.21995,-14.98259]]]}},{"type":"Feature","properties":{"id":"MR"},"geometry":{"type":"Polygon","coordinates":[[[-17.0695,20.85742],[-17.0471,20.76408],[-16.75187,16.07666],[-16.50833,16.06725],[-16.48344,16.05802],[-16.44814,16.09753],[-16.4455,16.19786],[-16.37821,16.23709],[-16.27016,16.51565],[-16.12775,16.54702],[-15.98785,16.49798],[-15.81808,16.50983],[-15.79387,16.49765],[-15.65362,16.47279],[-15.62891,16.52168],[-15.50205,16.52513],[-15.51527,16.56101],[-15.44523,16.59309],[-15.42274,16.53846],[-15.09727,16.58717],[-15.12096,16.65],[-15.08062,16.67895],[-15.05556,16.62862],[-14.99925,16.64046],[-14.99462,16.69194],[-14.95359,16.68355],[-14.95016,16.64622],[-14.76545,16.63619],[-14.64271,16.64688],[-14.65215,16.62073],[-14.47551,16.625],[-14.37921,16.63569],[-14.32144,16.61495],[-13.9607,16.32294],[-13.9468,16.24483],[-13.87882,16.17544],[-13.84483,16.10651],[-13.78698,16.14164],[-13.70218,16.17527],[-13.71917,16.13735],[-13.63849,16.10651],[-13.54562,16.14295],[-13.37516,16.05785],[-13.36761,15.97667],[-13.26375,15.78713],[-13.29277,15.77193],[-13.21105,15.69626],[-13.25294,15.65857],[-13.08609,15.58236],[-13.07664,15.48561],[-12.96712,15.49404],[-12.92799,15.44358],[-12.9491,15.41727],[-12.9412,15.35024],[-12.89005,15.33799],[-12.82327,15.28749],[-12.89073,15.24162],[-12.78808,15.19955],[-12.7934,15.15531],[-12.74053,15.12151],[-12.66294,15.10908],[-12.60715,15.08405],[-12.56475,15.04095],[-12.50089,15.01907],[-12.46398,14.98873],[-12.44845,14.91708],[-12.4542,14.89377],[-12.39961,14.84524],[-12.33421,14.82998],[-12.27979,14.77297],[-12.23936,14.76324],[-12.17165,14.76077],[-12.14143,14.7787],[-12.08427,14.73338],[-11.97063,14.76326],[-11.79725,14.90331],[-11.84532,15.09532],[-11.69992,15.53887],[-11.63469,15.52415],[-11.43625,15.64849],[-11.00143,15.23615],[-10.89569,15.10891],[-10.70892,15.44342],[-10.63613,15.42257],[-10.38826,15.4411],[-10.27496,15.43349],[-10.07446,15.3706],[-9.68444,15.42786],[-9.40447,15.4396],[-9.44673,15.60553],[-9.33314,15.7044],[-9.31106,15.69412],[-9.32979,15.50032],[-5.50165,15.50061],[-5.33435,16.33354],[-5.60725,16.49919],[-6.57191,25.0002],[-4.83423,24.99935],[-8.66674,27.31569],[-8.66721,25.99918],[-12.0002,25.9986],[-12.00251,23.4538],[-12.14969,23.41935],[-12.36213,23.3187],[-12.5741,23.28975],[-13.00412,23.02297],[-13.10753,22.89493],[-13.15313,22.75649],[-13.08438,22.53866],[-13.01525,21.33343],[-16.95474,21.33997],[-16.99806,21.12142],[-17.0357,21.05368],[-17.0396,20.9961],[-17.06781,20.92697],[-17.0695,20.85742]]]}},{"type":"Feature","properties":{"id":"MW"},"geometry":{"type":"Polygon","coordinates":[[[32.67881,-13.634],[32.78314,-13.64279],[32.84697,-13.72191],[32.80526,-13.73521],[32.79594,-13.75164],[32.76962,-13.77224],[32.80431,-13.80024],[32.87117,-13.79982],[32.90551,-13.85533],[32.94439,-13.94997],[32.98782,-13.93281],[33.03417,-14.06298],[33.09837,-13.96355],[33.12832,-13.95822],[33.15399,-13.94073],[33.18643,-13.95847],[33.19055,-13.97954],[33.20651,-14.00186],[33.24128,-13.99861],[33.26634,-14.02743],[33.29835,-14.03217],[33.30471,-14.0589],[33.32136,-14.08488],[33.30368,-14.10735],[33.30101,-14.14722],[33.34693,-14.22095],[33.38178,-14.21246],[33.66931,-14.61913],[33.72236,-14.49618],[33.81643,-14.55002],[33.91273,-14.47839],[34.08679,-14.48936],[34.08456,-14.45811],[34.18733,-14.43823],[34.20704,-14.44182],[34.27674,-14.41913],[34.30167,-14.3986],[34.31446,-14.40533],[34.34733,-14.3976],[34.35004,-14.39107],[34.35828,-14.38592],[34.37149,-14.38845],[34.37733,-14.39685],[34.39312,-14.39502],[34.39724,-14.41098],[34.42059,-14.43318],[34.44823,-14.47997],[34.45053,-14.49873],[34.47628,-14.53363],[34.49179,-14.53789],[34.49762,-14.55355],[34.51239,-14.55239],[34.52955,-14.58325],[34.53526,-14.58636],[34.54397,-14.60613],[34.54552,-14.63719],[34.55208,-14.64674],[34.53516,-14.67782],[34.52057,-14.68263],[34.54286,-14.73101],[34.54706,-14.75222],[34.56655,-14.76973],[34.58354,-14.80952],[34.57217,-14.82799],[34.57749,-14.87469],[34.56582,-14.89875],[34.57963,-14.90796],[34.59629,-14.96186],[34.61534,-14.98052],[34.61285,-14.99188],[34.62195,-15.00232],[34.61955,-15.01277],[34.60624,-15.02388],[34.6041,-15.04153],[34.57303,-15.06657],[34.59139,-15.09864],[34.56994,-15.11935],[34.60212,-15.28194],[34.43126,-15.44778],[34.44981,-15.60864],[34.36111,-15.73806],[34.28412,-15.78399],[34.25195,-15.90321],[34.43126,-16.04737],[34.40344,-16.20923],[34.71954,-16.49831],[35.02716,-16.83707],[35.13771,-16.81687],[35.17017,-16.93521],[35.04805,-17.00027],[35.09685,-17.12947],[35.30748,-17.12881],[35.27065,-16.93817],[35.30929,-16.82871],[35.27219,-16.69402],[35.14235,-16.56812],[35.25828,-16.4792],[35.30157,-16.2211],[35.43355,-16.11371],[35.52309,-16.16323],[35.54849,-16.13388],[35.67054,-16.09843],[35.70107,-16.10147],[35.80487,-16.03907],[35.85303,-15.41913],[35.78799,-15.17428],[35.91812,-14.89514],[35.87212,-14.89478],[35.86945,-14.67481],[35.5299,-14.27714],[35.47989,-14.15594],[34.86579,-13.48178],[34.27137,-12.48656],[34.7892,-12.06985],[34.96296,-11.57354],[34.91153,-11.39799],[34.79375,-11.32245],[34.63305,-11.11731],[34.61161,-11.01611],[34.67047,-10.93796],[34.65946,-10.6828],[34.57581,-10.56271],[34.51911,-10.12279],[34.54499,-10.0678],[34.03865,-9.49398],[33.95829,-9.54066],[33.9638,-9.62206],[33.94208,-9.72174],[33.81587,-9.63142],[33.79879,-9.60807],[33.77849,-9.59254],[33.76677,-9.58516],[33.667,-9.61259],[33.58082,-9.58306],[33.48778,-9.62402],[33.42212,-9.59414],[33.42435,-9.57561],[33.34856,-9.50713],[33.30857,-9.48377],[33.27612,-9.49918],[33.24917,-9.48825],[33.2048,-9.50561],[33.14925,-9.49322],[33.03889,-9.4146],[32.99726,-9.36489],[32.95389,-9.40138],[32.93486,-9.43111],[33.01408,-9.50942],[32.98713,-9.5812],[33.00256,-9.63053],[33.06309,-9.61699],[33.09751,-9.68672],[33.12,-9.59474],[33.2095,-9.61099],[33.23827,-9.6637],[33.23776,-9.69044],[33.24334,-9.70838],[33.27587,-9.75922],[33.29938,-9.78578],[33.29672,-9.79288],[33.32565,-9.81766],[33.35929,-9.81546],[33.3653,-9.84506],[33.38075,-9.86603],[33.38762,-9.90882],[33.31297,-10.05133],[33.53863,-10.20148],[33.54797,-10.36077],[33.70675,-10.56896],[33.48049,-10.79469],[33.29355,-10.83583],[33.25998,-10.88862],[33.40393,-11.15633],[33.29267,-11.3789],[33.28737,-11.43762],[33.23663,-11.40637],[33.24252,-11.59302],[33.30574,-11.59035],[33.32479,-11.91841],[33.25998,-12.14242],[33.3705,-12.34931],[33.47636,-12.32498],[33.54709,-12.36347],[33.37517,-12.54085],[33.28177,-12.54692],[33.18837,-12.61377],[33.05917,-12.59554],[32.94397,-12.76868],[32.96733,-12.88251],[33.02181,-12.88707],[32.98353,-13.14685],[33.01374,-13.20953],[32.94507,-13.28088],[32.87899,-13.46625],[32.84517,-13.46308],[32.84176,-13.52794],[32.73771,-13.58592],[32.68604,-13.56473],[32.67881,-13.634]]]}},{"type":"Feature","properties":{"id":"NA"},"geometry":{"type":"Polygon","coordinates":[[[11.26455,-17.25284],[15.70388,-29.23989],[16.45332,-28.63117],[16.46592,-28.57126],[16.59922,-28.53246],[16.76685,-28.45027],[16.77955,-28.28563],[16.87705,-28.17507],[16.89937,-28.06092],[17.11051,-28.04774],[17.36801,-28.32009],[17.41779,-28.70624],[18.99742,-28.87414],[19.99882,-28.42622],[19.99817,-24.76768],[19.99912,-21.99991],[20.99751,-22.00026],[20.99904,-18.31743],[21.45556,-18.31795],[23.0996,-18.00075],[23.29618,-17.99855],[23.61088,-18.4881],[24.19416,-18.01919],[24.40577,-17.95726],[24.57485,-18.07151],[24.6303,-17.9863],[24.71887,-17.9218],[24.73364,-17.89338],[24.95586,-17.79674],[25.05895,-17.84452],[25.16882,-17.78253],[25.26433,-17.79571],[25.00198,-17.58221],[24.70864,-17.49501],[24.5621,-17.52963],[24.38712,-17.46818],[24.32811,-17.49082],[24.23619,-17.47489],[23.47474,-17.62877],[21.42831,-18.02509],[21.18747,-17.93235],[20.75385,-18.00746],[20.27046,-17.87028],[19.76165,-17.89903],[18.84226,-17.80375],[18.61118,-17.61751],[18.42063,-17.38963],[14.21493,-17.3911],[13.97735,-17.42959],[13.3695,-16.97635],[12.90653,-17.03233],[12.54878,-17.25148],[12.06573,-17.14275],[11.79313,-17.27066],[11.75063,-17.25013],[11.26455,-17.25284]]]}},{"type":"Feature","properties":{"id":"NE"},"geometry":{"type":"Polygon","coordinates":[[[0.16936,14.51654],[0.38051,14.05575],[0.46949,13.94739],[0.6063,13.77306],[0.596,13.71253],[0.61924,13.68491],[0.77377,13.6866],[0.77637,13.64442],[0.90156,13.62346],[0.99514,13.5668],[1.02813,13.46635],[1.20088,13.38951],[1.24429,13.39373],[1.29329,13.35638],[1.24516,13.33968],[1.21217,13.37853],[1.18873,13.31771],[0.99253,13.37515],[0.99167,13.10727],[1.86561,12.60683],[2.04654,12.72842],[2.17769,12.68857],[2.26349,12.41915],[2.05785,12.35539],[2.39723,11.89473],[2.45824,11.98672],[2.39657,12.10952],[2.37783,12.24804],[2.6593,12.30631],[2.84039,12.40707],[2.88459,12.37571],[2.90382,12.35383],[2.94656,12.31451],[2.96733,12.28574],[3.00536,12.27249],[3.06158,12.1949],[3.12543,12.15748],[3.27014,12.01312],[3.2662,11.98601],[3.29752,11.9342],[3.3098,11.88675],[3.39975,11.88053],[3.48187,11.86092],[3.53656,11.7825],[3.5636,11.77611],[3.55124,11.72872],[3.60978,11.69375],[3.67988,11.75429],[3.67122,11.80865],[3.63063,11.83042],[3.61955,11.91847],[3.67775,11.97599],[3.63136,12.11826],[3.66364,12.25884],[3.65381,12.52389],[3.94151,12.74667],[4.10528,12.97294],[4.14367,13.17189],[4.14184,13.47911],[4.23456,13.47725],[4.4668,13.68286],[4.87425,13.78],[4.91844,13.73605],[5.0053,13.73538],[5.07396,13.75052],[5.21026,13.73627],[5.27797,13.75474],[5.36647,13.84958],[5.53075,13.88657],[5.63117,13.83607],[5.82515,13.76356],[6.09071,13.67701],[6.15771,13.64564],[6.22941,13.673],[6.27714,13.67634],[6.42854,13.60127],[6.69617,13.34057],[6.81873,13.14551],[6.94181,13.00388],[7.0521,13.00076],[7.12171,13.02078],[7.21887,13.12595],[7.40959,13.09553],[7.81574,13.34285],[8.07997,13.30847],[8.25185,13.20369],[8.4217,13.05924],[8.49294,13.0763],[8.60431,13.01768],[8.6495,12.93848],[8.97413,12.83661],[9.65995,12.80614],[10.00373,13.18171],[10.19993,13.27129],[10.46731,13.28819],[10.65467,13.36122],[11.45685,13.38076],[11.67726,13.29875],[11.88428,13.25665],[12.03775,13.13715],[12.16189,13.10056],[12.20048,13.12896],[12.31756,13.08716],[12.48072,13.06167],[12.50158,13.14434],[12.53565,13.14844],[12.56046,13.19683],[12.52857,13.21003],[12.55166,13.20786],[12.55213,13.22682],[12.56861,13.22833],[12.58033,13.27805],[12.6196,13.27737],[12.64019,13.29608],[12.67066,13.27369],[12.6947,13.29792],[12.6862,13.31178],[12.71839,13.30677],[12.70912,13.32331],[12.74044,13.32581],[12.74628,13.35563],[12.76877,13.39095],[12.78585,13.37317],[12.79478,13.37951],[12.78199,13.39412],[12.83314,13.40598],[12.81366,13.42944],[12.85271,13.4443],[12.86953,13.49814],[12.90155,13.48612],[12.96086,13.51491],[13.01656,13.52952],[13.04085,13.52918],[13.066,13.54796],[13.08681,13.51583],[13.10085,13.53932],[13.12385,13.52167],[13.1211,13.53886],[13.1411,13.54796],[13.14033,13.521],[13.17295,13.53461],[13.20007,13.51491],[13.20917,13.54846],[13.23843,13.5669],[13.22758,13.58776],[13.25852,13.58934],[13.24766,13.60344],[13.27938,13.65482],[13.33808,13.71979],[13.6302,13.71094],[13.47559,14.40881],[13.48259,14.46704],[13.69843,14.55043],[13.67878,14.64013],[13.809,14.72915],[13.78991,14.87519],[13.86301,15.04043],[14.38316,15.73013],[15.50373,16.89649],[15.6032,18.77402],[15.75098,19.93002],[15.99632,20.35364],[15.6721,20.70069],[15.59841,20.74039],[15.56004,20.79488],[15.55382,20.86507],[15.57248,20.92138],[15.62515,20.95395],[15.28332,21.44557],[15.20213,21.49365],[15.19692,21.99339],[14.99751,23.00539],[14.22918,22.61719],[13.5631,23.16574],[11.96886,23.51735],[7.48273,20.87258],[7.38361,20.79165],[5.8153,19.45101],[4.26651,19.14224],[4.26762,17.00432],[4.21787,17.00118],[4.19893,16.39923],[3.50368,15.35934],[3.03134,15.42221],[3.01806,15.34571],[1.31275,15.27978],[0.96711,14.98275],[0.72632,14.95898],[0.70037,14.94412],[0.23859,15.00135],[0.23963,14.74433],[0.16936,14.51654]]]}},{"type":"Feature","properties":{"id":"NG"},"geometry":{"type":"Polygon","coordinates":[[[2.67523,7.87825],[2.73095,7.7755],[2.73405,7.5423],[2.78668,7.5116],[2.79559,7.43004],[2.74435,7.42485],[2.74572,7.27784],[2.77473,7.13496],[2.74057,7.106],[2.75516,7.04527],[2.71104,6.95147],[2.74194,6.9271],[2.73405,6.78508],[2.78941,6.75982],[2.78211,6.69495],[2.76254,6.6843],[2.7307,6.63749],[2.74795,6.57082],[2.70464,6.50831],[2.72031,6.48827],[2.70641,6.45126],[2.70566,6.38038],[2.74181,6.13349],[5.87055,3.78489],[8.34397,4.30689],[8.60302,4.87353],[8.78027,5.1243],[8.92029,5.58403],[8.83687,5.68483],[8.88156,5.78857],[8.84209,5.82562],[9.40189,6.3286],[9.46678,6.45655],[9.52634,6.43778],[9.63294,6.5171],[9.70315,6.51249],[9.78486,6.79059],[9.86314,6.77756],[10.15135,7.03781],[10.21466,6.88996],[10.53639,6.93432],[10.57214,7.16345],[10.59746,7.14719],[10.60789,7.06885],[10.83646,6.92216],[10.8179,6.83377],[10.96572,6.69086],[11.09644,6.68437],[11.09495,6.51717],[11.15936,6.50362],[11.23506,6.53176],[11.31814,6.50755],[11.42578,6.53211],[11.42749,6.59606],[11.51499,6.60892],[11.57755,6.74059],[11.55521,6.86047],[11.63117,6.9905],[11.87396,7.09398],[11.84864,7.26098],[11.93205,7.47812],[12.01844,7.52981],[11.99908,7.67302],[12.20909,7.97553],[12.19271,8.10826],[12.24782,8.17904],[12.25421,8.44418],[12.32768,8.4272],[12.4489,8.52536],[12.44356,8.61768],[12.49351,8.64127],[12.68474,8.65111],[12.71701,8.7595],[12.79,8.75361],[12.82327,8.96969],[12.90138,9.11023],[12.91958,9.33905],[12.85628,9.36698],[13.02385,9.49334],[13.22642,9.57266],[13.26118,9.77385],[13.29941,9.8296],[13.25025,9.86042],[13.24007,9.91474],[13.27697,9.93926],[13.286,9.9822],[13.25323,10.00127],[13.25025,10.03647],[13.34083,10.11641],[13.41739,10.12148],[13.43627,10.13196],[13.47061,10.16457],[13.53361,10.41657],[13.53446,10.46873],[13.57824,10.53642],[13.54964,10.61236],[13.63334,10.74696],[13.64398,10.80177],[13.71814,10.87713],[13.72879,10.92282],[13.70853,10.95636],[13.73085,10.98147],[13.73548,11.00641],[13.78217,11.00186],[13.88088,11.13545],[13.87418,11.14269],[13.91418,11.19002],[13.93873,11.22235],[13.9789,11.31427],[14.14352,11.25173],[14.15622,11.24213],[14.16223,11.24786],[14.17821,11.23831],[14.61353,11.5071],[14.64717,11.57623],[14.6434,11.65257],[14.55207,11.72001],[14.61612,11.7798],[14.60735,11.86768],[14.6422,11.91186],[14.66125,12.17896],[14.54778,12.23852],[14.53834,12.2865],[14.48118,12.35199],[14.2569,12.36892],[14.2339,12.3649],[14.20077,12.38519],[14.17579,12.42526],[14.19013,12.43816],[14.18154,12.46163],[14.20317,12.52624],[14.08251,13.0797],[13.6302,13.71094],[13.33808,13.71979],[13.27938,13.65482],[13.24766,13.60344],[13.25852,13.58934],[13.22758,13.58776],[13.23843,13.5669],[13.20917,13.54846],[13.20007,13.51491],[13.17295,13.53461],[13.14033,13.521],[13.1411,13.54796],[13.1211,13.53886],[13.12385,13.52167],[13.10085,13.53932],[13.08681,13.51583],[13.066,13.54796],[13.04085,13.52918],[13.01656,13.52952],[12.96086,13.51491],[12.90155,13.48612],[12.86953,13.49814],[12.85271,13.4443],[12.81366,13.42944],[12.83314,13.40598],[12.78199,13.39412],[12.79478,13.37951],[12.78585,13.37317],[12.76877,13.39095],[12.74628,13.35563],[12.74044,13.32581],[12.70912,13.32331],[12.71839,13.30677],[12.6862,13.31178],[12.6947,13.29792],[12.67066,13.27369],[12.64019,13.29608],[12.6196,13.27737],[12.58033,13.27805],[12.56861,13.22833],[12.55213,13.22682],[12.55166,13.20786],[12.52857,13.21003],[12.56046,13.19683],[12.53565,13.14844],[12.50158,13.14434],[12.48072,13.06167],[12.31756,13.08716],[12.20048,13.12896],[12.16189,13.10056],[12.03775,13.13715],[11.88428,13.25665],[11.67726,13.29875],[11.45685,13.38076],[10.65467,13.36122],[10.46731,13.28819],[10.19993,13.27129],[10.00373,13.18171],[9.65995,12.80614],[8.97413,12.83661],[8.6495,12.93848],[8.60431,13.01768],[8.49294,13.0763],[8.4217,13.05924],[8.25185,13.20369],[8.07997,13.30847],[7.81574,13.34285],[7.40959,13.09553],[7.21887,13.12595],[7.12171,13.02078],[7.0521,13.00076],[6.94181,13.00388],[6.81873,13.14551],[6.69617,13.34057],[6.42854,13.60127],[6.27714,13.67634],[6.22941,13.673],[6.15771,13.64564],[6.09071,13.67701],[5.82515,13.76356],[5.63117,13.83607],[5.53075,13.88657],[5.36647,13.84958],[5.27797,13.75474],[5.21026,13.73627],[5.07396,13.75052],[5.0053,13.73538],[4.91844,13.73605],[4.87425,13.78],[4.4668,13.68286],[4.23456,13.47725],[4.14184,13.47911],[4.14367,13.17189],[4.10528,12.97294],[3.94151,12.74667],[3.65381,12.52389],[3.66364,12.25884],[3.63136,12.11826],[3.67775,11.97599],[3.61955,11.91847],[3.63063,11.83042],[3.67122,11.80865],[3.67988,11.75429],[3.60978,11.69375],[3.57604,11.66871],[3.47682,11.4388],[3.6938,11.12989],[3.72264,11.13444],[3.84243,10.59316],[3.78292,10.40538],[3.66943,10.4689],[3.57275,10.27185],[3.66668,10.18417],[3.54429,9.87739],[3.35383,9.83641],[3.32099,9.78032],[3.34726,9.70696],[3.13928,9.47167],[3.14147,9.28375],[3.08017,9.10006],[2.77907,9.06924],[2.78829,8.97138],[2.76151,8.87201],[2.75413,8.19925],[2.67523,7.87825]]]}},{"type":"Feature","properties":{"id":"NI"},"geometry":{"type":"Polygon","coordinates":[[[-87.90019,12.84737],[-86.03263,11.09328],[-85.70132,11.0648],[-85.60949,11.21958],[-84.91024,10.9449],[-84.68433,11.08306],[-84.22187,10.87241],[-84.17381,10.79486],[-83.90018,10.71728],[-83.66397,10.80835],[-83.68276,11.01562],[-82.78663,12.4173],[-83.13355,13.06878],[-82.28946,14.65367],[-83.04763,15.03256],[-83.13724,15.00002],[-83.21405,14.99354],[-83.5124,15.01211],[-83.62101,14.89448],[-83.69281,14.87129],[-83.8801,14.76907],[-84.10755,14.75927],[-84.35285,14.69453],[-84.57635,14.65484],[-84.70381,14.68473],[-84.80587,14.82965],[-84.90082,14.80489],[-85.00465,14.72209],[-85.02147,14.6065],[-85.14249,14.54935],[-85.21287,14.37615],[-85.17391,14.25839],[-85.33973,14.25123],[-85.41715,14.12026],[-85.7591,13.95622],[-85.74966,13.84141],[-86.04045,13.99537],[-86.00818,14.08047],[-86.12422,14.05033],[-86.34258,13.76706],[-86.54617,13.80107],[-86.77843,13.77373],[-86.70169,13.30126],[-86.84683,13.30385],[-86.91327,13.26567],[-86.92666,13.19073],[-86.95301,13.06442],[-86.95507,13.03733],[-87.03785,12.98682],[-87.06665,13.00455],[-87.37107,12.98646],[-87.61441,13.1834],[-87.90019,12.84737]]]}},{"type":"Feature","properties":{"id":"BV"},"geometry":{"type":"Polygon","coordinates":[[[2.85578,-54.70531],[3.87126,-54.71036],[3.87947,-54.15611],[2.86398,-54.15099],[2.85578,-54.70531]]]}},{"type":"Feature","properties":{"id":"NP"},"geometry":{"type":"Polygon","coordinates":[[[80.05743,28.91479],[80.06466,28.83813],[80.07471,28.82452],[80.11762,28.8288],[80.21495,28.75562],[80.25701,28.75396],[80.26576,28.71994],[80.37589,28.63071],[80.43906,28.63576],[80.52443,28.54897],[80.50575,28.6706],[80.55142,28.69182],[80.72427,28.57472],[80.89648,28.47237],[81.08507,28.38346],[81.19847,28.36284],[81.32923,28.13521],[81.38683,28.17638],[81.48179,28.12148],[81.47867,28.08303],[81.91223,27.84995],[81.97214,27.93322],[82.06554,27.92222],[82.46405,27.6716],[82.70378,27.72122],[82.75073,27.5865],[82.73597,27.50202],[82.80395,27.497],[82.93261,27.50328],[82.94969,27.47004],[83.03552,27.44781],[83.16993,27.45694],[83.27197,27.38309],[83.2673,27.36235],[83.29999,27.32778],[83.35136,27.33885],[83.40579,27.40758],[83.38932,27.4804],[83.61288,27.47013],[83.85595,27.35797],[83.86182,27.4241],[83.93306,27.44939],[84.02229,27.43836],[84.10791,27.52399],[84.21376,27.45218],[84.25735,27.44941],[84.29315,27.39],[84.62161,27.33885],[84.68708,27.22295],[84.67257,27.09726],[84.64725,27.07632],[84.64399,27.04613],[84.75686,27.00308],[84.77651,27.01623],[84.793,26.9968],[84.82531,27.02063],[84.84904,27.0095],[84.85187,27.00889],[84.85333,27.00836],[84.85754,26.98984],[84.86608,26.98354],[84.89152,26.97241],[84.96687,26.95599],[84.97186,26.9149],[85.00536,26.89523],[85.05592,26.88991],[85.0237,26.85003],[85.15883,26.86966],[85.19291,26.86909],[85.18046,26.80519],[85.21159,26.75933],[85.34302,26.74954],[85.47752,26.79292],[85.56471,26.84133],[85.5757,26.85955],[85.59388,26.85095],[85.61621,26.86721],[85.66239,26.84822],[85.71996,26.8207],[85.73483,26.79613],[85.72315,26.67471],[85.73756,26.64784],[85.76907,26.63076],[85.79386,26.62528],[85.82317,26.59865],[85.83126,26.61134],[85.85126,26.60866],[85.85583,26.59017],[85.84562,26.56254],[85.94664,26.61492],[85.96312,26.65137],[86.03453,26.66502],[86.06934,26.65731],[86.13942,26.61738],[86.13596,26.60651],[86.18662,26.61515],[86.19812,26.59489],[86.23151,26.58975],[86.25546,26.61492],[86.31975,26.61922],[86.39545,26.58484],[86.4563,26.5668],[86.49726,26.54218],[86.54258,26.53819],[86.57217,26.49661],[86.61313,26.48658],[86.62686,26.46891],[86.69277,26.45044],[86.74025,26.42386],[86.76797,26.45892],[86.83301,26.43906],[86.94543,26.52076],[86.95912,26.52076],[87.01559,26.53228],[87.04004,26.56595],[87.04691,26.58685],[87.0707,26.58571],[87.09147,26.45039],[87.15325,26.40509],[87.18863,26.40558],[87.24682,26.4143],[87.26587,26.40592],[87.26569,26.37518],[87.34568,26.34787],[87.36491,26.40463],[87.46566,26.44058],[87.51,26.43188],[87.5473,26.41819],[87.55274,26.40596],[87.58815,26.3914],[87.58678,26.38187],[87.60498,26.38025],[87.61261,26.39033],[87.62763,26.39371],[87.65132,26.39379],[87.64978,26.40597],[87.67785,26.41608],[87.68033,26.43764],[87.73308,26.40828],[87.7605,26.40805],[87.78951,26.47303],[87.82865,26.44982],[87.83792,26.43484],[87.86144,26.44921],[87.85869,26.46327],[87.8895,26.48724],[87.9089,26.45996],[87.8989,26.44886],[87.92452,26.44583],[87.92083,26.42984],[87.93332,26.41758],[87.96607,26.39755],[87.99164,26.39202],[87.99233,26.3711],[88.00572,26.36145],[88.02992,26.36457],[88.02906,26.38494],[88.09284,26.43706],[88.104,26.46957],[88.08923,26.50168],[88.10382,26.51927],[88.09963,26.54195],[88.12665,26.57993],[88.13163,26.6031],[88.16452,26.64111],[88.16502,26.67798],[88.19107,26.75516],[88.16914,26.872],[88.13747,26.89872],[88.14549,26.92055],[88.13781,26.93329],[88.12084,26.95051],[88.12219,26.96845],[88.13519,26.98625],[88.11743,26.9882],[88.02108,27.08747],[87.98975,27.1175],[88.00889,27.15142],[88.01646,27.21612],[88.07277,27.43007],[88.04008,27.49223],[88.19107,27.79285],[88.1973,27.85067],[88.13378,27.88015],[87.82681,27.95248],[87.72718,27.80938],[87.56996,27.84517],[87.11696,27.84104],[87.03757,27.94835],[86.75582,28.04182],[86.74181,28.10638],[86.56265,28.09569],[86.51609,27.96623],[86.42736,27.91122],[86.22966,27.9786],[86.18607,28.17364],[86.088,28.09264],[86.08333,28.02121],[86.12069,27.93047],[86.06309,27.90021],[85.9486,27.94085],[85.97813,27.99023],[85.90743,28.05144],[85.84672,28.18187],[85.74864,28.23126],[85.71907,28.38064],[85.69105,28.38475],[85.60854,28.25045],[85.59765,28.30529],[85.4233,28.32996],[85.38127,28.28336],[85.10729,28.34092],[85.18668,28.54076],[85.19135,28.62825],[85.06059,28.68562],[84.85511,28.58041],[84.62317,28.73887],[84.47528,28.74023],[84.2231,28.89571],[84.24801,29.02783],[84.18107,29.23451],[83.97559,29.33091],[83.82303,29.30513],[83.63156,29.16249],[83.44787,29.30513],[83.28131,29.56813],[83.07116,29.61957],[82.73024,29.81695],[82.5341,29.9735],[82.38622,30.02608],[82.16984,30.0692],[82.19475,30.16884],[82.10757,30.23745],[82.10135,30.35439],[81.99082,30.33423],[81.62033,30.44703],[81.41018,30.42153],[81.39928,30.21862],[81.33355,30.15303],[81.2623,30.14596],[81.29032,30.08806],[81.24362,30.0126],[81.12842,30.01395],[81.03953,30.20059],[80.92906,30.17644],[80.8925,30.22273],[80.87705,30.12798],[80.78281,30.06731],[80.74384,29.99924],[80.72161,30.00013],[80.67389,29.95657],[80.65355,29.95471],[80.62917,29.96512],[80.60128,29.9582],[80.57441,29.92161],[80.57218,29.89453],[80.55948,29.86811],[80.56247,29.86661],[80.55446,29.86107],[80.55287,29.85054],[80.54802,29.85021],[80.54536,29.84712],[80.53862,29.84734],[80.53506,29.83848],[80.53047,29.83781],[80.52291,29.8282],[80.50699,29.82269],[80.49875,29.81227],[80.49283,29.79514],[80.46159,29.80218],[80.44494,29.79905],[80.43485,29.80557],[80.43026,29.7989],[80.41009,29.79417],[80.39713,29.75878],[80.38576,29.75893],[80.3858,29.75062],[80.36769,29.75014],[80.36477,29.74086],[80.36636,29.72406],[80.37825,29.70154],[80.38155,29.68693],[80.38975,29.66504],[80.40584,29.65952],[80.42447,29.63106],[80.40824,29.61805],[80.40803,29.59741],[80.38932,29.57241],[80.37876,29.57129],[80.37932,29.56091],[80.34147,29.553],[80.35726,29.53201],[80.34464,29.51245],[80.28662,29.47644],[80.3019,29.45193],[80.24272,29.44389],[80.28113,29.34417],[80.31718,29.31237],[80.30611,29.28946],[80.29336,29.19604],[80.24285,29.219],[80.26843,29.14061],[80.23049,29.11666],[80.1844,29.13746],[80.13187,29.09232],[80.12895,29.06217],[80.13547,29.06007],[80.11745,28.98156],[80.10011,28.98546],[80.05743,28.91479]]]}},{"type":"Feature","properties":{"id":"NR"},"geometry":{"type":"Polygon","coordinates":[[[166.65,-0.8],[167.2,-0.8],[167.2,-0.26],[166.65,-0.26],[166.65,-0.8]]]}},{"type":"Feature","properties":{"id":"PK"},"geometry":{"type":"Polygon","coordinates":[[[60.87249,29.8597],[61.31508,29.38903],[61.55055,28.97766],[61.63673,28.82151],[61.93581,28.55284],[62.40259,28.42703],[62.59499,28.24842],[62.79412,28.28108],[62.77107,28.08652],[62.84905,27.47627],[62.79684,27.34381],[62.80815,27.21341],[63.19649,27.25674],[63.32828,27.13461],[63.25005,27.08692],[63.25005,26.84212],[63.18688,26.83844],[63.19353,26.64316],[62.77352,26.64099],[62.31256,26.51988],[62.21304,26.26601],[62.05117,26.31647],[61.89391,26.26251],[61.83831,26.07249],[61.83968,25.7538],[61.683,25.66638],[61.54432,24.57536],[68.11329,23.53945],[68.20763,23.85849],[68.39339,23.96838],[68.74643,23.97027],[68.76411,24.30736],[68.92719,24.31393],[68.99105,24.21972],[69.09679,24.272],[69.1888,24.23256],[69.30484,24.27826],[69.59579,24.29777],[69.73335,24.17007],[70.03428,24.172],[70.11268,24.29359],[70.58235,24.42339],[70.58097,24.25447],[70.70079,24.21377],[70.90164,24.22066],[70.87142,24.29766],[70.98266,24.36586],[71.04461,24.34657],[71.12838,24.42662],[71.00341,24.46038],[70.97594,24.60904],[71.09405,24.69017],[70.94002,24.92843],[70.89148,25.15064],[70.66695,25.39314],[70.67382,25.68186],[70.60378,25.71898],[70.53649,25.68928],[70.37444,25.67443],[70.2687,25.71156],[70.0985,25.93238],[70.08193,26.08094],[70.17532,26.24118],[70.17532,26.55362],[70.05584,26.60398],[69.88555,26.56836],[69.50904,26.74892],[69.58519,27.18109],[70.03136,27.56627],[70.12502,27.8057],[70.37307,28.01208],[70.60927,28.02178],[70.79054,27.68423],[71.89921,27.96035],[71.9244,28.11555],[72.20329,28.3869],[72.29495,28.66367],[72.40402,28.78283],[72.94272,29.02487],[73.01337,29.16422],[73.08654,29.23877],[73.28094,29.56646],[73.3962,29.94707],[73.58665,30.01848],[73.80299,30.06969],[73.97225,30.19829],[73.95736,30.28466],[73.88993,30.36305],[73.93953,30.40796],[73.93232,30.49483],[73.95566,30.46938],[74.01849,30.52441],[74.07154,30.52426],[74.09694,30.56684],[74.10793,30.61147],[74.23427,30.72294],[74.27959,30.74006],[74.32371,30.84756],[74.37074,30.85493],[74.41829,30.93594],[74.53957,30.98997],[74.55948,31.04359],[74.68497,31.05594],[74.69836,31.12467],[74.60008,31.13334],[74.60952,31.09586],[74.56023,31.08303],[74.51013,31.13848],[74.53223,31.30321],[74.60334,31.42507],[74.64574,31.41796],[74.65355,31.45685],[74.58626,31.51175],[74.62248,31.54686],[74.57498,31.60382],[74.48884,31.71809],[74.55253,31.76655],[74.60866,31.88776],[74.81595,31.96439],[74.8774,32.04991],[74.93079,32.06657],[74.99516,32.04147],[75.25763,32.09951],[75.32432,32.21527],[75.36672,32.22325],[75.37719,32.2716],[75.32226,32.29946],[75.33265,32.32703],[75.28784,32.37322],[75.1954,32.40445],[75.19334,32.42402],[75.13532,32.41329],[75.10906,32.47428],[75.02683,32.49745],[74.99181,32.45024],[74.82101,32.49796],[74.81243,32.48116],[74.68351,32.49209],[74.69098,32.52995],[74.67175,32.56844],[74.65251,32.56416],[74.64068,32.61118],[74.69758,32.66351],[74.65227,32.69724],[74.70952,32.84202],[74.64008,32.82089],[74.6369,32.75407],[74.54137,32.74815],[74.46335,32.77695],[74.44687,32.79506],[74.41143,32.89904],[74.30783,32.92858],[74.33976,32.95682],[74.31854,33.02891],[74.17548,33.075],[74.15374,33.13477],[74.01309,33.21556],[74.00794,33.25462],[74.1275,33.30571],[74.17983,33.3679],[74.18354,33.47626],[74.1372,33.56092],[74.03295,33.57662],[73.98296,33.64338],[73.98968,33.66155],[73.96232,33.72298],[74.00891,33.75437],[74.05763,33.82443],[74.14001,33.83002],[74.2783,33.89535],[74.26637,33.98635],[74.21625,34.02605],[74.07875,34.03523],[73.94373,34.01617],[73.89636,34.05635],[73.90678,34.10686],[74.01077,34.21677],[73.90517,34.35317],[73.8475,34.32935],[73.74862,34.34183],[73.75731,34.38226],[73.88732,34.48911],[73.90125,34.51843],[73.95584,34.56269],[73.93401,34.63386],[73.9706,34.68516],[74.12149,34.67528],[74.31667,34.78673],[74.58137,34.76389],[74.6663,34.703],[74.89194,34.66628],[75.01479,34.64629],[75.38009,34.55021],[75.75438,34.51827],[76.04614,34.67566],[76.14074,34.63462],[76.47186,34.78965],[76.67409,34.74598],[76.74377,34.84039],[76.77675,34.94702],[76.89966,34.92999],[76.93725,34.99569],[77.11796,35.05419],[76.93465,35.39866],[76.85088,35.39754],[76.75475,35.52617],[76.77323,35.66062],[76.50961,35.8908],[76.33453,35.84296],[76.14913,35.82848],[76.15325,35.9264],[75.93028,36.13136],[76.00906,36.17511],[76.0324,36.41198],[75.92391,36.56986],[75.72737,36.7529],[75.45562,36.71971],[75.40481,36.95382],[75.13839,37.02622],[74.56453,37.03023],[74.53739,36.96224],[74.43389,37.00977],[74.04856,36.82648],[73.82685,36.91421],[72.6323,36.84601],[72.18135,36.71838],[71.80267,36.49924],[71.60491,36.39429],[71.19505,36.04134],[71.37969,35.95865],[71.55273,35.71483],[71.49917,35.6267],[71.65781,35.44284],[71.54294,35.31037],[71.5541,35.28776],[71.68647,35.21091],[71.52938,35.09023],[71.55996,35.02627],[71.50194,35.00398],[71.51275,34.97473],[71.29472,34.87728],[71.28356,34.80882],[71.0842,34.68975],[71.11536,34.62911],[71.0089,34.54568],[71.023,34.4508],[71.17758,34.36306],[71.12815,34.26619],[71.13078,34.16503],[71.09453,34.13524],[71.09307,34.11961],[71.06933,34.10564],[71.08068,34.06538],[70.88119,33.97933],[70.54336,33.9463],[69.90203,34.04194],[69.87307,33.9689],[69.85794,33.92562],[69.90034,33.90333],[69.91699,33.85331],[69.94986,33.83056],[69.99801,33.73576],[70.14236,33.71701],[70.14785,33.6553],[70.20141,33.64387],[70.17062,33.53535],[70.32775,33.34496],[70.13686,33.21064],[70.07114,33.21951],[70.02563,33.14282],[69.84601,33.09111],[69.79766,33.13247],[69.71526,33.09911],[69.57656,33.09911],[69.49341,33.02025],[69.49813,32.88629],[69.54731,32.87504],[69.46758,32.85399],[69.38018,32.76601],[69.43649,32.7302],[69.44747,32.6678],[69.38155,32.56601],[69.2712,32.53046],[69.23599,32.45946],[69.28785,32.29235],[69.27032,32.14141],[69.3225,31.93186],[69.20577,31.85957],[69.11514,31.70782],[69.00939,31.62249],[68.95995,31.64822],[68.91078,31.59687],[68.79997,31.61665],[68.6956,31.75687],[68.57475,31.83158],[68.4649,31.76553],[68.27605,31.75863],[68.25614,31.80357],[68.1655,31.82691],[68.00071,31.6564],[67.86066,31.6259],[67.72056,31.52304],[67.58323,31.52772],[67.62374,31.40473],[67.79079,31.41826],[67.80229,31.31756],[67.72384,31.32534],[67.68487,31.30202],[67.31357,31.19371],[67.15547,31.24531],[67.02724,31.2379],[67.04147,31.31561],[66.86571,31.2828],[66.78588,31.20942],[66.72561,31.20526],[66.68166,31.07597],[66.57852,30.97657],[66.45423,30.95777],[66.45054,30.95361],[66.42668,30.95251],[66.38823,30.92947],[66.28413,30.57001],[66.35948,30.41818],[66.23609,30.06321],[66.37458,29.9698],[66.25305,29.85017],[65.06584,29.53045],[64.62116,29.58903],[64.19796,29.50407],[64.14539,29.38726],[64.03449,29.41343],[63.99793,29.39563],[63.8891,29.43705],[63.5717,29.48652],[62.47751,29.40782],[60.87249,29.8597]]]}},{"type":"Feature","properties":{"id":"PA"},"geometry":{"type":"Polygon","coordinates":[[[-83.05209,8.33394],[-82.9388,8.26634],[-82.88592,8.10851],[-82.89081,8.05204],[-82.90317,8.04061],[-82.94503,7.93865],[-82.13751,6.97312],[-78.06168,7.07793],[-77.89178,7.22681],[-77.81426,7.48319],[-77.72157,7.47612],[-77.72514,7.72348],[-77.57185,7.51147],[-77.17257,7.97422],[-77.45064,8.49991],[-77.32389,8.81247],[-77.58292,9.22278],[-78.79327,9.93766],[-82.51044,9.65379],[-82.56507,9.57279],[-82.5547,9.53989],[-82.58903,9.56012],[-82.61345,9.49881],[-82.66667,9.49746],[-82.74456,9.58026],[-82.84953,9.6219],[-82.88523,9.56977],[-82.84871,9.4973],[-82.93516,9.46741],[-82.93516,9.07687],[-82.70542,8.95697],[-82.76782,8.88541],[-82.85399,8.85708],[-82.88652,8.82994],[-82.86652,8.80653],[-82.91484,8.77023],[-82.91931,8.74749],[-82.87412,8.69735],[-82.83459,8.61522],[-82.82232,8.56667],[-82.83975,8.54755],[-82.83854,8.5323],[-82.83339,8.52364],[-82.8382,8.48117],[-82.8679,8.44042],[-82.9145,8.42958],[-82.93056,8.43465],[-83.05209,8.33394]]]}},{"type":"Feature","properties":{"id":"PE"},"geometry":{"type":"Polygon","coordinates":[[[-84.52388,-3.36941],[-84.46057,-7.80762],[-78.15039,-17.99635],[-73.98689,-20.10822],[-70.59118,-18.35072],[-70.378,-18.3495],[-70.31267,-18.31258],[-70.16394,-18.31737],[-69.96732,-18.25992],[-69.81607,-18.12582],[-69.75305,-17.94605],[-69.82868,-17.72048],[-69.79087,-17.65563],[-69.66483,-17.65083],[-69.46846,-17.49842],[-69.46863,-17.37466],[-69.57641,-17.29164],[-69.6401,-17.28606],[-69.6001,-17.21524],[-69.34021,-16.98193],[-69.16734,-16.7286],[-69.00853,-16.66769],[-69.04027,-16.57214],[-68.98358,-16.42165],[-68.79464,-16.33272],[-68.89354,-16.25983],[-68.96135,-16.19452],[-69.10014,-16.22596],[-69.20291,-16.16668],[-69.40336,-15.61358],[-69.14856,-15.23478],[-69.36254,-14.94634],[-69.35291,-14.79845],[-68.8932,-14.2138],[-69.05799,-13.67067],[-68.8864,-13.40792],[-68.85615,-12.87769],[-68.65044,-12.50689],[-68.98115,-11.8979],[-69.57156,-10.94555],[-69.57835,-10.94051],[-69.90896,-10.92744],[-70.38791,-11.07096],[-70.51395,-10.92249],[-70.61593,-10.99833],[-70.62487,-9.80666],[-70.55429,-9.76692],[-70.58453,-9.58303],[-70.53373,-9.42628],[-71.23394,-9.9668],[-72.14721,-9.99928],[-72.31883,-9.5184],[-72.72216,-9.41397],[-73.21498,-9.40904],[-72.92886,-9.04074],[-73.76576,-7.89884],[-73.65485,-7.77897],[-73.96938,-7.58465],[-73.77011,-7.28944],[-73.73986,-6.87919],[-73.12983,-6.43852],[-73.24579,-6.05764],[-72.83973,-5.14765],[-72.64391,-5.0391],[-71.87003,-4.51661],[-70.96814,-4.36915],[-70.77601,-4.15717],[-70.33236,-4.15214],[-70.19582,-4.3607],[-70.11305,-4.27281],[-70.04144,-4.3429],[-70.05483,-4.36737],[-70.02694,-4.37268],[-70.02917,-4.35214],[-69.99449,-4.32082],[-69.95389,-4.32159],[-69.94741,-4.22816],[-70.28228,-3.82383],[-70.36708,-3.79711],[-70.49789,-3.88172],[-70.57617,-3.81698],[-70.71693,-3.79762],[-70.04609,-2.73906],[-70.94377,-2.23142],[-71.28376,-2.37682],[-71.74106,-2.14869],[-72.02362,-2.3161],[-72.92587,-2.44514],[-73.15521,-2.24852],[-73.21666,-1.74827],[-73.423,-1.77057],[-73.65312,-1.26222],[-74.26675,-0.97229],[-74.42701,-0.50218],[-75.18513,-0.0308],[-75.25764,-0.11943],[-75.40192,-0.17196],[-75.61997,-0.10012],[-75.60169,-0.18708],[-75.53615,-0.19213],[-75.22862,-0.60048],[-75.22862,-0.95588],[-75.3872,-0.9374],[-75.57429,-1.55961],[-76.05203,-2.12179],[-76.63547,-2.59017],[-77.94147,-3.05454],[-78.19369,-3.36431],[-78.14324,-3.47653],[-78.22642,-3.51113],[-78.24589,-3.39907],[-78.34362,-3.38633],[-78.38624,-3.64037],[-78.68394,-4.60754],[-78.85814,-4.67378],[-79.01659,-5.01481],[-79.10533,-4.97893],[-79.16181,-4.9632],[-79.26511,-4.96696],[-79.3824,-4.82689],[-79.45861,-4.70149],[-79.46428,-4.65778],[-79.48651,-4.62681],[-79.49011,-4.59567],[-79.48402,-4.53296],[-79.51269,-4.51593],[-79.54256,-4.5268],[-79.56616,-4.50891],[-79.60221,-4.45633],[-79.64358,-4.43344],[-79.70272,-4.46493],[-79.79876,-4.49659],[-79.83987,-4.47083],[-79.86408,-4.42232],[-79.91231,-4.3892],[-79.95154,-4.39861],[-79.99068,-4.38158],[-80.13882,-4.29326],[-80.16672,-4.29642],[-80.23847,-4.38682],[-80.28671,-4.41573],[-80.30096,-4.4414],[-80.36061,-4.47682],[-80.39121,-4.4808],[-80.4509,-4.42668],[-80.4485,-4.37384],[-80.42889,-4.3518],[-80.4276,-4.33802],[-80.36816,-4.28179],[-80.30954,-4.20227],[-80.45023,-4.20938],[-80.44953,-4.12908],[-80.48446,-4.08893],[-80.47584,-4.07561],[-80.47528,-4.06102],[-80.4822,-4.05477],[-80.46987,-4.04509],[-80.45579,-4.0269],[-80.46386,-4.01342],[-80.39812,-3.9819],[-80.29478,-4.0129],[-80.12603,-3.89439],[-80.15341,-3.85483],[-80.19926,-3.68894],[-80.18817,-3.66221],[-80.18741,-3.63994],[-80.19289,-3.62307],[-80.19092,-3.59925],[-80.19848,-3.59249],[-80.21143,-3.59368],[-80.21787,-3.57475],[-80.21186,-3.56524],[-80.20813,-3.53351],[-80.21787,-3.50048],[-80.22834,-3.50074],[-80.23427,-3.48712],[-80.24585,-3.4872],[-80.24296,-3.48135],[-80.24484,-3.47898],[-80.23999,-3.47701],[-80.24137,-3.46137],[-80.20647,-3.431],[-80.30602,-3.39149],[-84.52388,-3.36941]]]}},{"type":"Feature","properties":{"id":"PG"},"geometry":{"type":"Polygon","coordinates":[[[140.85295,-6.72996],[141.01763,-6.90181],[141.00782,-9.1242],[140.88922,-9.34945],[142.0601,-9.56571],[142.0953,-9.23534],[142.1462,-9.19923],[142.23304,-9.19253],[142.31447,-9.24611],[142.5723,-9.35994],[142.81927,-9.31709],[144.30183,-9.48146],[155.22803,-12.9001],[154.74815,-7.33315],[155.29973,-7.02898],[155.94342,-6.85209],[156.07484,-6.52458],[157.60997,-5.69776],[156.88247,-1.39237],[141.00167,0.68547],[140.99934,-3.92043],[140.9987,-5.17984],[140.99813,-6.3233],[140.85295,-6.72996]]]}},{"type":"Feature","properties":{"id":"PL"},"geometry":{"type":"Polygon","coordinates":[[[14.12256,52.84311],[14.13806,52.82392],[14.22071,52.81175],[14.61073,52.59847],[14.6289,52.57136],[14.60081,52.53116],[14.63056,52.48993],[14.54423,52.42568],[14.55228,52.35264],[14.56378,52.33838],[14.58149,52.28007],[14.70139,52.25038],[14.71319,52.22144],[14.68344,52.19612],[14.70616,52.16927],[14.67683,52.13936],[14.6917,52.10283],[14.72971,52.09167],[14.76026,52.06624],[14.71339,52.00337],[14.70488,51.97679],[14.7139,51.95643],[14.71836,51.95606],[14.72163,51.95188],[14.7177,51.94048],[14.70601,51.92944],[14.69412,51.90234],[14.65601,51.88422],[14.64563,51.86801],[14.59027,51.83636],[14.60493,51.80473],[14.64625,51.79472],[14.66386,51.73282],[14.69065,51.70842],[14.75392,51.67445],[14.75759,51.62318],[14.7727,51.61263],[14.71125,51.56209],[14.73047,51.54606],[14.72652,51.53902],[14.73219,51.52922],[14.94749,51.47155],[14.9652,51.44793],[14.96899,51.38367],[14.98008,51.33449],[15.04288,51.28387],[15.01242,51.21285],[15.0047,51.16874],[14.99311,51.16249],[14.99414,51.15813],[15.00083,51.14974],[14.99646,51.14365],[14.99079,51.14284],[14.99689,51.12205],[14.98229,51.11354],[14.97938,51.07742],[14.95529,51.04552],[14.92942,50.99744],[14.89252,50.94999],[14.89681,50.9422],[14.81664,50.88148],[14.82803,50.86966],[14.86269,50.8694],[14.86754,50.87745],[15.00054,50.86144],[14.99659,50.90054],[15.01633,50.97837],[14.96419,50.99108],[15.02433,51.0242],[15.04131,51.01029],[15.06019,51.02304],[15.10152,51.01095],[15.11937,50.99021],[15.148,51.01413],[15.18276,51.01656],[15.1743,50.9833],[15.2361,50.99886],[15.28807,50.9635],[15.27039,50.93441],[15.2773,50.8907],[15.36656,50.83956],[15.3803,50.77187],[15.43798,50.80833],[15.73186,50.73885],[15.81683,50.75666],[15.87331,50.67188],[15.97884,50.69743],[16.0175,50.63009],[15.98317,50.61528],[16.02437,50.60046],[16.10265,50.66405],[16.21135,50.63013],[16.23174,50.67101],[16.33611,50.66579],[16.44597,50.58041],[16.40868,50.56372],[16.41099,50.54818],[16.39391,50.54321],[16.36053,50.49606],[16.32426,50.50207],[16.26448,50.47984],[16.2556,50.46766],[16.23714,50.46075],[16.23229,50.44323],[16.2029,50.44722],[16.19526,50.43291],[16.21585,50.40627],[16.23684,50.41431],[16.28276,50.36648],[16.30289,50.38292],[16.36495,50.37679],[16.3622,50.34875],[16.39379,50.3207],[16.42674,50.32509],[16.46241,50.29668],[16.46713,50.2855],[16.48,50.27647],[16.48361,50.27162],[16.54382,50.23142],[16.55699,50.21379],[16.56373,50.1797],[16.56021,50.16441],[16.63137,50.1142],[16.7014,50.09659],[16.8456,50.20834],[16.98018,50.24172],[17.00353,50.21449],[17.02825,50.23118],[16.99803,50.25753],[17.02138,50.27772],[16.99803,50.30316],[16.94448,50.31281],[16.90877,50.38642],[16.85933,50.41093],[16.88169,50.44395],[16.90916,50.44865],[16.97748,50.41956],[17.1224,50.39494],[17.14498,50.38117],[17.19579,50.38817],[17.19991,50.3654],[17.28372,50.31707],[17.34981,50.32853],[17.33831,50.2833],[17.35196,50.26289],[17.37307,50.28325],[17.42929,50.27145],[17.58889,50.27837],[17.67764,50.28977],[17.69292,50.32859],[17.74648,50.29966],[17.72176,50.25665],[17.76609,50.23545],[17.75493,50.21426],[17.70528,50.18812],[17.58859,50.16326],[17.64155,50.12756],[17.64975,50.111],[17.67635,50.10321],[17.6888,50.12037],[17.73262,50.09668],[17.77107,50.0492],[17.77669,50.02253],[17.84196,49.98743],[17.85003,49.98682],[17.87252,49.97374],[18.00191,50.01723],[18.04667,50.00768],[18.04585,50.03311],[18.00212,50.04865],[18.03212,50.06574],[18.07898,50.04535],[18.10924,49.99813],[18.20241,49.99958],[18.21752,49.97309],[18.27107,49.96779],[18.27953,49.93967],[18.28979,49.92906],[18.31953,49.91503],[18.33261,49.92456],[18.33562,49.94747],[18.41604,49.93498],[18.49599,49.9053],[18.53423,49.89906],[18.54495,49.9079],[18.54299,49.92537],[18.57697,49.91565],[18.57045,49.87849],[18.60568,49.86023],[18.5847,49.85229],[18.58998,49.84611],[18.56869,49.83399],[18.61401,49.76197],[18.61368,49.75426],[18.62645,49.75002],[18.62943,49.74603],[18.62667,49.72203],[18.66675,49.70849],[18.70958,49.70422],[18.71907,49.68351],[18.80479,49.6815],[18.81906,49.6176],[18.85854,49.54559],[18.84521,49.51672],[18.94536,49.52143],[18.97283,49.49914],[18.9742,49.39557],[19.18019,49.41165],[19.25435,49.53391],[19.36009,49.53747],[19.37795,49.574],[19.45348,49.61583],[19.52626,49.57311],[19.53313,49.52856],[19.57845,49.46077],[19.64162,49.45184],[19.6375,49.40897],[19.72127,49.39288],[19.78581,49.41701],[19.82237,49.27806],[19.75286,49.20751],[19.86409,49.19316],[19.90529,49.23532],[19.98494,49.22904],[20.08238,49.1813],[20.13738,49.31685],[20.21977,49.35265],[20.31453,49.34817],[20.31728,49.39914],[20.39939,49.3896],[20.46422,49.41612],[20.5631,49.375],[20.61666,49.41791],[20.72274,49.41813],[20.77971,49.35383],[20.9229,49.29626],[20.98733,49.30774],[21.09799,49.37176],[21.041,49.41791],[21.12477,49.43666],[21.19756,49.4054],[21.27858,49.45988],[21.43376,49.41433],[21.62328,49.4447],[21.77983,49.35443],[21.82927,49.39467],[21.96385,49.3437],[22.04427,49.22136],[22.56155,49.08865],[22.89122,49.00725],[22.86336,49.10513],[22.72009,49.20288],[22.748,49.32759],[22.69444,49.49378],[22.64534,49.53094],[22.78304,49.65543],[22.80261,49.69098],[22.83179,49.69875],[22.99329,49.84249],[23.28221,50.0957],[23.57974,50.26706],[23.68858,50.33417],[23.71382,50.38248],[23.79445,50.40481],[23.99563,50.41289],[24.03668,50.44507],[24.07048,50.5071],[24.0996,50.60752],[24.0595,50.71625],[23.95925,50.79271],[23.99254,50.83847],[24.0952,50.83262],[24.14524,50.86128],[24.04576,50.90196],[23.92217,51.00836],[23.90376,51.07697],[23.80678,51.18405],[23.63858,51.32182],[23.69905,51.40871],[23.62751,51.50512],[23.56236,51.53673],[23.57053,51.55938],[23.53198,51.74298],[23.62691,51.78208],[23.61523,51.92066],[23.68733,51.9906],[23.64066,52.07626],[23.61,52.11264],[23.54314,52.12148],[23.47859,52.18215],[23.20071,52.22848],[23.18196,52.28812],[23.34141,52.44845],[23.45112,52.53774],[23.58296,52.59868],[23.73615,52.6149],[23.93763,52.71332],[23.91805,52.94016],[23.94689,52.95919],[23.92184,53.02079],[23.87548,53.0831],[23.91393,53.16469],[23.85657,53.22923],[23.81995,53.24131],[23.62004,53.60942],[23.51284,53.95052],[23.48456,53.98955],[23.53065,54.04558],[23.49196,54.14764],[23.45223,54.17775],[23.42418,54.17911],[23.39525,54.21672],[23.34122,54.25163],[23.24656,54.25701],[23.19694,54.28847],[23.15566,54.29759],[23.16012,54.31021],[23.13905,54.31567],[23.09575,54.29829],[23.04323,54.31567],[23.05695,54.347],[22.99649,54.35927],[23.00584,54.38514],[22.88821,54.40124],[22.88683,54.40983],[22.83697,54.40644],[22.79705,54.36264],[21.41123,54.32395],[20.63871,54.3706],[19.8038,54.44203],[19.64312,54.45423],[18.57853,55.25302],[14.20811,54.12784],[14.22634,53.9291],[14.20647,53.91671],[14.18544,53.91258],[14.20823,53.90776],[14.21323,53.8664],[14.27249,53.74464],[14.26782,53.69866],[14.2836,53.67721],[14.27133,53.66613],[14.28477,53.65955],[14.2853,53.63392],[14.31904,53.61581],[14.30416,53.55499],[14.3273,53.50587],[14.35209,53.49506],[14.4215,53.27724],[14.44133,53.27427],[14.45125,53.26241],[14.40301,53.20717],[14.37853,53.20405],[14.36696,53.16444],[14.38679,53.13669],[14.35044,53.05829],[14.25954,53.00264],[14.14056,52.95786],[14.15873,52.87715],[14.12256,52.84311]]]}},{"type":"Feature","properties":{"id":"KP"},"geometry":{"type":"Polygon","coordinates":[[[123.90497,38.79949],[124.36766,38.39606],[125.10527,37.56869],[125.81159,37.72949],[126.18398,37.72094],[126.1974,37.82546],[126.43239,37.84095],[126.46818,37.80873],[126.56709,37.76857],[126.59918,37.76364],[126.66067,37.7897],[126.68793,37.83728],[126.68793,37.9175],[126.67023,37.95852],[126.84961,38.0344],[126.88106,38.10246],[126.95887,38.1347],[126.95338,38.17735],[127.04479,38.25518],[127.15749,38.30722],[127.38727,38.33227],[127.49672,38.30647],[127.55013,38.32257],[128.02917,38.31861],[128.27652,38.41657],[128.31105,38.58462],[128.37487,38.62345],[128.65655,38.61914],[131.95041,41.5445],[130.65022,42.32281],[130.66367,42.38024],[130.64181,42.41422],[130.60805,42.4317],[130.56835,42.43281],[130.55143,42.52158],[130.51345,42.57836],[130.51105,42.61981],[130.47328,42.61665],[130.47397,42.55295],[130.4302,42.55156],[130.43226,42.60831],[130.38007,42.60149],[130.35467,42.63408],[130.24051,42.72658],[130.2309,42.79174],[130.26849,42.9025],[130.09735,42.91243],[130.14369,42.98468],[129.96409,42.97306],[129.95082,43.01051],[129.8865,43.00395],[129.84603,42.95277],[129.87204,42.91633],[129.84372,42.92054],[129.83711,42.87269],[129.81342,42.8474],[129.80719,42.79218],[129.7835,42.76521],[129.76037,42.72179],[129.77183,42.69435],[129.75294,42.59409],[129.73669,42.56231],[129.74226,42.47526],[129.71694,42.43137],[129.59901,42.45449],[129.54794,42.37052],[129.42821,42.44626],[129.37963,42.4422],[129.35646,42.4574],[129.29912,42.41255],[129.2665,42.38016],[129.21243,42.37236],[129.25449,42.32162],[129.19801,42.31387],[129.21792,42.26524],[129.17707,42.25749],[129.2023,42.23843],[129.19492,42.20296],[129.15887,42.1807],[129.16368,42.16403],[129.12729,42.15984],[129.12823,42.14825],[129.04815,42.13425],[129.01914,42.09185],[128.95683,42.08013],[128.94601,42.02098],[128.73229,42.03756],[128.04487,42.01769],[128.15119,41.74568],[128.30716,41.60322],[128.22126,41.44472],[128.20658,41.42702],[128.20272,41.40874],[128.18546,41.41279],[128.16478,41.40224],[128.14925,41.38138],[128.1235,41.38022],[128.10882,41.36205],[128.05904,41.39438],[128.0326,41.39149],[128.03827,41.41698],[127.93055,41.44491],[127.85614,41.40771],[127.83914,41.42065],[127.74653,41.42515],[127.69992,41.41859],[127.65452,41.42052],[127.64044,41.40745],[127.61049,41.43037],[127.58156,41.42573],[127.52998,41.46787],[127.47367,41.46897],[127.44775,41.4588],[127.40604,41.4581],[127.39445,41.47855],[127.35643,41.47675],[127.3536,41.45546],[127.28776,41.48395],[127.24888,41.48022],[127.28588,41.50407],[127.26931,41.51307],[127.23635,41.49527],[127.23318,41.52098],[127.20623,41.5177],[127.20605,41.53209],[127.16872,41.52245],[127.10057,41.54462],[127.18305,41.58848],[127.12537,41.5976],[127.04092,41.74685],[127.0119,41.73904],[126.93122,41.77406],[126.93792,41.80548],[126.91337,41.80171],[126.86797,41.78193],[126.83166,41.71437],[126.80308,41.76401],[126.79304,41.69156],[126.70394,41.75274],[126.74497,41.67342],[126.61485,41.67304],[126.53189,41.35206],[126.47186,41.34356],[126.51615,41.37333],[126.43341,41.35026],[126.43203,41.33042],[126.36594,41.28438],[126.35307,41.24322],[126.31736,41.2285],[126.30552,41.18989],[126.28389,41.18795],[126.2923,41.16948],[126.2717,41.15371],[126.23445,41.14608],[126.22364,41.12746],[126.15016,41.08698],[126.11824,41.08219],[126.12768,41.0532],[126.00889,40.91286],[125.9131,40.88574],[125.90984,40.91195],[125.80839,40.86614],[125.77423,40.89262],[125.70608,40.86562],[125.46592,40.70562],[125.0045,40.52371],[125.0354,40.46079],[124.88502,40.4668],[124.741,40.37048],[124.71919,40.32115],[124.59423,40.27271],[124.46994,40.17546],[124.40719,40.13655],[124.3322,40.05573],[124.37346,40.0278],[124.34703,39.94804],[124.23201,39.9248],[124.16679,39.77477],[123.90497,38.79949]]]}},{"type":"Feature","properties":{"id":"PY"},"geometry":{"type":"Polygon","coordinates":[[[-62.64455,-22.25091],[-62.53962,-22.36912],[-62.50937,-22.38119],[-62.2257,-22.55346],[-62.19909,-22.69955],[-61.9749,-23.02665],[-61.1111,-23.60111],[-60.99815,-23.79225],[-60.28163,-24.04436],[-60.03719,-24.01071],[-59.48444,-24.33802],[-59.34333,-24.48839],[-58.86234,-24.73155],[-58.81668,-24.76522],[-58.49936,-24.857],[-58.32624,-24.9999],[-58.25492,-24.92528],[-57.80821,-25.13863],[-57.72929,-25.24644],[-57.72304,-25.26689],[-57.72264,-25.27762],[-57.72012,-25.27747],[-57.71208,-25.27302],[-57.71064,-25.28123],[-57.71322,-25.28461],[-57.70867,-25.286],[-57.70285,-25.28288],[-57.70547,-25.29146],[-57.70397,-25.29144],[-57.69953,-25.28346],[-57.69459,-25.28653],[-57.69867,-25.29452],[-57.70564,-25.29567],[-57.70667,-25.30038],[-57.69972,-25.30418],[-57.6989,-25.31175],[-57.70186,-25.32005],[-57.66307,-25.35445],[-57.5517,-25.45303],[-57.60904,-25.60701],[-57.70105,-25.65514],[-57.87176,-25.93604],[-58.1188,-26.16704],[-58.15938,-26.18424],[-58.10377,-26.21967],[-58.14067,-26.25585],[-58.21243,-26.48962],[-58.25088,-26.7548],[-58.65321,-27.14028],[-58.60038,-27.30177],[-58.07373,-27.23509],[-57.66002,-27.36018],[-57.32013,-27.41566],[-56.74129,-27.60506],[-56.68018,-27.56428],[-56.73614,-27.46289],[-56.05018,-27.30528],[-55.89195,-27.3467],[-55.79269,-27.44704],[-55.72986,-27.44278],[-55.58927,-27.32144],[-55.60043,-27.27934],[-55.57262,-27.24501],[-55.63236,-27.17509],[-55.5661,-27.16249],[-55.56301,-27.10085],[-55.45932,-27.1104],[-55.44542,-27.02105],[-55.37864,-26.96614],[-55.31375,-26.96751],[-55.25243,-26.93808],[-55.20183,-26.97011],[-55.12836,-26.94563],[-55.15342,-26.88226],[-55.04991,-26.79549],[-55.00584,-26.78754],[-54.95747,-26.78906],[-54.9337,-26.67829],[-54.8022,-26.67031],[-54.79941,-26.52551],[-54.70732,-26.45099],[-54.64084,-26.1018],[-54.68431,-25.99054],[-54.60681,-25.97411],[-54.62234,-25.92888],[-54.589,-25.8277],[-54.65904,-25.6802],[-54.59398,-25.59224],[-54.59509,-25.53696],[-54.60539,-25.48062],[-54.62368,-25.46388],[-54.4423,-25.13381],[-54.32876,-24.48464],[-54.25941,-24.35366],[-54.33734,-24.13704],[-54.28482,-24.07123],[-54.32807,-24.01865],[-54.6238,-23.83078],[-55.02725,-23.97288],[-55.04609,-23.98401],[-55.0512,-23.98064],[-55.0521,-23.98578],[-55.12292,-23.99669],[-55.40491,-23.97778],[-55.44061,-23.91699],[-55.43585,-23.87157],[-55.45143,-23.71401],[-55.55438,-23.2827],[-55.54726,-23.27521],[-55.53236,-23.2691],[-55.52288,-23.2595],[-55.52992,-23.25246],[-55.54575,-23.22044],[-55.51769,-23.20632],[-55.61056,-23.04909],[-55.63849,-22.95122],[-55.66909,-22.86921],[-55.61433,-22.70778],[-55.62532,-22.62779],[-55.6315,-22.62026],[-55.69437,-22.57771],[-55.69471,-22.57094],[-55.6986,-22.56268],[-55.71377,-22.55734],[-55.72407,-22.5508],[-55.73771,-22.52657],[-55.74175,-22.51396],[-55.74728,-22.50538],[-55.74677,-22.48575],[-55.75149,-22.48198],[-55.75273,-22.47477],[-55.7466,-22.46909],[-55.74767,-22.46469],[-55.73493,-22.45918],[-55.74797,-22.38373],[-55.79011,-22.38484],[-55.84899,-22.28433],[-56.22665,-22.26495],[-56.45893,-22.08072],[-56.5212,-22.11556],[-56.63761,-22.26463],[-56.80807,-22.27813],[-57.98625,-22.09157],[-57.94584,-21.74243],[-57.88239,-21.6868],[-57.93492,-21.65505],[-57.84536,-20.93155],[-58.04454,-20.39853],[-58.16225,-20.16193],[-58.21106,-19.79319],[-59.06965,-19.29148],[-60.00638,-19.2981],[-61.73723,-19.63958],[-61.93912,-20.10053],[-62.26883,-20.55311],[-62.2757,-21.06657],[-62.64455,-22.25091]]]}},{"type":"Feature","properties":{"id":"QA"},"geometry":{"type":"Polygon","coordinates":[[[50.57069,25.57887],[50.8133,24.74049],[50.92992,24.54396],[51.09638,24.46907],[51.29972,24.50747],[51.39468,24.62785],[51.58834,24.66608],[51.83108,24.71675],[51.83682,26.70231],[50.93865,26.30758],[50.81266,25.88946],[50.86149,25.6965],[50.7801,25.595],[50.80824,25.54641],[50.57069,25.57887]]]}},{"type":"Feature","properties":{"id":"RO"},"geometry":{"type":"Polygon","coordinates":[[[20.26068,46.12332],[20.35862,45.99356],[20.66262,45.83757],[20.70069,45.7493],[20.77416,45.75601],[20.78446,45.78522],[20.82364,45.77738],[20.80361,45.65875],[20.76798,45.60969],[20.83321,45.53567],[20.77217,45.49788],[20.86026,45.47295],[20.87948,45.42743],[21.09894,45.30144],[21.17612,45.32566],[21.20443,45.26262],[21.48278,45.19557],[21.50835,45.13407],[21.4505,45.04294],[21.35855,45.01941],[21.54972,44.93014],[21.5607,44.89027],[21.48579,44.8704],[21.44013,44.87613],[21.35643,44.86364],[21.36933,44.82696],[21.38802,44.78133],[21.55007,44.77304],[21.60019,44.75208],[21.61942,44.67059],[21.67504,44.67107],[21.71692,44.65349],[21.7795,44.66165],[21.99364,44.63395],[22.08324,44.48962],[22.17697,44.47125],[22.30844,44.6619],[22.45301,44.7194],[22.61917,44.61489],[22.69196,44.61587],[22.76749,44.54446],[22.70981,44.51852],[22.61368,44.55719],[22.56493,44.53419],[22.54021,44.47836],[22.45436,44.47258],[22.56012,44.30712],[22.68166,44.28206],[22.67698,44.21863],[23.05412,44.07278],[23.01674,44.01946],[22.88383,43.98565],[22.83753,43.88055],[22.86357,43.83873],[23.05288,43.79494],[23.26772,43.84843],[23.4507,43.84936],[23.61687,43.79289],[23.73978,43.80627],[24.18149,43.68218],[24.35364,43.70211],[24.50264,43.76314],[24.62281,43.74082],[24.73542,43.68523],[24.81399,43.71082],[25.01346,43.71255],[25.10718,43.6831],[25.17144,43.70261],[25.38665,43.61917],[25.77289,43.70362],[25.94911,43.85745],[26.05584,43.90925],[26.10115,43.96908],[26.361,44.03824],[26.62712,44.05698],[26.95141,44.13555],[27.13502,44.1407],[27.27802,44.12912],[27.30514,44.08832],[27.41981,44.01627],[27.60834,44.01206],[27.64542,44.04958],[27.73468,43.95326],[27.92008,44.00761],[27.99558,43.84193],[28.23293,43.76],[29.24336,43.70874],[30.04414,45.08461],[29.69272,45.19227],[29.65428,45.25629],[29.68175,45.26885],[29.59798,45.38857],[29.42632,45.44545],[29.24779,45.43388],[28.96077,45.33164],[28.94292,45.28045],[28.81383,45.3384],[28.78911,45.24179],[28.71358,45.22631],[28.5735,45.24759],[28.34554,45.32102],[28.28504,45.43907],[28.21139,45.46895],[28.18741,45.47358],[28.08927,45.6051],[28.16568,45.6421],[28.13111,45.92819],[28.08612,46.01105],[28.13684,46.18099],[28.10937,46.22852],[28.19864,46.31869],[28.18902,46.35283],[28.25769,46.43334],[28.22281,46.50481],[28.24825,46.64177],[28.16087,46.78783],[28.11573,46.82978],[28.08259,46.99032],[27.92175,47.06801],[27.81755,47.14209],[27.78605,47.19111],[27.78768,47.20335],[27.75893,47.23868],[27.72708,47.29972],[27.69344,47.28749],[27.60709,47.32404],[27.57293,47.3851],[27.56864,47.4643],[27.47804,47.48542],[27.42055,47.58407],[27.32202,47.64009],[27.25519,47.71366],[27.28554,47.73479],[27.1618,47.92391],[27.14695,47.98147],[27.12352,48.013],[27.02985,48.09083],[27.04873,48.12338],[26.96119,48.13003],[26.98042,48.15752],[26.94265,48.1969],[26.88423,48.20179],[26.83101,48.2281],[26.81161,48.25049],[26.62823,48.25804],[26.55202,48.22445],[26.33504,48.18418],[26.26968,48.08151],[26.14059,47.98716],[25.9164,47.97647],[25.77723,47.93919],[25.63878,47.94924],[25.24812,47.89378],[25.12968,47.75329],[24.88896,47.7234],[24.81893,47.82031],[24.70632,47.84428],[24.61994,47.95062],[24.43578,47.97131],[24.34926,47.9244],[24.22566,47.90231],[24.15408,47.91683],[24.12447,47.90644],[24.06466,47.95317],[24.03496,47.95109],[24.00801,47.968],[23.98553,47.96076],[23.96337,47.96672],[23.94192,47.94868],[23.89352,47.94512],[23.8602,47.9329],[23.80904,47.98142],[23.75188,47.99705],[23.66399,47.98464],[23.63894,48.00293],[23.5653,48.00499],[23.52803,48.01818],[23.4979,47.96858],[23.33577,48.0237],[23.27397,48.08245],[23.14986,48.11099],[23.1133,48.08061],[23.08858,48.00716],[23.0158,47.99338],[22.92241,48.02002],[22.94301,47.96672],[22.89849,47.95851],[22.77991,47.87211],[22.76617,47.8417],[22.70874,47.82537],[22.67543,47.78501],[22.43803,47.77382],[22.41979,47.7391],[22.31816,47.76126],[22.00527,47.48867],[22.03389,47.42508],[22.01055,47.37767],[21.94463,47.38046],[21.78395,47.11104],[21.648,47.03902],[21.68645,46.99595],[21.59581,46.91628],[21.59307,46.86935],[21.52028,46.84118],[21.48935,46.7577],[21.5277,46.73327],[21.43926,46.65109],[21.33214,46.63035],[21.26929,46.4993],[21.28061,46.44941],[21.16872,46.30118],[21.06572,46.24897],[20.86797,46.28884],[20.74574,46.25467],[20.76089,46.21737],[20.71798,46.16746],[20.63863,46.12728],[20.5022,46.18993],[20.45377,46.14405],[20.35573,46.16629],[20.28324,46.1438],[20.26068,46.12332]]]}},{"type":"Feature","properties":{"id":"RU-KGD"},"geometry":{"type":"Polygon","coordinates":[[[18.57853,55.25302],[19.64312,54.45423],[19.8038,54.44203],[20.63871,54.3706],[21.41123,54.32395],[22.79705,54.36264],[22.7253,54.41732],[22.70208,54.45312],[22.67788,54.532],[22.71293,54.56454],[22.68021,54.58486],[22.7522,54.63525],[22.74225,54.64339],[22.75467,54.6483],[22.73397,54.66604],[22.73631,54.72952],[22.87317,54.79492],[22.85083,54.88711],[22.76422,54.92521],[22.68723,54.9811],[22.65451,54.97037],[22.60075,55.01863],[22.58907,55.07085],[22.47688,55.04408],[22.31562,55.0655],[22.14267,55.05345],[22.11697,55.02131],[22.06087,55.02935],[22.02582,55.05078],[22.03984,55.07888],[21.99543,55.08691],[21.96505,55.07353],[21.91291,55.08215],[21.87807,55.09413],[21.85521,55.09493],[21.64954,55.1791],[21.57414,55.1991],[21.51095,55.18507],[21.46766,55.21115],[21.38892,55.29241],[21.35465,55.28427],[21.26425,55.24456],[20.95181,55.27994],[20.60454,55.40986],[18.57853,55.25302]]]}},{"type":"Feature","properties":{"id":"RW"},"geometry":{"type":"Polygon","coordinates":[[[28.86193,-2.53185],[28.86972,-2.53867],[28.87752,-2.55077],[28.89228,-2.55896],[28.89762,-2.57984],[28.90226,-2.62385],[28.9058,-2.6474],[28.89537,-2.65366],[28.89793,-2.66111],[28.94346,-2.69124],[29.00454,-2.70519],[29.04064,-2.74317],[29.0542,-2.7078],[29.05694,-2.61058],[29.08973,-2.5918],[29.13282,-2.60612],[29.15024,-2.59609],[29.2141,-2.6303],[29.32234,-2.6483],[29.36645,-2.82611],[29.45434,-2.79971],[29.54429,-2.82851],[29.57313,-2.80931],[29.65544,-2.78762],[29.72351,-2.81668],[29.77655,-2.76096],[29.8901,-2.7511],[29.95911,-2.33348],[30.14034,-2.43626],[30.42933,-2.31064],[30.54501,-2.41404],[30.77339,-2.38789],[30.83915,-2.35795],[30.89303,-2.08223],[30.80802,-1.91477],[30.84079,-1.64652],[30.71974,-1.43244],[30.57123,-1.33264],[30.50889,-1.16412],[30.45116,-1.10641],[30.47238,-1.05711],[30.44019,-1.0481],[30.42783,-1.06389],[30.35462,-1.06509],[30.34234,-1.1298],[30.3008,-1.1552],[30.21223,-1.27894],[30.17034,-1.27585],[30.16862,-1.34338],[30.12047,-1.38371],[30.08176,-1.37487],[30.04966,-1.43013],[30.0179,-1.41451],[29.98203,-1.4569],[29.91448,-1.48281],[29.89585,-1.45179],[29.87749,-1.35634],[29.86375,-1.35882],[29.82436,-1.30837],[29.79672,-1.37311],[29.77724,-1.36693],[29.75891,-1.34557],[29.7381,-1.33986],[29.59061,-1.39016],[29.53062,-1.40499],[29.45038,-1.5054],[29.36322,-1.50887],[29.35315,-1.53609],[29.2692,-1.62841],[29.25719,-1.65801],[29.24323,-1.66826],[29.24458,-1.69663],[29.11847,-1.90576],[29.17562,-2.12278],[29.105,-2.27043],[29.00051,-2.29001],[28.95642,-2.37321],[28.89601,-2.37321],[28.86846,-2.44866],[28.89277,-2.47462],[28.89342,-2.49017],[28.88846,-2.50493],[28.87497,-2.50887],[28.86209,-2.5231],[28.86193,-2.53185]]]}},{"type":"Feature","properties":{"id":"EH"},"geometry":{"type":"Polygon","coordinates":[[[-17.35589,20.80492],[-17.0471,20.76408],[-17.0695,20.85742],[-17.06781,20.92697],[-17.0396,20.9961],[-17.0357,21.05368],[-16.99806,21.12142],[-16.95474,21.33997],[-13.01525,21.33343],[-13.08438,22.53866],[-13.15313,22.75649],[-13.10753,22.89493],[-13.00412,23.02297],[-12.5741,23.28975],[-12.36213,23.3187],[-12.14969,23.41935],[-12.00251,23.4538],[-12.0002,25.9986],[-8.66721,25.99918],[-8.66674,27.31569],[-8.66879,27.6666],[-8.77527,27.66663],[-8.71787,26.9898],[-9.08698,26.98639],[-9.56957,26.90042],[-9.81998,26.71379],[-10.68417,26.90984],[-11.35695,26.8505],[-11.23622,26.72023],[-11.38635,26.611],[-11.62052,26.05229],[-12.06001,26.04442],[-12.12281,25.13682],[-12.92147,24.39502],[-13.00628,24.01923],[-13.75627,23.77231],[-14.10361,22.75501],[-14.1291,22.41636],[-14.48112,22.00886],[-14.47329,21.63839],[-14.78487,21.36587],[-16.44269,21.39745],[-16.9978,21.36239],[-17.02707,21.34022],[-17.21511,21.34226],[-17.35589,20.80492]]]}},{"type":"Feature","properties":{"id":"SA"},"geometry":{"type":"Polygon","coordinates":[[[34.46254,27.99552],[34.51305,27.70027],[37.8565,22.00903],[39.63762,18.37348],[41.37609,16.19728],[42.15205,16.40211],[42.76801,16.40371],[42.94625,16.39721],[42.94351,16.49467],[42.97215,16.51093],[43.11601,16.53166],[43.14039,16.60033],[43.13575,16.67475],[43.23815,16.63997],[43.21085,16.70377],[43.22545,16.74315],[43.26647,16.7429],[43.253,16.76871],[43.26149,16.80084],[43.24801,16.80613],[43.23257,16.80692],[43.22012,16.83932],[43.18338,16.84852],[43.15215,16.87966],[43.13936,16.90188],[43.13567,16.92093],[43.19523,16.94458],[43.1813,16.98438],[43.18013,17.02995],[43.25351,17.0169],[43.17515,17.13012],[43.20156,17.25901],[43.33514,17.30516],[43.22768,17.38569],[43.28029,17.51719],[43.43496,17.56482],[43.68198,17.3621],[44.56878,17.45219],[46.36504,17.23656],[46.75231,17.29033],[47.00571,16.94765],[47.18215,16.94909],[47.46642,17.11716],[47.59551,17.45416],[48.18534,18.17129],[49.12261,18.62021],[50.774,18.78866],[52.00311,19.00083],[54.99756,20.00083],[55.66469,21.99658],[55.21095,22.70827],[55.13599,22.63334],[52.56622,22.94341],[51.59617,24.12041],[51.58871,24.27256],[51.41644,24.39615],[51.58834,24.66608],[51.39468,24.62785],[51.29972,24.50747],[51.09638,24.46907],[50.92992,24.54396],[50.8133,24.74049],[50.57069,25.57887],[50.302,25.87592],[50.26923,26.08243],[50.38162,26.53976],[50.71771,26.73086],[50.37726,27.89227],[49.98877,27.87827],[49.00421,28.81495],[48.42991,28.53628],[47.70561,28.5221],[47.59863,28.66798],[47.58376,28.83382],[47.46202,29.0014],[46.5527,29.10283],[46.42415,29.05947],[44.72255,29.19736],[42.97796,30.48295],[42.05521,31.13963],[41.4418,31.37357],[40.42419,31.94517],[39.19715,32.15468],[38.99233,31.99721],[36.99791,31.50081],[37.99354,30.49998],[37.66395,30.33245],[37.4971,29.99949],[36.75083,29.86903],[36.50005,29.49696],[36.07081,29.18469],[34.95987,29.35727],[34.88293,29.37455],[34.46254,27.99552]]]}},{"type":"Feature","properties":{"id":"SD"},"geometry":{"type":"Polygon","coordinates":[[[21.81432,12.81362],[21.89371,12.68001],[21.98711,12.63292],[22.17693,12.68087],[22.21332,12.74014],[22.46345,12.61925],[22.38873,12.45514],[22.49656,12.15849],[22.48369,12.02766],[22.64092,12.07485],[22.54907,11.64372],[22.7997,11.40424],[22.93124,11.41645],[22.97249,11.21955],[22.87758,10.91915],[23.02221,10.69235],[23.3128,10.45214],[23.67164,9.86923],[23.69628,9.67147],[23.62179,9.53823],[23.64981,9.44303],[23.64358,9.28637],[23.56263,9.19418],[23.4848,9.16959],[23.44744,8.99128],[23.59065,8.99743],[23.51905,8.71749],[24.25691,8.69288],[24.56268,8.89185],[24.55924,9.2397],[24.74601,9.72124],[24.84996,9.81319],[24.97739,9.9081],[25.05688,10.06776],[25.0823,10.31491],[25.78141,10.42599],[25.93163,10.38159],[25.93241,10.17941],[26.21338,9.91545],[26.33869,9.5801],[26.70685,9.48735],[27.14427,9.62858],[27.90704,9.61323],[28.03504,9.34338],[28.76769,9.35151],[28.76838,9.44296],[28.99983,9.67155],[29.06988,9.74826],[29.53844,9.75133],[29.54,10.07949],[29.94629,10.29245],[30.00389,10.28633],[30.53005,9.95992],[30.82893,9.71451],[30.84605,9.7498],[31.28504,9.75287],[31.77539,10.28939],[31.99177,10.65065],[32.46967,11.04662],[32.39358,11.18207],[32.39578,11.70208],[32.09664,11.95125],[32.73921,11.95203],[32.74131,12.23735],[33.26351,12.22057],[33.13988,11.43248],[33.26977,10.83632],[33.24645,10.77913],[33.52294,10.64382],[33.66604,10.44254],[33.80913,10.32994],[33.90159,10.17179],[33.96984,10.15446],[33.99185,9.99623],[33.96323,9.80972],[33.9082,9.762],[33.87958,9.49937],[34.10229,9.50238],[34.08717,9.55243],[34.13186,9.7492],[34.20484,9.9033],[34.22718,10.02506],[34.32352,10.1181],[34.34783,10.23914],[34.29399,10.52426],[34.27974,10.53726],[34.4372,10.781],[34.59062,10.89072],[34.77383,10.74588],[34.77756,10.6822],[34.86618,10.74588],[34.86916,10.78832],[34.97491,10.86147],[34.97789,10.91559],[34.93172,10.95946],[35.01215,11.19626],[34.95557,11.2471],[35.09556,11.56278],[35.05832,11.71158],[35.11492,11.85156],[35.24302,11.91132],[35.70144,12.66629],[36.01458,12.72478],[36.14268,12.70879],[36.16651,12.88019],[36.13374,12.92665],[36.15514,12.96658],[36.24545,13.36759],[36.38993,13.56459],[36.48824,13.83954],[36.44653,13.95666],[36.55837,14.25789],[36.54087,14.27785],[36.43478,15.17022],[36.54276,15.23478],[36.69761,15.75323],[36.76371,15.80831],[36.92193,16.23451],[36.92161,16.63224],[36.99777,17.07172],[37.42694,17.04041],[37.50967,17.32199],[38.13362,17.53906],[38.33988,17.64271],[38.38674,17.73724],[38.45455,17.90132],[38.58398,18.02216],[39.63762,18.37348],[37.8565,22.00903],[34.0765,22.00501],[33.99686,21.76784],[33.57251,21.72406],[33.17563,22.00405],[24.99885,21.99535],[24.99794,19.99661],[23.99715,20.00038],[23.99539,19.49944],[23.99997,15.69575],[23.62785,15.7804],[23.38812,15.69649],[23.10792,15.71297],[22.92795,15.54747],[22.92579,15.47007],[22.99584,15.40105],[22.99584,15.22989],[22.9343,15.1003],[22.81654,15.032],[22.66115,14.86308],[22.71526,14.68988],[22.41485,14.59654],[22.44944,14.24986],[22.55997,14.23024],[22.55424,14.13158],[22.23289,13.95372],[22.08674,13.77863],[22.12783,13.65099],[22.22156,13.54988],[22.21984,13.46175],[22.29434,13.36056],[22.1599,13.19281],[22.02914,13.13976],[21.94819,13.05637],[21.81432,12.81362]]]}},{"type":"Feature","properties":{"id":"SS"},"geometry":{"type":"Polygon","coordinates":[[[23.44744,8.99128],[23.59065,8.99743],[23.51905,8.71749],[24.25691,8.69288],[24.14451,8.34195],[24.35965,8.26177],[24.85156,8.16933],[24.98855,7.96588],[25.25319,7.8487],[25.29214,7.66675],[25.20649,7.61115],[25.20337,7.50312],[25.35281,7.42595],[25.37461,7.33024],[25.90076,7.09549],[26.40735,6.64244],[26.32729,6.36272],[26.55807,6.23953],[26.46434,6.11222],[27.22705,5.71254],[27.22705,5.62889],[27.28621,5.56382],[27.24386,5.35147],[27.29158,5.25045],[27.46616,5.08666],[27.44848,5.01587],[27.56656,4.89375],[27.65462,4.89375],[27.76469,4.79284],[27.79551,4.59976],[28.20719,4.35614],[28.6651,4.42638],[28.8126,4.48784],[29.03054,4.48784],[29.22207,4.34297],[29.43341,4.50101],[29.49726,4.7007],[29.82087,4.56246],[29.79666,4.37809],[30.06964,4.13221],[30.1621,4.10586],[30.22374,3.93896],[30.2869,3.96427],[30.47691,3.83353],[30.55396,3.84451],[30.57378,3.74567],[30.56277,3.62703],[30.74712,3.64362],[30.77609,3.68333],[30.80463,3.5939],[30.85997,3.5743],[30.84849,3.48729],[30.8654,3.49448],[30.90685,3.59463],[30.97601,3.693],[31.16786,3.79266],[31.28837,3.7966],[31.50672,3.6748],[31.51205,3.63352],[31.57539,3.68183],[31.70757,3.72602],[31.81459,3.82083],[31.86821,3.78664],[31.96205,3.6499],[31.95907,3.57408],[32.05187,3.589],[32.08491,3.56287],[32.08866,3.53543],[32.19888,3.50867],[32.20782,3.6053],[32.41337,3.748],[32.72021,3.77327],[32.89746,3.81339],[33.02852,3.89296],[33.18356,3.77812],[33.51264,3.75068],[33.9873,4.23316],[34.47601,4.72162],[35.34151,5.02364],[35.30992,4.90402],[35.47843,4.91872],[35.42366,4.76969],[35.51424,4.61643],[35.9419,4.61933],[35.82118,4.77382],[35.81968,5.10757],[35.8576,5.33413],[35.50792,5.42431],[35.29938,5.34042],[35.31188,5.50106],[35.13058,5.62118],[35.12611,5.68937],[35.00546,5.89387],[34.96227,6.26415],[35.01738,6.46991],[34.87736,6.60161],[34.77459,6.5957],[34.65096,6.72589],[34.53776,6.74808],[34.53925,6.82794],[34.47669,6.91076],[34.35753,6.91963],[34.19369,7.04382],[34.19369,7.12807],[34.01495,7.25664],[34.03878,7.27437],[34.02984,7.36449],[33.87642,7.5491],[33.71407,7.65983],[33.44745,7.7543],[33.32531,7.71297],[33.24637,7.77939],[33.04944,7.78989],[33.01717,7.8462],[33.0025,7.94032],[33.03794,7.96845],[33.07846,8.05175],[33.18083,8.13047],[33.1853,8.29264],[33.19776,8.40903],[33.3119,8.45474],[33.55954,8.46736],[33.66938,8.44442],[33.71407,8.3678],[33.87195,8.41938],[33.89579,8.4842],[34.0256,8.4997],[34.14453,8.60204],[34.14304,9.04654],[34.10229,9.50238],[33.87958,9.49937],[33.9082,9.762],[33.96323,9.80972],[33.99185,9.99623],[33.96984,10.15446],[33.90159,10.17179],[33.80913,10.32994],[33.66604,10.44254],[33.52294,10.64382],[33.24645,10.77913],[33.26977,10.83632],[33.13988,11.43248],[33.26351,12.22057],[32.74131,12.23735],[32.73921,11.95203],[32.09664,11.95125],[32.39578,11.70208],[32.39358,11.18207],[32.46967,11.04662],[31.99177,10.65065],[31.77539,10.28939],[31.28504,9.75287],[30.84605,9.7498],[30.82893,9.71451],[30.53005,9.95992],[30.00389,10.28633],[29.94629,10.29245],[29.54,10.07949],[29.53844,9.75133],[29.06988,9.74826],[28.99983,9.67155],[28.99841,10.16491],[27.83386,10.17032],[27.83248,9.76861],[27.90704,9.61323],[27.14427,9.62858],[26.70685,9.48735],[26.33869,9.5801],[26.21338,9.91545],[25.93241,10.17941],[25.93163,10.38159],[25.78141,10.42599],[25.0823,10.31491],[25.05688,10.06776],[24.97739,9.9081],[24.84996,9.81319],[24.72138,9.83483],[24.66344,9.79584],[24.51307,9.818],[24.22923,9.77444],[24.19258,9.73096],[24.12151,9.73291],[24.10357,9.6643],[23.69628,9.67147],[23.62179,9.53823],[23.64981,9.44303],[23.64358,9.28637],[23.56263,9.19418],[23.4848,9.16959],[23.44744,8.99128]]]}},{"type":"Feature","properties":{"id":"SN"},"geometry":{"type":"Polygon","coordinates":[[[-17.73012,14.75133],[-17.43598,13.59273],[-15.47902,13.58758],[-15.36504,13.79313],[-14.93719,13.80173],[-14.34721,13.46578],[-13.8955,13.59126],[-13.79409,13.34472],[-14.36795,13.23033],[-15.14917,13.57989],[-15.26908,13.37768],[-15.80478,13.34832],[-15.80355,13.16729],[-16.69343,13.16791],[-16.74676,13.06025],[-17.43966,13.04579],[-17.4623,11.92379],[-16.69097,12.35727],[-16.38191,12.36449],[-16.20591,12.46157],[-16.04278,12.4716],[-15.89035,12.45032],[-15.6804,12.42635],[-15.42892,12.5368],[-15.33674,12.6142],[-15.17582,12.6847],[-13.7075,12.67618],[-13.48915,12.67551],[-13.04901,12.63296],[-13.06603,12.49342],[-12.96077,12.47864],[-12.90018,12.54953],[-12.56544,12.36867],[-12.40734,12.38695],[-12.35601,12.31752],[-12.01818,12.40606],[-11.96926,12.40573],[-11.89888,12.44797],[-11.77425,12.38561],[-11.46267,12.44559],[-11.36793,12.40153],[-11.37908,12.50663],[-11.43436,12.55657],[-11.41307,12.64452],[-11.42389,12.72893],[-11.37943,12.73663],[-11.40295,12.92476],[-11.34475,12.93613],[-11.40913,12.9752],[-11.40861,13.02663],[-11.63383,13.39646],[-11.82884,13.30477],[-11.88583,13.39847],[-11.8654,13.45724],[-12.03706,13.62496],[-12.05783,13.72671],[-11.93407,13.92473],[-12.00187,13.98404],[-11.99192,14.20481],[-12.02796,14.29033],[-12.09457,14.30796],[-12.11912,14.36617],[-12.20117,14.40658],[-12.20701,14.46036],[-12.1986,14.47897],[-12.22186,14.49028],[-12.21473,14.55284],[-12.18375,14.55542],[-12.19688,14.60584],[-12.15036,14.62046],[-12.14332,14.65152],[-12.17645,14.66356],[-12.16873,14.6888],[-12.20237,14.69876],[-12.23936,14.76324],[-12.27979,14.77297],[-12.33421,14.82998],[-12.39961,14.84524],[-12.4542,14.89377],[-12.44845,14.91708],[-12.46398,14.98873],[-12.50089,15.01907],[-12.56475,15.04095],[-12.60715,15.08405],[-12.66294,15.10908],[-12.74053,15.12151],[-12.7934,15.15531],[-12.78808,15.19955],[-12.89073,15.24162],[-12.82327,15.28749],[-12.89005,15.33799],[-12.9412,15.35024],[-12.9491,15.41727],[-12.92799,15.44358],[-12.96712,15.49404],[-13.07664,15.48561],[-13.08609,15.58236],[-13.25294,15.65857],[-13.21105,15.69626],[-13.29277,15.77193],[-13.26375,15.78713],[-13.36761,15.97667],[-13.37516,16.05785],[-13.54562,16.14295],[-13.63849,16.10651],[-13.71917,16.13735],[-13.70218,16.17527],[-13.78698,16.14164],[-13.84483,16.10651],[-13.87882,16.17544],[-13.9468,16.24483],[-13.9607,16.32294],[-14.32144,16.61495],[-14.37921,16.63569],[-14.47551,16.625],[-14.65215,16.62073],[-14.64271,16.64688],[-14.76545,16.63619],[-14.95016,16.64622],[-14.95359,16.68355],[-14.99462,16.69194],[-14.99925,16.64046],[-15.05556,16.62862],[-15.08062,16.67895],[-15.12096,16.65],[-15.09727,16.58717],[-15.42274,16.53846],[-15.44523,16.59309],[-15.51527,16.56101],[-15.50205,16.52513],[-15.62891,16.52168],[-15.65362,16.47279],[-15.79387,16.49765],[-15.81808,16.50983],[-15.98785,16.49798],[-16.12775,16.54702],[-16.27016,16.51565],[-16.37821,16.23709],[-16.4455,16.19786],[-16.44814,16.09753],[-16.48344,16.05802],[-16.50833,16.06725],[-16.75187,16.07666],[-17.73012,14.75133]]]}},{"type":"Feature","properties":{"id":"SG"},"geometry":{"type":"Polygon","coordinates":[[[103.55749,1.19701],[103.75628,1.1424],[104.03527,1.2689],[104.09815,1.41191],[104.03341,1.45684],[104.00131,1.42405],[103.89565,1.42841],[103.81575,1.48124],[103.75227,1.44239],[103.7219,1.46108],[103.67107,1.4296],[103.55749,1.19701]]]}},{"type":"Feature","properties":{"id":"SL"},"geometry":{"type":"Polygon","coordinates":[[[-14.36218,8.64107],[-12.15048,6.15992],[-11.50429,6.92704],[-11.45805,6.92702],[-11.41144,6.99032],[-11.39797,6.98819],[-11.37102,7.02278],[-11.37539,7.07661],[-11.33342,7.0784],[-11.35231,7.1399],[-11.32312,7.17064],[-11.30501,7.21364],[-11.27218,7.23931],[-11.25476,7.2285],[-11.14374,7.32194],[-11.14511,7.33965],[-11.10305,7.39115],[-11.07842,7.39293],[-10.94152,7.50834],[-10.92023,7.49668],[-10.79475,7.59019],[-10.70214,7.73302],[-10.60422,7.7739],[-10.60492,8.04072],[-10.57523,8.04829],[-10.51554,8.1393],[-10.45023,8.15627],[-10.37572,8.16552],[-10.35315,8.15049],[-10.29766,8.21072],[-10.31635,8.28554],[-10.30084,8.30008],[-10.2729,8.4435],[-10.27822,8.48816],[-10.32225,8.5059],[-10.35684,8.48519],[-10.38208,8.49062],[-10.4001,8.44817],[-10.49915,8.34178],[-10.55399,8.3067],[-10.58876,8.33651],[-10.65768,8.35086],[-10.68686,8.28674],[-10.703,8.28029],[-10.6745,8.37761],[-10.63897,8.39247],[-10.64043,8.47373],[-10.61957,8.49215],[-10.62094,8.52805],[-10.57451,8.57478],[-10.57232,8.59778],[-10.49228,8.62599],[-10.46958,8.65281],[-10.46748,8.67941],[-10.51082,8.74147],[-10.52018,8.78346],[-10.55983,8.80823],[-10.56339,8.85818],[-10.5761,8.89851],[-10.57571,8.92687],[-10.59107,8.98927],[-10.57326,9.05098],[-10.62,9.07361],[-10.66961,9.08506],[-10.73355,9.07785],[-10.71467,9.18903],[-10.66051,9.19963],[-10.66806,9.26295],[-10.65472,9.2951],[-10.70514,9.33872],[-10.73381,9.38352],[-10.80642,9.38462],[-10.80488,9.4157],[-10.84213,9.45769],[-10.84419,9.51738],[-10.88496,9.59431],[-11.07593,9.84405],[-11.14614,9.86849],[-11.1997,10.00046],[-11.89624,9.99763],[-11.91023,9.93927],[-12.12634,9.87203],[-12.24262,9.92386],[-12.43824,9.8799],[-12.47394,9.85014],[-12.69126,9.4157],[-12.76765,9.33999],[-12.91013,9.27003],[-12.94309,9.29171],[-12.95906,9.17768],[-13.00884,9.10921],[-13.07089,9.08234],[-13.08953,9.0409],[-13.18668,9.09421],[-13.23886,9.07022],[-13.27388,9.06361],[-13.30375,9.03564],[-14.36218,8.64107]]]}},{"type":"Feature","properties":{"id":"SV"},"geometry":{"type":"Polygon","coordinates":[[[-90.42092,13.19698],[-87.90019,12.84737],[-87.61441,13.1834],[-87.73714,13.32715],[-87.83645,13.39692],[-87.72162,13.44964],[-87.72033,13.50465],[-87.78316,13.52117],[-87.7684,13.59293],[-87.75896,13.60211],[-87.73106,13.75443],[-87.70128,13.8044],[-87.82453,13.92623],[-88.01164,13.87141],[-88.07327,13.99237],[-88.10588,14.0007],[-88.2372,13.99911],[-88.22673,13.95789],[-88.26458,13.9124],[-88.49092,13.86074],[-88.50053,13.96938],[-88.7091,14.04583],[-88.73708,14.13083],[-88.79708,14.16445],[-88.80772,14.11668],[-88.82815,14.10619],[-88.86162,14.17386],[-88.90282,14.20581],[-88.96024,14.2098],[-89.02204,14.30813],[-89.0344,14.33225],[-89.07045,14.33823],[-89.09877,14.41157],[-89.17284,14.3636],[-89.21567,14.38846],[-89.34776,14.43013],[-89.39558,14.45088],[-89.43368,14.41223],[-89.47093,14.42936],[-89.53342,14.38247],[-89.5396,14.41522],[-89.57925,14.41556],[-89.57187,14.3527],[-89.60397,14.32925],[-89.50614,14.26084],[-89.52397,14.22628],[-89.5511,14.22228],[-89.58097,14.20315],[-89.58174,14.20315],[-89.64191,14.20348],[-89.70756,14.1537],[-89.74679,14.06448],[-89.73358,14.03501],[-89.7746,14.03201],[-89.82181,14.06431],[-89.88937,14.0396],[-90.10402,13.84958],[-90.11344,13.73679],[-90.42092,13.19698]]]}},{"type":"Feature","properties":{"id":"SM"},"geometry":{"type":"Polygon","coordinates":[[[12.40415,43.95485],[12.40506,43.94325],[12.41165,43.93769],[12.41551,43.92984],[12.40733,43.92379],[12.41233,43.90956],[12.40935,43.9024],[12.41641,43.89991],[12.44184,43.90498],[12.45648,43.89369],[12.48771,43.89706],[12.49429,43.90973],[12.49247,43.91774],[12.49724,43.92248],[12.50269,43.92363],[12.50496,43.93017],[12.51553,43.94096],[12.51427,43.94897],[12.50655,43.95796],[12.50875,43.96198],[12.50622,43.97131],[12.51109,43.97201],[12.51064,43.98165],[12.5154,43.98508],[12.51463,43.99122],[12.50678,43.99113],[12.49406,43.98492],[12.47853,43.98052],[12.46205,43.97463],[12.44684,43.96597],[12.43662,43.95698],[12.42005,43.9578],[12.41414,43.95273],[12.40415,43.95485]]]}},{"type":"Feature","properties":{"id":"RS"},"geometry":{"type":"Polygon","coordinates":[[[18.81394,45.91329],[18.85783,45.85493],[18.90305,45.71863],[18.96691,45.66731],[18.88776,45.57253],[18.94562,45.53712],[19.07471,45.53086],[19.08364,45.48804],[18.99918,45.49333],[18.97446,45.37528],[19.10774,45.29547],[19.28208,45.23813],[19.41941,45.23475],[19.43589,45.17137],[19.19144,45.17863],[19.14063,45.12972],[19.07952,45.14668],[19.1011,45.01191],[19.05205,44.97692],[19.15573,44.95409],[19.06853,44.89915],[19.02871,44.92541],[18.98957,44.90645],[19.01994,44.85493],[19.19191,44.92202],[19.29903,44.9095],[19.37181,44.87922],[19.3458,44.78804],[19.33224,44.76727],[19.33464,44.73758],[19.29928,44.68903],[19.25886,44.6608],[19.24229,44.62224],[19.21959,44.59175],[19.18307,44.57426],[19.19517,44.55053],[19.16483,44.52209],[19.13277,44.52674],[19.12174,44.50091],[19.15045,44.45148],[19.14693,44.41364],[19.11616,44.40141],[19.10749,44.39421],[19.10704,44.38249],[19.10365,44.37795],[19.10372,44.36877],[19.11925,44.36684],[19.1083,44.3558],[19.11547,44.34218],[19.13423,44.34017],[19.1323,44.31604],[19.16741,44.28648],[19.18328,44.28383],[19.208,44.29243],[19.23229,44.26241],[19.25311,44.26397],[19.28649,44.27565],[19.32791,44.26745],[19.34825,44.23124],[19.34443,44.21816],[19.35889,44.20903],[19.35344,44.18955],[19.36323,44.18165],[19.40927,44.16722],[19.43905,44.13088],[19.47338,44.15034],[19.48386,44.14332],[19.46966,44.12129],[19.49292,44.11384],[19.50965,44.08129],[19.55368,44.07186],[19.57467,44.04716],[19.62467,44.05268],[19.61394,44.03522],[19.62342,44.01883],[19.61806,44.01374],[19.59832,44.01007],[19.58956,44.00334],[19.56364,43.99898],[19.5681,43.98558],[19.54193,43.97595],[19.52656,43.956],[19.47669,43.95606],[19.4488,43.96014],[19.42872,43.95816],[19.40314,43.96607],[19.38614,43.961],[19.24363,44.01502],[19.23465,43.98764],[19.33885,43.8625],[19.3986,43.79668],[19.46373,43.76254],[19.4815,43.73284],[19.53317,43.70399],[19.5094,43.62744],[19.49386,43.637],[19.49172,43.60034],[19.51755,43.57958],[19.41618,43.57777],[19.42829,43.55591],[19.41301,43.53946],[19.40082,43.58741],[19.38589,43.59232],[19.36778,43.6114],[19.33426,43.58833],[19.25611,43.59997],[19.24774,43.53061],[19.22807,43.5264],[19.22229,43.47926],[19.44683,43.38883],[19.46142,43.34559],[19.48171,43.32644],[19.52639,43.32005],[19.54407,43.24776],[19.62661,43.2286],[19.64063,43.19027],[19.7723,43.16127],[19.7838,43.13418],[19.84139,43.09665],[19.86688,43.11163],[19.92576,43.08539],[19.96549,43.11098],[19.98887,43.0538],[20.04729,43.02732],[20.05373,43.00941],[20.12325,42.96237],[20.14896,42.99058],[20.15785,42.97576],[20.34528,42.90676],[20.35692,42.8335],[20.40594,42.84853],[20.43734,42.83157],[20.53484,42.8885],[20.48392,42.93173],[20.50653,42.96282],[20.52954,42.97579],[20.54623,42.96094],[20.59859,43.02171],[20.64279,43.00477],[20.6615,43.07565],[20.69515,43.09641],[20.6579,43.15216],[20.59584,43.20398],[20.68688,43.21335],[20.72957,43.24651],[20.82145,43.26769],[20.88466,43.21956],[20.85016,43.20342],[20.83727,43.17842],[20.96287,43.12416],[21.00749,43.13984],[21.05378,43.10707],[21.08952,43.13471],[21.14465,43.11089],[21.16653,42.9983],[21.2041,43.02277],[21.23877,43.00848],[21.23534,42.95523],[21.27785,42.89539],[21.32974,42.90424],[21.36941,42.87397],[21.44047,42.87276],[21.38874,42.75602],[21.46711,42.73768],[21.47498,42.74695],[21.59154,42.72643],[21.58616,42.70363],[21.63353,42.69763],[21.65662,42.67208],[21.75025,42.70125],[21.79155,42.65296],[21.75672,42.62695],[21.73825,42.60168],[21.7458,42.5551],[21.70331,42.54568],[21.7035,42.51899],[21.62358,42.4531],[21.6428,42.41648],[21.62126,42.37699],[21.58118,42.38067],[21.57021,42.3647],[21.53467,42.36809],[21.52496,42.32967],[21.56772,42.30946],[21.58992,42.25915],[21.70111,42.23789],[21.77176,42.2648],[21.84654,42.3247],[21.91595,42.30392],[21.94405,42.34669],[22.02908,42.29848],[22.17504,42.3225],[22.29605,42.37477],[22.29275,42.34913],[22.34773,42.31725],[22.45919,42.33822],[22.47498,42.3915],[22.51961,42.3991],[22.55669,42.50144],[22.44223,42.57773],[22.49515,42.74629],[22.43111,42.81912],[22.5424,42.87772],[22.61981,42.89445],[22.67312,42.87608],[22.76538,42.90375],[22.78916,42.98317],[22.84804,43.00458],[22.98104,43.11199],[23.00806,43.19279],[22.89727,43.22417],[22.82036,43.33665],[22.53397,43.47225],[22.47582,43.6558],[22.41043,43.69566],[22.36095,43.80331],[22.41828,44.00664],[22.53313,44.02417],[22.62016,44.09177],[22.61711,44.16938],[22.67698,44.21863],[22.68166,44.28206],[22.56012,44.30712],[22.45436,44.47258],[22.54021,44.47836],[22.56493,44.53419],[22.61368,44.55719],[22.70981,44.51852],[22.76749,44.54446],[22.69196,44.61587],[22.61917,44.61489],[22.45301,44.7194],[22.30844,44.6619],[22.17697,44.47125],[22.08324,44.48962],[21.99364,44.63395],[21.7795,44.66165],[21.71692,44.65349],[21.67504,44.67107],[21.61942,44.67059],[21.60019,44.75208],[21.55007,44.77304],[21.38802,44.78133],[21.36933,44.82696],[21.35643,44.86364],[21.44013,44.87613],[21.48579,44.8704],[21.5607,44.89027],[21.54972,44.93014],[21.35855,45.01941],[21.4505,45.04294],[21.50835,45.13407],[21.48278,45.19557],[21.20443,45.26262],[21.17612,45.32566],[21.09894,45.30144],[20.87948,45.42743],[20.86026,45.47295],[20.77217,45.49788],[20.83321,45.53567],[20.76798,45.60969],[20.80361,45.65875],[20.82364,45.77738],[20.78446,45.78522],[20.77416,45.75601],[20.70069,45.7493],[20.66262,45.83757],[20.35862,45.99356],[20.26068,46.12332],[20.09713,46.17315],[20.03533,46.14509],[20.01816,46.17696],[19.93508,46.17553],[19.81491,46.1313],[19.66007,46.19005],[19.56113,46.16824],[19.52473,46.1171],[19.28826,45.99694],[19.14543,45.9998],[19.10388,46.04015],[19.0791,45.96458],[19.01284,45.96529],[18.99712,45.93537],[18.81394,45.91329]]]}},{"type":"Feature","properties":{"id":"SR"},"geometry":{"type":"Polygon","coordinates":[[[-58.0307,3.95513],[-57.35891,3.32121],[-56.70519,2.02964],[-56.55439,2.02003],[-56.47045,1.95135],[-55.99278,1.83137],[-55.89863,1.89861],[-55.92159,2.05236],[-56.13054,2.27723],[-55.96292,2.53188],[-55.71493,2.40342],[-55.01919,2.564],[-54.6084,2.32856],[-54.42864,2.42442],[-54.28534,2.67798],[-54.1286,3.28688],[-54.06852,3.30299],[-54.06681,3.32347],[-54.0529,3.364],[-54.059,3.38422],[-54.01617,3.4178],[-54.00904,3.46724],[-53.97892,3.60482],[-54.01033,3.65193],[-54.02441,3.64559],[-54.02981,3.63078],[-54.05128,3.63557],[-54.08286,3.6742],[-54.09101,3.72919],[-54.13444,3.80139],[-54.20302,3.80858],[-54.19367,3.84387],[-54.35709,4.05006],[-54.32584,4.14937],[-54.39022,4.18207],[-54.39056,4.28273],[-54.44794,4.52564],[-54.42051,4.56581],[-54.41554,4.61483],[-54.43511,4.63494],[-54.42,4.71911],[-54.46394,4.72938],[-54.46223,4.78027],[-54.46918,4.88795],[-54.47879,4.90454],[-54.44051,4.94713],[-54.35743,5.1477],[-54.29117,5.24771],[-54.14457,5.36582],[-54.00724,5.55072],[-54.01074,5.68785],[-53.83024,6.10624],[-56.96411,6.23066],[-57.33078,5.32634],[-57.19036,5.18326],[-57.37442,5.0208],[-57.8699,4.89394],[-58.0307,3.95513]]]}},{"type":"Feature","properties":{"id":"SK"},"geometry":{"type":"Polygon","coordinates":[[[16.83317,48.38138],[16.84243,48.35258],[16.90903,48.32519],[16.89461,48.31332],[16.97701,48.17385],[17.02919,48.13996],[17.05735,48.14179],[17.09168,48.09366],[17.07039,48.0317],[17.16001,48.00636],[17.23699,48.02094],[17.71215,47.7548],[17.83355,47.74209],[18.02938,47.75665],[18.29305,47.73541],[18.45668,47.76286],[18.49033,47.75225],[18.55672,47.76749],[18.61942,47.75663],[18.71567,47.77579],[18.74074,47.8157],[18.8506,47.82308],[18.76821,47.87469],[18.76614,47.98199],[18.817,47.9998],[18.82176,48.04206],[18.90575,48.05754],[18.98385,48.05766],[19.01952,48.07052],[19.23924,48.0595],[19.29864,48.08932],[19.40082,48.08174],[19.43601,48.09505],[19.47545,48.08691],[19.52025,48.18314],[19.54278,48.20923],[19.54965,48.20705],[19.63338,48.25006],[19.69745,48.20591],[19.76852,48.20602],[19.82688,48.16803],[19.86448,48.17593],[19.91889,48.12645],[19.97348,48.16539],[20.09382,48.20053],[20.24419,48.28056],[20.29943,48.26104],[20.40332,48.36126],[20.5215,48.53336],[20.82475,48.58092],[20.87608,48.55297],[21.1322,48.4959],[21.44063,48.58456],[21.51638,48.54718],[21.54144,48.51216],[21.61113,48.50545],[21.66435,48.41017],[21.66883,48.38944],[21.73602,48.34832],[21.83335,48.33422],[21.83412,48.35887],[22.00561,48.38418],[22.08869,48.38339],[22.14689,48.4005],[22.16023,48.56548],[22.21379,48.6218],[22.34567,48.69877],[22.38532,48.86448],[22.42721,48.90986],[22.48296,48.99172],[22.54338,49.01424],[22.56155,49.08865],[22.04427,49.22136],[21.96385,49.3437],[21.82927,49.39467],[21.77983,49.35443],[21.62328,49.4447],[21.43376,49.41433],[21.27858,49.45988],[21.19756,49.4054],[21.12477,49.43666],[21.041,49.41791],[21.09799,49.37176],[20.98733,49.30774],[20.9229,49.29626],[20.77971,49.35383],[20.72274,49.41813],[20.61666,49.41791],[20.5631,49.375],[20.46422,49.41612],[20.39939,49.3896],[20.31728,49.39914],[20.31453,49.34817],[20.21977,49.35265],[20.13738,49.31685],[20.08238,49.1813],[19.98494,49.22904],[19.90529,49.23532],[19.86409,49.19316],[19.75286,49.20751],[19.82237,49.27806],[19.78581,49.41701],[19.72127,49.39288],[19.6375,49.40897],[19.64162,49.45184],[19.57845,49.46077],[19.53313,49.52856],[19.52626,49.57311],[19.45348,49.61583],[19.37795,49.574],[19.36009,49.53747],[19.25435,49.53391],[19.18019,49.41165],[18.9742,49.39557],[18.97283,49.49914],[18.94536,49.52143],[18.84521,49.51672],[18.7443,49.48903],[18.71967,49.4999],[18.67791,49.50787],[18.6144,49.49824],[18.57183,49.51162],[18.53063,49.49022],[18.54848,49.47059],[18.45145,49.39349],[18.40969,49.39911],[18.4139,49.36517],[18.37077,49.32534],[18.18456,49.28909],[18.15022,49.24518],[18.1104,49.08624],[18.09298,49.05828],[18.07834,49.0431],[18.05572,49.03111],[17.91814,49.01784],[17.91329,48.99407],[17.88316,48.92681],[17.78158,48.92529],[17.73126,48.87885],[17.7094,48.86721],[17.5295,48.81117],[17.51991,48.81264],[17.5166,48.82518],[17.50952,48.81973],[17.45671,48.85004],[17.3853,48.80936],[17.29857,48.84929],[17.19355,48.87602],[17.15433,48.84605],[17.10957,48.8313],[17.00215,48.70887],[16.93955,48.60371],[16.94611,48.53614],[16.85204,48.44968],[16.8497,48.38321],[16.83588,48.3844],[16.83317,48.38138]]]}},{"type":"Feature","properties":{"id":"SI"},"geometry":{"type":"Polygon","coordinates":[[[13.37671,46.29668],[13.4149,46.20846],[13.47587,46.22725],[13.56114,46.2054],[13.56682,46.18703],[13.64451,46.18966],[13.66815,46.1776],[13.64053,46.13587],[13.57669,46.09406],[13.52102,46.0633],[13.49867,46.0595],[13.49568,46.04839],[13.50998,46.04498],[13.50186,46.02031],[13.47581,46.00703],[13.50104,45.98078],[13.51429,45.97808],[13.52963,45.96588],[13.56759,45.96991],[13.58903,45.99009],[13.62074,45.98388],[13.63458,45.98947],[13.64375,45.98296],[13.6329,45.94894],[13.63815,45.93607],[13.61931,45.91782],[13.60857,45.89907],[13.59565,45.89446],[13.58644,45.88173],[13.57563,45.8425],[13.58858,45.83503],[13.59784,45.8072],[13.66986,45.79955],[13.78968,45.74144],[13.83332,45.70855],[13.83422,45.68703],[13.87933,45.65207],[13.9191,45.6322],[13.8695,45.60835],[13.86771,45.59898],[13.84625,45.58227],[13.78445,45.5825],[13.74587,45.59811],[13.7198,45.59352],[13.6076,45.64761],[13.45644,45.59464],[13.56979,45.4895],[13.6251,45.46241],[13.67398,45.4436],[13.79196,45.47168],[13.81742,45.43729],[13.88124,45.42637],[13.90771,45.45149],[13.98353,45.45411],[13.99488,45.47551],[13.96063,45.50825],[14.00578,45.52352],[14.07116,45.48752],[14.20348,45.46896],[14.22371,45.50388],[14.24239,45.50607],[14.26611,45.48239],[14.27681,45.4902],[14.32487,45.47142],[14.36693,45.48642],[14.49603,45.54044],[14.50341,45.60689],[14.54718,45.62226],[14.57397,45.67165],[14.60977,45.66403],[14.59576,45.62812],[14.6476,45.5974],[14.65207,45.59181],[14.68048,45.58875],[14.69884,45.564],[14.6876,45.54483],[14.68605,45.53006],[14.6964,45.52312],[14.70481,45.53497],[14.72996,45.52874],[14.79918,45.49299],[14.81935,45.4591],[14.87136,45.46699],[14.91179,45.4821],[14.92266,45.52788],[15.02385,45.48533],[15.05496,45.49232],[15.0856,45.47981],[15.08379,45.46903],[15.17615,45.4208],[15.22413,45.42598],[15.23915,45.45103],[15.2746,45.46651],[15.33571,45.45115],[15.38077,45.48962],[15.30052,45.53401],[15.29837,45.5841],[15.27657,45.60515],[15.30661,45.61319],[15.30266,45.6336],[15.34695,45.63382],[15.34214,45.64702],[15.39098,45.63816],[15.4057,45.64727],[15.37103,45.69116],[15.35176,45.67158],[15.35189,45.68408],[15.3648,45.69754],[15.34356,45.71319],[15.31419,45.68729],[15.30972,45.69811],[15.29676,45.69119],[15.30168,45.68735],[15.29944,45.68593],[15.25423,45.72275],[15.40836,45.79491],[15.47531,45.79802],[15.47325,45.8253],[15.52234,45.82195],[15.57492,45.85182],[15.64787,45.82784],[15.66532,45.84138],[15.69392,45.84336],[15.71006,45.84874],[15.67967,45.86939],[15.69109,45.88534],[15.68383,45.88867],[15.67967,45.90455],[15.70636,45.92116],[15.70448,45.99922],[15.71246,46.01196],[15.72977,46.04682],[15.66058,46.07162],[15.62238,46.094],[15.6083,46.11992],[15.59646,46.14719],[15.61384,46.15319],[15.60492,46.16333],[15.64904,46.19229],[15.6434,46.21396],[15.67397,46.22539],[15.75436,46.21969],[15.75254,46.20751],[15.76688,46.20858],[15.78817,46.21719],[15.80563,46.25911],[15.97965,46.30652],[16.07814,46.34526],[16.07314,46.36458],[16.04965,46.3824],[16.05763,46.39353],[16.14859,46.40547],[16.18824,46.38282],[16.30233,46.37837],[16.30162,46.40437],[16.27329,46.41467],[16.27398,46.42875],[16.25161,46.48143],[16.24135,46.48474],[16.24169,46.49851],[16.26813,46.50474],[16.26551,46.51581],[16.2874,46.51522],[16.30276,46.51156],[16.31988,46.52745],[16.37193,46.55008],[16.38771,46.53608],[16.5007,46.49644],[16.52604,46.47831],[16.59527,46.47524],[16.52604,46.5051],[16.53138,46.53241],[16.50139,46.56684],[16.39217,46.63673],[16.38594,46.6549],[16.41863,46.66238],[16.42641,46.69228],[16.36516,46.70408],[16.32199,46.77666],[16.30966,46.7787],[16.31303,46.79838],[16.3408,46.80641],[16.34547,46.83836],[16.2941,46.87137],[16.2365,46.87775],[16.21892,46.86961],[16.18972,46.86591],[16.15565,46.85394],[16.13947,46.85514],[16.11282,46.86858],[16.05786,46.83927],[15.99054,46.82772],[15.99126,46.78199],[15.98432,46.74991],[15.99769,46.7266],[16.02808,46.71094],[16.04347,46.68694],[16.04036,46.6549],[15.99988,46.67947],[15.98512,46.68463],[15.94864,46.68769],[15.87691,46.7211],[15.8162,46.71897],[15.78518,46.70712],[15.76771,46.69863],[15.73823,46.70011],[15.72279,46.69548],[15.69523,46.69823],[15.67411,46.70735],[15.6543,46.70616],[15.6543,46.69228],[15.6365,46.6894],[15.63255,46.68069],[15.62317,46.67947],[15.59826,46.68908],[15.54533,46.66985],[15.55333,46.64988],[15.54431,46.6312],[15.46906,46.61321],[15.45514,46.63697],[15.41235,46.65556],[15.23711,46.63994],[15.14215,46.66131],[15.01451,46.641],[14.98024,46.6009],[14.9595,46.63293],[14.92283,46.60848],[14.87129,46.61],[14.86419,46.59411],[14.83549,46.56614],[14.81836,46.51046],[14.72185,46.49974],[14.66892,46.44936],[14.5942,46.43434],[14.56463,46.37208],[14.52176,46.42617],[14.45877,46.41717],[14.42608,46.44614],[14.314,46.43327],[14.28326,46.44315],[14.15989,46.43327],[14.12097,46.47724],[14.04002,46.49117],[14.00422,46.48474],[13.89837,46.52331],[13.7148,46.5222],[13.68684,46.43881],[13.59777,46.44137],[13.5763,46.42613],[13.5763,46.40915],[13.47019,46.3621],[13.43418,46.35992],[13.45344,46.32636],[13.37671,46.29668]]]}},{"type":"Feature","properties":{"id":"SE"},"geometry":{"type":"Polygon","coordinates":[[[10.40861,58.38489],[12.16597,56.60205],[12.07466,56.29488],[12.65312,56.04345],[12.6372,55.91371],[12.88472,55.63369],[12.60345,55.42675],[12.84405,55.13257],[14.28399,55.1553],[14.89259,55.5623],[15.79951,55.54655],[19.64795,57.06466],[19.84909,57.57876],[20.5104,59.15546],[19.08191,60.19152],[19.23413,60.61414],[20.15877,63.06556],[24.14112,65.39731],[24.15107,65.81427],[24.14503,65.84046],[24.12597,65.85092],[24.13249,65.86342],[24.15292,65.86293],[23.90497,66.15802],[23.71339,66.21299],[23.64982,66.30603],[23.67591,66.3862],[23.63776,66.43568],[23.85959,66.56434],[23.89488,66.772],[23.98059,66.79585],[23.98563,66.84149],[23.56214,67.17038],[23.58215,67.2654],[23.75372,67.29914],[23.72291,67.43912],[23.39577,67.46974],[23.54701,67.59306],[23.45627,67.85297],[23.65793,67.9497],[23.40081,68.05545],[23.26469,68.15134],[23.15377,68.14759],[23.10336,68.26551],[22.73028,68.40881],[22.00429,68.50692],[21.03001,68.88969],[20.90649,68.89696],[20.85104,68.93142],[20.91658,68.96764],[20.78802,69.03087],[20.55258,69.06069],[20.0695,69.04469],[20.28444,68.93283],[20.33435,68.80174],[20.22027,68.67246],[19.95647,68.55546],[20.22027,68.48759],[19.93508,68.35911],[18.97255,68.52416],[18.63032,68.50849],[18.39503,68.58672],[18.1241,68.53721],[18.13836,68.20874],[17.90787,67.96537],[17.30416,68.11591],[16.7409,67.91037],[16.38441,67.52923],[16.12774,67.52106],[16.09922,67.4364],[16.39154,67.21653],[16.35589,67.06419],[15.37197,66.48217],[15.49318,66.28509],[15.05113,66.15572],[14.53778,66.12399],[14.50926,65.31786],[13.64276,64.58402],[14.11117,64.46674],[14.16051,64.18725],[13.98222,64.00953],[13.21208,64.09605],[12.92816,64.05958],[12.68405,63.9752],[12.29919,63.67246],[12.14928,63.59373],[12.21267,63.47673],[11.98529,63.27487],[12.19919,63.00104],[12.07085,62.6297],[12.29187,62.25699],[12.14746,61.7147],[12.40595,61.57226],[12.57707,61.56547],[12.86939,61.35427],[12.69115,61.06584],[12.2277,61.02442],[12.59133,60.50559],[12.52003,60.13846],[12.45111,60.04067],[12.39446,60.01589],[12.34228,59.96583],[12.23121,59.92758],[12.16787,59.88807],[11.87121,59.86039],[11.92112,59.69531],[11.69297,59.59442],[11.75228,59.48658],[11.8213,59.24985],[11.65732,58.90177],[11.45199,58.89604],[11.4601,58.99022],[11.34459,59.11672],[11.15367,59.07862],[11.08911,58.98745],[10.64958,58.89391],[10.40861,58.38489]]]}},{"type":"Feature","properties":{"id":"SZ"},"geometry":{"type":"Polygon","coordinates":[[[30.7854,-26.66924],[30.81101,-26.84722],[30.88826,-26.79622],[30.90771,-26.85968],[30.97516,-26.91481],[30.95899,-27.00048],[31.15014,-27.20204],[31.49344,-27.31518],[31.97639,-27.31747],[31.97463,-27.11057],[32.00893,-26.8096],[32.09664,-26.80721],[32.13315,-26.84345],[32.13409,-26.5317],[32.07763,-26.4053],[32.07424,-26.30603],[32.10435,-26.15656],[32.08633,-26.01178],[32.01072,-25.99353],[32.00272,-25.99614],[31.97537,-25.95271],[31.86881,-25.99973],[31.4175,-25.71886],[31.31237,-25.7431],[31.13073,-25.91558],[30.95819,-26.26303],[30.80815,-26.45504],[30.7854,-26.66924]]]}},{"type":"Feature","properties":{"id":"SY"},"geometry":{"type":"Polygon","coordinates":[[[35.48515,34.70851],[35.97653,34.63394],[35.98412,34.6511],[36.03412,34.62861],[36.06871,34.63345],[36.07386,34.62826],[36.07931,34.63412],[36.0903,34.62932],[36.11407,34.63387],[36.12497,34.64228],[36.17239,34.62854],[36.19548,34.63779],[36.22406,34.62692],[36.24286,34.63426],[36.29165,34.62991],[36.30569,34.64733],[36.30277,34.66787],[36.32399,34.69334],[36.35513,34.6834],[36.34938,34.66258],[36.37083,34.64083],[36.39083,34.63066],[36.40143,34.63712],[36.42452,34.62392],[36.46285,34.64087],[36.45152,34.58213],[36.40731,34.61399],[36.40328,34.55463],[36.33822,34.52232],[36.34745,34.5002],[36.44499,34.50372],[36.46499,34.46276],[36.48611,34.45773],[36.57279,34.40499],[36.52919,34.37007],[36.56091,34.32036],[36.60778,34.31009],[36.58121,34.27558],[36.59195,34.2316],[36.62537,34.20251],[36.5128,34.09916],[36.50576,34.05982],[36.41078,34.05253],[36.27651,33.9128],[36.38263,33.86579],[36.39804,33.83078],[36.15488,33.84974],[36.06897,33.82728],[35.9341,33.6596],[36.06425,33.57508],[35.94185,33.52801],[35.94816,33.47886],[35.88668,33.43183],[35.82577,33.40479],[35.81324,33.36354],[35.77477,33.33609],[35.813,33.3172],[35.77513,33.27342],[35.81295,33.24841],[35.81647,33.2028],[35.83846,33.19397],[35.84285,33.16673],[35.81911,33.1336],[35.81911,33.11077],[35.84802,33.1031],[35.87188,32.98028],[35.89298,32.9456],[35.87012,32.91976],[35.84632,32.87382],[35.83758,32.82817],[35.75983,32.74803],[35.88409,32.71371],[35.93307,32.71966],[35.96633,32.66237],[36.02239,32.65911],[36.08074,32.51463],[36.20379,32.52751],[36.20875,32.49529],[36.24046,32.49991],[36.40354,32.37735],[36.71424,32.31557],[36.83946,32.31293],[38.79171,33.37328],[40.64314,34.31604],[40.97676,34.39788],[41.12388,34.65742],[41.2345,34.80049],[41.21654,35.1508],[41.26569,35.42708],[41.38184,35.62502],[41.37027,35.84095],[41.2564,36.06012],[41.28864,36.35368],[41.40058,36.52502],[41.81736,36.58782],[41.97566,36.70737],[42.00416,36.74411],[42.36697,37.0627],[42.35212,37.10858],[42.32313,37.17814],[42.34305,37.23217],[42.28274,37.28266],[42.26039,37.27017],[42.23683,37.2863],[42.20848,37.27396],[42.2112,37.32491],[42.18225,37.28569],[42.08982,37.2064],[41.96622,37.16537],[41.51166,37.07942],[41.24559,37.07914],[41.21847,37.06216],[41.19804,37.0638],[41.19838,37.08024],[40.90856,37.13147],[40.75515,37.12268],[40.51895,37.02722],[40.4132,37.01351],[40.2769,36.92546],[40.0619,36.85627],[40.05091,36.84116],[39.81589,36.75538],[39.21538,36.66834],[39.06789,36.70365],[38.72972,36.70806],[38.54742,36.84734],[38.38786,36.90296],[38.34938,36.89945],[38.22246,36.92148],[38.03526,36.86221],[38.02213,36.8233],[37.99329,36.83188],[37.81442,36.75992],[37.67005,36.74631],[37.49547,36.67571],[37.47144,36.63109],[37.32519,36.66125],[37.21988,36.6736],[37.16348,36.65657],[37.11336,36.67957],[37.08589,36.63453],[37.01929,36.67681],[37.01647,36.69512],[37.04619,36.71101],[37.04399,36.73483],[36.99886,36.74012],[36.99594,36.75236],[36.73175,36.81753],[36.67991,36.83443],[36.61581,36.74629],[36.63159,36.71281],[36.57398,36.65186],[36.59099,36.57866],[36.54206,36.49539],[36.60387,36.39268],[36.60305,36.33355],[36.65653,36.33861],[36.66588,36.33058],[36.65197,36.31391],[36.65412,36.29707],[36.67553,36.29499],[36.70017,36.23693],[36.61382,36.22121],[36.5037,36.24323],[36.46748,36.19725],[36.39367,36.22405],[36.37384,36.17266],[36.3801,36.01321],[36.33956,35.98687],[36.29796,36.00904],[36.28209,36.00154],[36.29942,35.95918],[36.27222,35.94556],[36.25745,35.96286],[36.19771,35.94896],[36.17368,35.92047],[36.17351,35.84439],[36.1623,35.80925],[36.14029,35.81015],[36.13772,35.83635],[36.11553,35.8605],[35.99829,35.88242],[36.01741,35.92041],[36.00438,35.94132],[35.931,35.92109],[35.51152,36.10954],[35.48515,34.70851]]]}},{"type":"Feature","properties":{"id":"TD"},"geometry":{"type":"Polygon","coordinates":[[[13.47559,14.40881],[13.6302,13.71094],[14.08251,13.0797],[14.46881,13.08259],[14.53521,12.9463],[14.55006,12.93697],[14.56443,12.91125],[14.5537,12.86544],[14.56538,12.83573],[14.54624,12.79832],[14.55559,12.77581],[14.57722,12.76517],[14.58683,12.74651],[14.62211,12.77296],[14.62391,12.74826],[14.67301,12.71963],[14.71584,12.73202],[14.71326,12.66161],[14.75326,12.68522],[14.7742,12.63372],[14.83025,12.63983],[14.85257,12.56151],[14.85342,12.45283],[14.87832,12.46364],[14.87754,12.43691],[14.90913,12.38577],[14.90801,12.33245],[14.89411,12.31132],[14.90552,12.27517],[14.89239,12.25161],[14.90321,12.22535],[14.89248,12.20212],[14.91145,12.18609],[14.88432,12.16478],[14.9608,12.09513],[14.98535,12.10571],[15.00146,12.1223],[15.01779,12.11855],[15.03599,12.10772],[15.03461,12.08288],[15.06122,12.05871],[15.05659,12.03655],[15.03822,12.03537],[15.05762,12.00624],[15.08345,12.00465],[15.08345,11.98097],[15.04423,11.97509],[15.06174,11.9347],[15.04808,11.8731],[15.12104,11.78939],[15.06311,11.713],[15.1086,11.58161],[15.14413,11.56042],[15.1225,11.5103],[15.0856,11.46185],[15.0585,11.40481],[15.10021,11.04101],[15.04957,11.02347],[15.09967,10.88017],[15.06311,10.82049],[15.14087,10.67191],[15.16113,10.6164],[15.14533,10.53169],[15.22911,10.49203],[15.2794,10.40087],[15.30601,10.31238],[15.52385,10.09934],[15.68761,9.99344],[15.42171,9.93097],[15.24618,9.99246],[15.14043,9.99246],[15.0753,9.94416],[14.95722,9.97926],[14.80201,9.93368],[14.46727,9.99995],[14.20789,10.0008],[14.13476,9.81969],[14.02198,9.72885],[13.97544,9.6365],[14.03795,9.60785],[14.10318,9.51983],[14.37698,9.26986],[14.35707,9.19611],[14.83566,8.80557],[15.10911,8.65892],[15.20426,8.50892],[15.39493,8.09526],[15.50743,7.79302],[15.59272,7.7696],[15.56964,7.58936],[15.49743,7.52179],[15.73118,7.52006],[15.79942,7.44149],[16.40703,7.68809],[16.41583,7.77971],[16.59072,7.88557],[16.59415,7.76444],[16.65801,7.74739],[16.65716,7.67288],[16.835,7.53336],[16.921,7.61878],[16.99619,7.63596],[17.66378,7.98511],[17.92436,7.95825],[18.02731,8.01085],[18.6085,8.05009],[18.64153,8.08714],[18.62612,8.14163],[18.67177,8.21845],[18.79898,8.25668],[18.8618,8.34535],[19.11044,8.68172],[18.86388,8.87971],[19.0702,9.00835],[19.76165,9.04039],[20.36748,9.11019],[20.82979,9.44696],[21.26348,9.97642],[21.34934,9.95907],[21.52766,10.2105],[21.63553,10.217],[21.71479,10.29932],[21.72139,10.64136],[22.45889,11.00246],[22.87758,10.91915],[22.97249,11.21955],[22.93124,11.41645],[22.7997,11.40424],[22.54907,11.64372],[22.64092,12.07485],[22.48369,12.02766],[22.49656,12.15849],[22.38873,12.45514],[22.46345,12.61925],[22.21332,12.74014],[22.17693,12.68087],[21.98711,12.63292],[21.89371,12.68001],[21.81432,12.81362],[21.94819,13.05637],[22.02914,13.13976],[22.1599,13.19281],[22.29434,13.36056],[22.21984,13.46175],[22.22156,13.54988],[22.12783,13.65099],[22.08674,13.77863],[22.23289,13.95372],[22.55424,14.13158],[22.55997,14.23024],[22.44944,14.24986],[22.41485,14.59654],[22.71526,14.68988],[22.66115,14.86308],[22.81654,15.032],[22.9343,15.1003],[22.99584,15.22989],[22.99584,15.40105],[22.92579,15.47007],[22.92795,15.54747],[23.10792,15.71297],[23.38812,15.69649],[23.62785,15.7804],[23.99997,15.69575],[23.99539,19.49944],[16.00389,23.44852],[14.99751,23.00539],[15.19692,21.99339],[15.20213,21.49365],[15.28332,21.44557],[15.62515,20.95395],[15.57248,20.92138],[15.55382,20.86507],[15.56004,20.79488],[15.59841,20.74039],[15.6721,20.70069],[15.99632,20.35364],[15.75098,19.93002],[15.6032,18.77402],[15.50373,16.89649],[14.38316,15.73013],[13.86301,15.04043],[13.78991,14.87519],[13.809,14.72915],[13.67878,14.64013],[13.69843,14.55043],[13.48259,14.46704],[13.47559,14.40881]]]}},{"type":"Feature","properties":{"id":"TG"},"geometry":{"type":"Polygon","coordinates":[[[-0.14462,11.10811],[-0.05733,11.08628],[-0.0275,11.11202],[-0.00514,11.10763],[0.00342,11.08317],[0.02395,11.06229],[0.03355,10.9807],[-0.0063,10.96417],[-0.00908,10.91644],[-0.02685,10.8783],[-0.0228,10.81916],[-0.07183,10.76794],[-0.07327,10.71845],[-0.09141,10.7147],[-0.05945,10.63458],[0.14299,10.52443],[0.15604,10.46434],[0.19191,10.40357],[0.29453,10.41546],[0.32581,10.30782],[0.39584,10.31112],[0.35671,10.21509],[0.35293,10.09412],[0.41576,10.06317],[0.41252,10.02018],[0.36366,10.03309],[0.35739,9.85318],[0.32075,9.72781],[0.34816,9.71607],[0.3805,9.58106],[0.23851,9.57389],[0.2409,9.52335],[0.30406,9.521],[0.31241,9.50337],[0.2254,9.47869],[0.25758,9.42696],[0.33148,9.44812],[0.36485,9.49749],[0.49118,9.48339],[0.56665,9.40554],[0.51069,9.24546],[0.53352,9.2092],[0.45576,9.04785],[0.53094,8.86963],[0.37319,8.75262],[0.47211,8.59945],[0.64731,8.48866],[0.73432,8.29529],[0.63897,8.25873],[0.5913,8.19622],[0.61156,8.18324],[0.6056,8.13959],[0.58891,8.12779],[0.62943,7.85751],[0.58811,7.7018],[0.58295,7.62368],[0.51979,7.58706],[0.52455,7.45354],[0.56322,7.38868],[0.62943,7.41099],[0.65327,7.31643],[0.60424,7.13666],[0.60493,7.01775],[0.52217,6.9723],[0.52098,6.94391],[0.56528,6.92506],[0.52853,6.82921],[0.57558,6.80712],[0.58004,6.76161],[0.6497,6.73682],[0.6348,6.62777],[0.73471,6.59128],[0.74862,6.56517],[0.71048,6.53083],[0.76732,6.4296],[0.85659,6.38695],[0.89212,6.3373],[1.00404,6.33423],[1.03108,6.24064],[1.05969,6.22998],[1.08644,6.16905],[1.19956,6.16922],[1.19974,6.11307],[1.27574,5.93551],[1.67336,6.02702],[1.62992,6.24226],[1.79826,6.28221],[1.76776,6.42823],[1.69549,6.54438],[1.60675,6.61601],[1.61241,6.64977],[1.58105,6.68619],[1.61812,6.74843],[1.55877,6.99737],[1.64249,6.99562],[1.61838,9.0527],[1.5649,9.16941],[1.41746,9.3226],[1.38977,9.48665],[1.33675,9.54765],[1.36624,9.5951],[1.35507,9.99525],[0.77666,10.37665],[0.80358,10.71459],[0.8804,10.803],[0.91245,10.99597],[0.66104,10.99964],[0.50159,10.93293],[0.51893,10.97641],[0.49987,10.99073],[0.50388,11.01011],[-0.13493,11.14075],[-0.14462,11.10811]]]}},{"type":"Feature","properties":{"id":"TH"},"geometry":{"type":"Polygon","coordinates":[[[97.19814,8.18901],[99.31854,5.99868],[99.50117,6.44501],[99.91873,6.50233],[100.0756,6.4045],[100.12,6.42105],[100.19511,6.72559],[100.29651,6.68439],[100.30828,6.66462],[100.31618,6.66781],[100.31884,6.66423],[100.32671,6.66526],[100.32607,6.65933],[100.31929,6.65413],[100.35413,6.54932],[100.41152,6.52299],[100.41791,6.5189],[100.42351,6.51762],[100.43027,6.52389],[100.66986,6.45086],[100.74361,6.50811],[100.74822,6.46231],[100.81045,6.45086],[100.85884,6.24929],[101.10313,6.25617],[101.12618,6.19431],[101.06165,6.14161],[101.12388,6.11411],[101.087,5.9193],[101.02708,5.91013],[100.98815,5.79464],[101.03593,5.74034],[101.09104,5.70396],[101.14062,5.61613],[101.25755,5.71065],[101.25524,5.78633],[101.58019,5.93534],[101.69773,5.75881],[101.75074,5.79091],[101.80144,5.74505],[101.88694,5.83845],[101.91776,5.84269],[101.9393,5.86561],[101.92411,5.91871],[101.94712,5.98421],[101.9714,6.00575],[101.97119,6.01961],[101.99209,6.04075],[102.01835,6.05407],[102.03376,6.07001],[102.05534,6.08149],[102.09148,6.14874],[102.08448,6.16257],[102.078,6.19469],[102.08127,6.22679],[102.09086,6.23546],[102.46318,7.22462],[102.47649,9.66162],[102.52395,11.25257],[102.91449,11.65512],[102.90973,11.75613],[102.83957,11.8519],[102.78427,11.98746],[102.77026,12.06815],[102.70176,12.1686],[102.73134,12.37091],[102.78116,12.40284],[102.7796,12.43781],[102.57567,12.65358],[102.51963,12.66117],[102.4994,12.71736],[102.53053,12.77506],[102.50068,12.91719],[102.49335,12.92711],[102.47943,12.97879],[102.53067,13.00179],[102.49814,13.01074],[102.46011,13.08057],[102.43422,13.09061],[102.36202,13.26475],[102.36001,13.31142],[102.34537,13.3487],[102.35987,13.39162],[102.35563,13.47307],[102.361,13.50551],[102.33412,13.5533],[102.36536,13.57749],[102.44601,13.5637],[102.51282,13.56848],[102.56235,13.57875],[102.58067,13.60657],[102.62483,13.60883],[102.62457,13.61041],[102.60101,13.62067],[102.57477,13.63003],[102.57095,13.63962],[102.5481,13.6589],[102.56848,13.69366],[102.73281,13.77273],[102.72388,13.79007],[102.76645,13.85608],[102.7831,13.93273],[102.86464,14.00353],[102.91251,14.01531],[102.89674,14.05632],[102.93275,14.19044],[103.16471,14.33424],[103.39353,14.35639],[103.53518,14.42575],[103.71109,14.4348],[103.70175,14.38052],[103.94405,14.32443],[104.27616,14.39861],[104.55014,14.36091],[104.69335,14.42726],[104.97667,14.38806],[105.02804,14.23722],[105.08408,14.20402],[105.14012,14.23873],[105.17748,14.34432],[105.19958,14.34173],[105.43783,14.43865],[105.53864,14.55731],[105.5121,14.80802],[105.61162,15.00037],[105.46661,15.13132],[105.58043,15.32724],[105.50662,15.32054],[105.4692,15.33709],[105.47635,15.3796],[105.58032,15.40552],[105.59852,15.45847],[105.59921,15.52282],[105.63783,15.63361],[105.61756,15.68792],[105.51458,15.77309],[105.46573,15.74742],[105.4339,15.75558],[105.38343,15.83982],[105.34446,15.92369],[105.38508,15.987],[105.42686,15.98987],[105.41484,16.01644],[105.22327,16.04779],[105.0468,16.11245],[105.01453,16.24664],[104.88057,16.37311],[104.85076,16.44695],[104.76013,16.50884],[104.73349,16.565],[104.76562,16.69605],[104.7397,16.81005],[104.76442,16.84752],[104.7373,16.91125],[104.73918,17.01476],[104.80716,17.19025],[104.80061,17.39367],[104.70725,17.52276],[104.45972,17.66152],[104.3514,17.82812],[104.2757,17.86139],[104.21776,17.99335],[104.10927,18.10826],[104.06533,18.21656],[103.97725,18.33631],[103.93916,18.33914],[103.85856,18.28282],[103.81273,18.3445],[103.699,18.34125],[103.62459,18.39582],[103.52588,18.42595],[103.45988,18.42619],[103.41044,18.4486],[103.30977,18.4341],[103.24693,18.38026],[103.23706,18.34132],[103.27946,18.33016],[103.29903,18.30458],[103.17054,18.25967],[103.15132,18.23367],[103.15166,18.17725],[103.07343,18.12351],[103.0775,18.0326],[103.0436,17.98371],[103.02137,17.96885],[102.9912,17.9949],[102.94867,18.00608],[102.90301,17.98297],[102.85743,17.97334],[102.81924,17.94092],[102.78885,17.93594],[102.75607,17.89225],[102.688,17.87224],[102.67543,17.84529],[102.69847,17.81766],[102.67607,17.80279],[102.63607,17.8318],[102.60045,17.83147],[102.5896,17.84889],[102.61453,17.9112],[102.61196,17.94729],[102.59234,17.96127],[102.55951,17.96771],[102.52793,17.96305],[102.51565,17.96901],[102.43266,17.98306],[102.3421,18.04729],[102.29627,18.05447],[102.18486,18.15058],[102.1628,18.20481],[102.08684,18.2203],[102.0459,18.19902],[102.03251,18.15792],[101.95527,18.09698],[101.89312,18.02983],[101.78154,18.07332],[101.72824,17.95154],[101.73339,17.92312],[101.66078,17.89985],[101.61529,17.89168],[101.59847,17.84724],[101.57546,17.86938],[101.55169,17.82175],[101.55255,17.80818],[101.57718,17.78587],[101.50062,17.74288],[101.45582,17.74639],[101.4299,17.72473],[101.40561,17.73601],[101.39625,17.72816],[101.42586,17.7204],[101.399,17.69407],[101.38106,17.69734],[101.3784,17.68254],[101.33145,17.6544],[101.28072,17.60852],[101.27506,17.61146],[101.17429,17.52407],[101.18279,17.49878],[101.15009,17.47021],[101.07679,17.50451],[100.96541,17.57926],[101.02185,17.87637],[101.1793,18.0544],[101.19118,18.2125],[101.15108,18.25624],[101.18227,18.34367],[101.13258,18.35876],[101.05035,18.42603],[101.27585,18.68875],[101.22832,18.73377],[101.25803,18.89545],[101.35606,19.04716],[101.261,19.12717],[101.24911,19.33334],[101.20604,19.35296],[101.21347,19.46223],[101.26991,19.48324],[101.26545,19.59242],[101.08928,19.59748],[100.90302,19.61901],[100.77231,19.48324],[100.64606,19.55884],[100.58219,19.49164],[100.49604,19.53504],[100.43005,19.67273],[100.44344,19.70829],[100.40293,19.75515],[100.5094,19.87904],[100.58808,20.15791],[100.55218,20.17741],[100.51623,20.14411],[100.47567,20.19133],[100.4537,20.19971],[100.45486,20.22837],[100.41392,20.25567],[100.40611,20.282],[100.38499,20.31267],[100.37439,20.35156],[100.33178,20.39926],[100.25899,20.39789],[100.22076,20.31598],[100.16758,20.30108],[100.17428,20.24633],[100.11785,20.24787],[100.09205,20.26678],[100.09755,20.31315],[100.08404,20.36626],[99.95721,20.46301],[99.91616,20.44986],[99.90499,20.4487],[99.89692,20.44789],[99.89301,20.44311],[99.89168,20.44548],[99.88451,20.44596],[99.88211,20.44488],[99.86383,20.44371],[99.81096,20.33687],[99.73199,20.34655],[99.68255,20.32077],[99.46008,20.39673],[99.46077,20.36198],[99.5569,20.20676],[99.52943,20.14811],[99.416,20.08614],[99.20328,20.12877],[99.0735,20.10298],[99.04226,19.93155],[99.02595,19.92542],[99.01874,19.79481],[98.92227,19.76298],[98.80125,19.80708],[98.56065,19.67807],[98.51182,19.71303],[98.24884,19.67876],[98.13829,19.78541],[98.03478,19.80756],[98.04364,19.65755],[97.84715,19.55782],[97.88423,19.5041],[97.78769,19.39429],[97.84186,19.29526],[97.78606,19.26769],[97.84024,19.22217],[97.83479,19.09972],[97.73797,19.04261],[97.73654,18.9812],[97.66487,18.9371],[97.73836,18.88478],[97.76752,18.58097],[97.5258,18.4939],[97.36444,18.57138],[97.34522,18.54596],[97.50383,18.26844],[97.56219,18.33885],[97.64116,18.29778],[97.60841,18.23846],[97.73723,17.97912],[97.66674,17.86848],[97.75789,17.73429],[97.90303,17.58659],[97.90955,17.56597],[97.9226,17.55018],[97.92345,17.54101],[97.95727,17.52726],[97.97281,17.50893],[97.98946,17.51195],[98.03667,17.45203],[98.05615,17.4413],[98.04825,17.42746],[98.1146,17.37767],[98.10439,17.33847],[98.11237,17.31893],[98.12344,17.32245],[98.22687,17.22008],[98.2394,17.22992],[98.28746,17.13258],[98.32377,17.11117],[98.31278,17.08123],[98.34566,17.04822],[98.39441,17.06266],[98.43629,17.03676],[98.47346,16.99556],[98.49801,16.95779],[98.53551,16.89835],[98.49603,16.8446],[98.53792,16.82927],[98.54804,16.81325],[98.52049,16.80823],[98.52736,16.79772],[98.46994,16.73613],[98.5032,16.7129],[98.49693,16.70254],[98.49713,16.69022],[98.51043,16.70107],[98.51579,16.69433],[98.51472,16.68521],[98.51833,16.676],[98.51062,16.64351],[98.56796,16.63117],[98.57422,16.6204],[98.56847,16.6162],[98.5853,16.58519],[98.57912,16.55983],[98.63817,16.47424],[98.6543,16.37301],[98.68074,16.27068],[98.84485,16.42354],[98.92656,16.36425],[98.8376,16.11706],[98.69585,16.13353],[98.57019,16.04578],[98.59853,15.87197],[98.541,15.65406],[98.58598,15.46821],[98.58015,15.3759],[98.56027,15.33471],[98.4866,15.39154],[98.39351,15.34177],[98.41906,15.27103],[98.40522,15.25268],[98.30446,15.30667],[98.22,15.21327],[98.18821,15.13125],[98.24874,14.83013],[98.56762,14.37701],[98.97356,14.04868],[99.16695,13.72621],[99.20617,13.20575],[99.12225,13.19847],[99.10646,13.05804],[99.18748,12.9898],[99.18905,12.84799],[99.29254,12.68921],[99.409,12.60603],[99.47519,12.1353],[99.56445,12.14805],[99.53424,12.02317],[99.64891,11.82699],[99.64108,11.78948],[99.5672,11.62732],[99.47598,11.62434],[99.39485,11.3925],[99.31573,11.32081],[99.32756,11.28545],[99.06938,10.94857],[99.02337,10.97217],[98.99701,10.92962],[99.0111,10.85294],[98.8621,10.77858],[98.78374,10.67806],[98.77275,10.62548],[98.80133,10.56139],[98.81481,10.54688],[98.81721,10.50064],[98.75507,10.39107],[98.7391,10.31488],[98.55174,9.92804],[98.52291,9.92389],[98.47298,9.95782],[98.33094,9.91973],[98.21525,9.56576],[97.63455,9.60854],[97.19814,8.18901]]]}},{"type":"Feature","properties":{"id":"TM"},"geometry":{"type":"Polygon","coordinates":[[[51.7708,40.29239],[53.89734,37.3464],[54.24565,37.32047],[54.36211,37.34912],[54.58664,37.45809],[54.67247,37.43532],[54.77822,37.51597],[54.81804,37.61285],[54.77684,37.62264],[54.851,37.75739],[55.13412,37.94705],[55.44152,38.08564],[55.76561,38.12238],[55.97847,38.08024],[56.33278,38.08132],[56.32454,38.18502],[56.43303,38.26054],[56.62255,38.24005],[56.75794,38.284],[56.78815,38.25301],[57.02659,38.18328],[57.21169,38.28965],[57.37236,38.09321],[57.35042,37.98546],[57.79534,37.89299],[58.21399,37.77281],[58.22999,37.6856],[58.4028,37.62837],[58.47786,37.6433],[58.55335,37.70745],[58.6921,37.64548],[58.9338,37.67374],[59.22905,37.51161],[59.33507,37.53146],[59.39797,37.47892],[59.38968,37.33931],[59.54606,37.177],[59.55178,37.13594],[59.74678,37.12499],[60.03959,37.02612],[60.34767,36.63214],[61.14516,36.64644],[61.18187,36.55348],[61.14646,36.39061],[61.23332,36.11319],[61.11848,35.96063],[61.22444,35.92879],[61.26152,35.80749],[61.22719,35.67038],[61.27349,35.60734],[61.33924,35.62353],[61.58742,35.43803],[61.77693,35.41341],[61.96718,35.45458],[62.06365,35.43423],[62.15871,35.33278],[62.29076,35.25728],[62.3017,35.12815],[62.47813,35.28507],[62.62288,35.22067],[62.74098,35.25432],[62.90853,35.37086],[63.10031,35.43256],[63.12276,35.53196],[63.10079,35.63024],[63.25288,35.68264],[63.19554,35.71369],[63.10834,35.81976],[63.12276,35.86208],[63.29944,35.85684],[63.50089,35.89934],[63.53693,35.94764],[64.05879,36.04708],[64.05385,36.10433],[64.43288,36.24401],[64.57295,36.34362],[64.62514,36.44311],[64.61141,36.6351],[64.97945,37.21913],[65.55198,37.24481],[65.64263,37.34388],[65.64485,37.44515],[65.7154,37.5541],[66.33888,37.31133],[66.55743,37.35409],[66.52303,37.39827],[66.59088,37.44869],[66.52496,37.56009],[66.53676,37.80084],[66.70761,37.9515],[66.51603,38.03376],[66.41042,38.02403],[66.24858,38.14184],[65.83913,38.25733],[65.55873,38.29052],[64.32576,38.98691],[64.19086,38.95561],[63.70778,39.22349],[63.69392,39.27266],[62.43337,39.98528],[62.34273,40.43206],[62.08717,40.58579],[61.86675,41.12488],[61.4446,41.29407],[61.39732,41.19873],[61.33199,41.14946],[61.22212,41.14946],[61.03261,41.25691],[60.5078,41.21694],[60.04199,41.44478],[60.18117,41.60082],[60.04869,41.74967],[60.08504,41.80997],[60.31082,41.74749],[60.32764,41.76772],[59.90226,41.99968],[60.0356,42.01028],[60.04659,42.08982],[59.96419,42.1428],[60.01831,42.2069],[59.89128,42.298],[59.73867,42.29965],[59.46212,42.29178],[59.2955,42.37064],[59.17528,42.53044],[59.00636,42.52436],[58.6266,42.79314],[58.57995,42.64103],[58.27504,42.69632],[58.14321,42.62159],[58.28453,42.55662],[58.51674,42.30348],[58.40688,42.29535],[58.3492,42.43335],[57.99214,42.50021],[57.90975,42.4374],[57.92897,42.24047],[57.84932,42.18555],[57.6296,42.16519],[57.03633,41.92043],[56.96218,41.80383],[57.12628,41.33429],[57.03423,41.25435],[56.00314,41.32584],[55.45471,41.25609],[54.95182,41.92424],[54.20635,42.38477],[52.97575,42.1308],[52.47884,41.78034],[52.26048,41.69249],[51.7708,40.29239]]]}},{"type":"Feature","properties":{"id":"TL"},"geometry":{"type":"Polygon","coordinates":[[[124.03753,-9.33294],[124.10539,-9.41206],[124.14517,-9.42324],[124.21247,-9.36904],[124.28115,-9.42189],[124.28115,-9.50453],[124.3535,-9.48493],[124.35258,-9.43002],[124.38451,-9.36395],[124.46288,-9.30339],[124.48554,-9.12883],[124.94011,-8.85617],[124.95128,-8.9585],[124.95574,-9.06277],[125.0814,-9.01699],[125.11196,-8.96494],[125.18632,-9.03142],[125.17993,-9.16819],[125.09434,-9.19669],[125.05672,-9.17022],[124.98227,-9.19149],[124.99746,-9.28172],[125.06166,-9.36082],[125.08792,-9.46277],[125.68138,-9.85176],[127.55165,-9.05052],[127.49908,-8.26585],[125.62512,-8.11166],[124.03753,-9.33294]]]}},{"type":"Feature","properties":{"id":"TN"},"geometry":{"type":"Polygon","coordinates":[[[7.52851,34.06493],[7.54088,33.7726],[7.73687,33.42114],[7.83028,33.18851],[8.11433,33.10175],[8.1179,33.05086],[8.31895,32.83483],[8.35999,32.50101],[9.07483,32.07865],[9.55749,30.22843],[9.88152,30.34074],[10.29516,30.90337],[10.12239,31.42098],[10.31364,31.72648],[10.51803,31.73429],[10.65948,31.97429],[10.78479,31.98856],[10.91766,32.14247],[11.53898,32.4138],[11.61254,32.51381],[11.48483,32.64775],[11.52671,33.07888],[11.55852,33.1409],[11.56255,33.16754],[11.66543,33.34642],[12.02012,35.25036],[10.73363,38.54816],[8.28559,38.46209],[8.64044,36.9401],[8.62972,36.86499],[8.67706,36.8364],[8.57613,36.78062],[8.46537,36.7706],[8.47609,36.66607],[8.16167,36.48817],[8.18936,36.44939],[8.40731,36.42208],[8.2626,35.91733],[8.26472,35.73669],[8.35371,35.66373],[8.36086,35.47774],[8.30329,35.29884],[8.47489,35.2388],[8.3555,35.10007],[8.30727,34.95378],[8.25446,34.926],[8.29655,34.72798],[8.20482,34.57575],[7.86264,34.3987],[7.81242,34.21841],[7.74207,34.16492],[7.66174,34.20167],[7.52851,34.06493]]]}},{"type":"Feature","properties":{"id":"TR"},"geometry":{"type":"Polygon","coordinates":[[[25.61285,40.17161],[25.94257,39.39358],[26.43357,39.43096],[26.70773,39.0312],[26.61814,38.81372],[26.21136,38.65436],[26.32173,38.48731],[26.24183,38.44695],[26.21136,38.17558],[27.05537,37.9131],[27.16428,37.72343],[26.99377,37.69034],[26.95583,37.64989],[27.14757,37.32],[27.18011,36.95503],[27.45627,36.9008],[27.24613,36.71622],[27.46117,36.53789],[27.89482,36.69898],[27.95037,36.46155],[28.23708,36.56812],[29.12174,36.12466],[29.59233,36.17753],[29.79413,35.94445],[32.82353,35.70297],[35.51152,36.10954],[35.931,35.92109],[36.00438,35.94132],[36.01741,35.92041],[35.99829,35.88242],[36.11553,35.8605],[36.13772,35.83635],[36.14029,35.81015],[36.1623,35.80925],[36.17351,35.84439],[36.17368,35.92047],[36.19771,35.94896],[36.25745,35.96286],[36.27222,35.94556],[36.29942,35.95918],[36.28209,36.00154],[36.29796,36.00904],[36.33956,35.98687],[36.3801,36.01321],[36.37384,36.17266],[36.39367,36.22405],[36.46748,36.19725],[36.5037,36.24323],[36.61382,36.22121],[36.70017,36.23693],[36.67553,36.29499],[36.65412,36.29707],[36.65197,36.31391],[36.66588,36.33058],[36.65653,36.33861],[36.60305,36.33355],[36.60387,36.39268],[36.54206,36.49539],[36.59099,36.57866],[36.57398,36.65186],[36.63159,36.71281],[36.61581,36.74629],[36.67991,36.83443],[36.73175,36.81753],[36.99594,36.75236],[36.99886,36.74012],[37.04399,36.73483],[37.04619,36.71101],[37.01647,36.69512],[37.01929,36.67681],[37.08589,36.63453],[37.11336,36.67957],[37.16348,36.65657],[37.21988,36.6736],[37.32519,36.66125],[37.47144,36.63109],[37.49547,36.67571],[37.67005,36.74631],[37.81442,36.75992],[37.99329,36.83188],[38.02213,36.8233],[38.03526,36.86221],[38.22246,36.92148],[38.34938,36.89945],[38.38786,36.90296],[38.54742,36.84734],[38.72972,36.70806],[39.06789,36.70365],[39.21538,36.66834],[39.81589,36.75538],[40.05091,36.84116],[40.0619,36.85627],[40.2769,36.92546],[40.4132,37.01351],[40.51895,37.02722],[40.75515,37.12268],[40.90856,37.13147],[41.19838,37.08024],[41.19804,37.0638],[41.21847,37.06216],[41.24559,37.07914],[41.51166,37.07942],[41.96622,37.16537],[42.08982,37.2064],[42.18225,37.28569],[42.2112,37.32491],[42.20848,37.27396],[42.23683,37.2863],[42.26039,37.27017],[42.28274,37.28266],[42.34305,37.23217],[42.32313,37.17814],[42.35212,37.10858],[42.56725,37.14878],[42.78887,37.38615],[42.94967,37.3157],[43.12683,37.37656],[43.30535,37.30355],[43.33508,37.33105],[43.50122,37.24262],[43.56702,37.25675],[43.63085,37.21957],[43.7009,37.23692],[43.8052,37.22825],[43.82699,37.19477],[43.84878,37.22205],[43.90949,37.22453],[44.01638,37.32904],[44.12984,37.31952],[44.2613,37.25055],[44.27998,37.16501],[44.22239,37.15756],[44.18503,37.09551],[44.25975,36.98119],[44.30897,36.96347],[44.35937,37.02843],[44.35315,37.04955],[44.38292,37.05914],[44.39429,37.04949],[44.4154,37.05216],[44.51514,37.1029],[44.63179,37.19229],[44.76698,37.16162],[44.78319,37.1431],[44.7868,37.16644],[44.75986,37.21549],[44.81021,37.2915],[44.58449,37.45018],[44.61401,37.60165],[44.56887,37.6429],[44.62096,37.71985],[44.55498,37.783],[44.45948,37.77065],[44.3883,37.85433],[44.22509,37.88859],[44.42476,38.25763],[44.50115,38.33939],[44.44386,38.38295],[44.38309,38.36117],[44.3119,38.37887],[44.3207,38.49799],[44.32058,38.62752],[44.28065,38.6465],[44.26155,38.71427],[44.30322,38.81581],[44.18863,38.93881],[44.20946,39.13975],[44.1043,39.19842],[44.03667,39.39223],[44.22452,39.4169],[44.29818,39.378],[44.37921,39.4131],[44.42832,39.4131],[44.41849,39.56659],[44.48111,39.61579],[44.47298,39.68788],[44.6137,39.78393],[44.65422,39.72163],[44.71806,39.71124],[44.81043,39.62677],[44.80977,39.65768],[44.75779,39.7148],[44.61845,39.8281],[44.46635,39.97733],[44.26973,40.04866],[44.1778,40.02845],[44.1057,40.03555],[43.92307,40.01787],[43.65688,40.11199],[43.65221,40.14889],[43.71136,40.16673],[43.59928,40.34019],[43.60862,40.43267],[43.54791,40.47413],[43.63664,40.54159],[43.7425,40.66805],[43.74872,40.7365],[43.67712,40.84846],[43.67712,40.93084],[43.58683,40.98961],[43.47319,41.02251],[43.44984,41.0988],[43.4717,41.12611],[43.44973,41.17666],[43.36118,41.2028],[43.23096,41.17536],[43.1945,41.25242],[43.13373,41.25503],[43.21707,41.30331],[43.02956,41.37891],[42.8785,41.50516],[42.84899,41.47265],[42.78995,41.50126],[42.84471,41.58912],[42.72794,41.59714],[42.60163,41.58516],[42.51772,41.43606],[42.26387,41.49346],[41.95134,41.52466],[41.81939,41.43621],[41.7124,41.47417],[41.7148,41.4932],[41.54366,41.52185],[41.2657,41.64323],[34.8305,42.4581],[28.32297,41.98371],[27.91479,41.97902],[27.83492,41.99709],[27.81235,41.94803],[27.69949,41.97515],[27.55191,41.90928],[27.52379,41.93756],[27.45478,41.96591],[27.27411,42.10409],[27.22376,42.10152],[27.19251,42.06028],[27.08486,42.08735],[27.03277,42.0809],[26.9692,42.00542],[26.79143,41.97386],[26.62996,41.97644],[26.56202,41.92731],[26.57961,41.90024],[26.5385,41.82403],[26.36952,41.82265],[26.33589,41.76802],[26.32952,41.73637],[26.35957,41.71149],[26.47958,41.67037],[26.52957,41.62179],[26.59566,41.60754],[26.59742,41.48058],[26.62811,41.41531],[26.62997,41.34613],[26.5837,41.32131],[26.5209,41.33993],[26.39861,41.25053],[26.32259,41.24929],[26.32049,41.06382],[26.36615,41.0128],[26.33297,40.98388],[26.35894,40.94292],[26.32259,40.94042],[26.28623,40.93005],[26.29441,40.89119],[26.25526,40.91286],[26.20856,40.86048],[26.21351,40.83298],[26.15685,40.80709],[26.12854,40.77339],[26.12495,40.74283],[26.08638,40.73214],[26.0754,40.72772],[26.03489,40.73051],[25.94795,40.72797],[26.04292,40.3958],[25.61285,40.17161]]]}},{"type":"Feature","properties":{"id":"TW"},"geometry":{"type":"Polygon","coordinates":[[[118.09488,24.38193],[118.179,24.33015],[118.41371,24.06775],[120.80773,21.49775],[121.80757,21.89772],[122.36499,25.95233],[120.49232,25.22863],[118.56434,24.49266],[118.42453,24.54644],[118.35291,24.51645],[118.28244,24.51231],[118.09488,24.38193]]]}},{"type":"Feature","properties":{"id":"TZ"},"geometry":{"type":"Polygon","coordinates":[[[29.43398,-4.41764],[29.48706,-5.95084],[30.80326,-8.29201],[31.0065,-8.571],[31.37533,-8.60769],[31.45952,-8.67453],[31.57147,-8.70619],[31.57147,-8.81388],[31.7007,-8.91797],[31.81587,-8.88618],[31.94663,-8.93846],[31.94196,-9.02303],[31.98866,-9.07069],[32.08206,-9.04609],[32.16146,-9.05993],[32.25486,-9.13371],[32.43543,-9.11988],[32.49147,-9.14754],[32.53137,-9.23597],[32.64844,-9.27528],[32.71891,-9.27723],[32.75611,-9.28583],[32.76543,-9.32314],[32.79041,-9.32195],[32.80946,-9.34287],[32.95389,-9.40138],[32.99726,-9.36489],[33.03889,-9.4146],[33.14925,-9.49322],[33.2048,-9.50561],[33.24917,-9.48825],[33.27612,-9.49918],[33.30857,-9.48377],[33.34856,-9.50713],[33.42435,-9.57561],[33.42212,-9.59414],[33.48778,-9.62402],[33.58082,-9.58306],[33.667,-9.61259],[33.76677,-9.58516],[33.77849,-9.59254],[33.79879,-9.60807],[33.81587,-9.63142],[33.94208,-9.72174],[33.9638,-9.62206],[33.95829,-9.54066],[34.03865,-9.49398],[34.54499,-10.0678],[34.51911,-10.12279],[34.57581,-10.56271],[34.65946,-10.6828],[34.67047,-10.93796],[34.61161,-11.01611],[34.63305,-11.11731],[34.79375,-11.32245],[34.91153,-11.39799],[34.96296,-11.57354],[35.63599,-11.55927],[35.82767,-11.41081],[36.19094,-11.57593],[36.19094,-11.70008],[36.62068,-11.72884],[36.80309,-11.56836],[37.3936,-11.68949],[37.76614,-11.53352],[37.8388,-11.3123],[37.93618,-11.26228],[38.21598,-11.27289],[38.47892,-11.42248],[38.88996,-11.16978],[39.24395,-11.17433],[39.58249,-10.96043],[40.01083,-10.80632],[40.44265,-10.4618],[40.74206,-10.25691],[39.89822,-6.52409],[39.92698,-5.99402],[39.94979,-5.5732],[39.97111,-5.17958],[39.99221,-4.78982],[39.62121,-4.68136],[39.56066,-4.99867],[39.21631,-4.67835],[37.81321,-3.69179],[37.75036,-3.54243],[37.63099,-3.50723],[37.57931,-3.44551],[37.71177,-3.30813],[37.67297,-3.06081],[37.51195,-2.95435],[36.78686,-2.54798],[34.0824,-1.02264],[34.03084,-1.05101],[34.02286,-1.00779],[33.93107,-0.99298],[30.80408,-0.99911],[30.76635,-0.9852],[30.69537,-1.01909],[30.6558,-1.06569],[30.47238,-1.05711],[30.45116,-1.10641],[30.50889,-1.16412],[30.57123,-1.33264],[30.71974,-1.43244],[30.84079,-1.64652],[30.80802,-1.91477],[30.89303,-2.08223],[30.83915,-2.35795],[30.77339,-2.38789],[30.54501,-2.41404],[30.41789,-2.66266],[30.52747,-2.65841],[30.40662,-2.86151],[30.4987,-2.9573],[30.57926,-2.89791],[30.6675,-2.98987],[30.83823,-2.97837],[30.84165,-3.25152],[30.63949,-3.35577],[30.68172,-3.40958],[30.47435,-3.54511],[30.39676,-3.70769],[30.405,-3.7906],[30.33514,-3.78067],[30.21703,-3.98995],[30.21532,-4.04338],[30.08399,-4.16289],[30.04417,-4.27451],[29.8853,-4.3607],[29.82885,-4.36153],[29.76616,-4.42514],[29.7641,-4.46348],[29.43398,-4.41764]]]}},{"type":"Feature","properties":{"id":"UG"},"geometry":{"type":"Polygon","coordinates":[[[29.58388,-0.89821],[29.59061,-1.39016],[29.7381,-1.33986],[29.75891,-1.34557],[29.77724,-1.36693],[29.79672,-1.37311],[29.82436,-1.30837],[29.86375,-1.35882],[29.87749,-1.35634],[29.89585,-1.45179],[29.91448,-1.48281],[29.98203,-1.4569],[30.0179,-1.41451],[30.04966,-1.43013],[30.08176,-1.37487],[30.12047,-1.38371],[30.16862,-1.34338],[30.17034,-1.27585],[30.21223,-1.27894],[30.3008,-1.1552],[30.34234,-1.1298],[30.35462,-1.06509],[30.42783,-1.06389],[30.44019,-1.0481],[30.47238,-1.05711],[30.6558,-1.06569],[30.69537,-1.01909],[30.76635,-0.9852],[30.80408,-0.99911],[33.93107,-0.99298],[33.90936,0.10581],[34.10868,0.36958],[34.08946,0.45472],[34.11941,0.48356],[34.11881,0.52244],[34.13493,0.58118],[34.20196,0.62289],[34.27867,0.64075],[34.28,0.67873],[34.31304,0.69598],[34.31545,0.76142],[34.3718,0.78167],[34.37858,0.79476],[34.3839,0.78953],[34.38613,0.79952],[34.41471,0.80832],[34.40952,0.82832],[34.42218,0.8472],[34.44492,0.85703],[34.52369,1.10692],[34.57427,1.09868],[34.58029,1.14712],[34.67001,1.20797],[34.79747,1.22067],[34.83189,1.26864],[34.82606,1.30944],[34.7918,1.36752],[34.87524,1.53361],[34.94132,1.5741],[34.99402,1.66316],[34.99978,1.96213],[34.98424,1.98512],[34.90947,2.42447],[34.95267,2.47209],[34.77244,2.70272],[34.78137,2.76223],[34.73967,2.85447],[34.65774,2.8753],[34.60114,2.93034],[34.56242,3.11478],[34.45815,3.18319],[34.40006,3.37949],[34.41794,3.44342],[34.39112,3.48802],[34.44922,3.51627],[34.45815,3.67385],[34.15429,3.80464],[34.06046,4.15235],[33.9873,4.23316],[33.51264,3.75068],[33.18356,3.77812],[33.02852,3.89296],[32.89746,3.81339],[32.72021,3.77327],[32.41337,3.748],[32.20782,3.6053],[32.19888,3.50867],[32.08866,3.53543],[32.08491,3.56287],[32.05187,3.589],[31.95907,3.57408],[31.96205,3.6499],[31.86821,3.78664],[31.81459,3.82083],[31.70757,3.72602],[31.57539,3.68183],[31.51205,3.63352],[31.50672,3.6748],[31.28837,3.7966],[31.16786,3.79266],[30.97601,3.693],[30.90685,3.59463],[30.8654,3.49448],[30.84849,3.48729],[30.86977,3.47726],[30.89681,3.50142],[30.94081,3.50847],[30.93689,3.40795],[30.89681,3.35209],[30.86462,3.27797],[30.84033,3.26794],[30.83596,3.25166],[30.84081,3.23846],[30.83059,3.22869],[30.79313,3.06552],[30.76738,3.04872],[30.8574,2.9508],[30.8857,2.83923],[30.75612,2.5863],[30.74652,2.49045],[30.75073,2.4221],[30.77733,2.43659],[30.83274,2.42244],[30.84978,2.38205],[30.87269,2.3688],[30.88209,2.34068],[30.89188,2.33973],[30.91059,2.3318],[30.91325,2.34115],[30.93698,2.33695],[30.94754,2.36087],[30.9417,2.37459],[30.95063,2.39526],[30.98951,2.40324],[31.02659,2.38253],[31.02848,2.37802],[31.07379,2.34432],[31.06766,2.33472],[31.07727,2.31653],[31.0856,2.30877],[31.081,2.30243],[31.07019,2.30273],[31.11165,2.27134],[31.13259,2.26242],[31.14126,2.28094],[31.16666,2.27554],[31.20301,2.29072],[31.2028,2.22082],[31.24803,2.1999],[31.30588,2.1571],[31.30127,2.11006],[30.48503,1.21675],[30.24671,1.14974],[30.22139,0.99635],[30.1484,0.89805],[29.98307,0.84295],[29.95477,0.64486],[29.97413,0.52124],[29.87284,0.39166],[29.81922,0.16824],[29.77454,0.16675],[29.7224,0.07291],[29.72687,-0.08051],[29.65091,-0.46777],[29.67474,-0.47969],[29.67176,-0.55714],[29.62708,-0.71055],[29.63006,-0.8997],[29.58388,-0.89821]]]}},{"type":"Feature","properties":{"id":"UA"},"geometry":{"type":"Polygon","coordinates":[[[22.14689,48.4005],[22.21276,48.42693],[22.38133,48.23726],[22.49806,48.25189],[22.59007,48.15121],[22.58733,48.10813],[22.67183,48.09195],[22.73427,48.12005],[22.82804,48.11442],[22.83044,48.09482],[22.85894,48.07693],[22.8828,48.04182],[22.84276,47.98602],[22.89849,47.95851],[22.94301,47.96672],[22.92241,48.02002],[23.0158,47.99338],[23.08858,48.00716],[23.1133,48.08061],[23.14986,48.11099],[23.27397,48.08245],[23.33577,48.0237],[23.4979,47.96858],[23.52803,48.01818],[23.5653,48.00499],[23.63894,48.00293],[23.66399,47.98464],[23.75188,47.99705],[23.80904,47.98142],[23.8602,47.9329],[23.89352,47.94512],[23.94192,47.94868],[23.96337,47.96672],[23.98553,47.96076],[24.00801,47.968],[24.03496,47.95109],[24.06466,47.95317],[24.12447,47.90644],[24.15408,47.91683],[24.22566,47.90231],[24.34926,47.9244],[24.43578,47.97131],[24.61994,47.95062],[24.70632,47.84428],[24.81893,47.82031],[24.88896,47.7234],[25.12968,47.75329],[25.24812,47.89378],[25.63878,47.94924],[25.77723,47.93919],[25.9164,47.97647],[26.14059,47.98716],[26.26968,48.08151],[26.33504,48.18418],[26.55202,48.22445],[26.62823,48.25804],[26.6839,48.35828],[26.79239,48.29071],[26.82809,48.31629],[26.70879,48.40527],[26.83753,48.41803],[26.93384,48.36558],[27.04662,48.37426],[27.0231,48.42485],[27.08078,48.43214],[27.12833,48.37495],[27.18326,48.38783],[27.27855,48.37534],[27.32159,48.4434],[27.37312,48.44189],[27.37741,48.41026],[27.44813,48.41222],[27.46891,48.45038],[27.58332,48.49243],[27.60396,48.48799],[27.58735,48.46148],[27.6676,48.44024],[27.74545,48.45886],[27.7833,48.44804],[27.80699,48.43352],[27.81789,48.42151],[27.87533,48.4037],[27.88391,48.36699],[27.96226,48.32469],[28.04527,48.32661],[28.09873,48.3124],[28.0765,48.23347],[28.18722,48.25977],[28.18508,48.22198],[28.20662,48.20488],[28.2856,48.23202],[28.32508,48.23384],[28.35519,48.24957],[28.36996,48.20543],[28.34912,48.1787],[28.30586,48.1597],[28.30609,48.14018],[28.34009,48.13147],[28.38712,48.17567],[28.44111,48.14908],[28.42626,48.13407],[28.42403,48.11866],[28.48428,48.0737],[28.51164,48.12396],[28.53921,48.17453],[28.69896,48.13106],[28.85232,48.12506],[28.8414,48.03392],[28.92408,47.96257],[29.17453,47.99348],[29.19977,47.88837],[29.27942,47.88952],[29.20663,47.80367],[29.27255,47.79953],[29.22191,47.7384],[29.22414,47.60012],[29.11743,47.55001],[29.18277,47.44805],[29.24543,47.42727],[29.30499,47.44202],[29.38156,47.37812],[29.39889,47.30179],[29.47854,47.30366],[29.48678,47.36043],[29.57399,47.37022],[29.59905,47.25721],[29.55236,47.25115],[29.57696,47.13581],[29.49732,47.12878],[29.53044,47.07851],[29.61038,47.09932],[29.62137,47.05069],[29.57056,46.94766],[29.72986,46.92234],[29.75458,46.8604],[29.88195,46.88589],[29.98632,46.81838],[29.94522,46.80055],[29.9743,46.75325],[29.94409,46.56002],[29.88916,46.54302],[30.02511,46.45132],[30.16794,46.40967],[30.09103,46.38694],[29.94114,46.40114],[29.8744,46.35522],[29.75732,46.46186],[29.66359,46.4215],[29.6763,46.36041],[29.5939,46.35472],[29.49623,46.45725],[29.35357,46.49505],[29.24886,46.37912],[29.23547,46.55435],[29.02409,46.49582],[29.01241,46.46177],[28.9306,46.45699],[29.004,46.31495],[28.98478,46.31803],[28.95275,46.25988],[28.99799,46.23495],[29.06656,46.19716],[28.95137,46.09394],[29.00613,46.04962],[28.98004,46.00385],[28.81021,45.97709],[28.74383,45.96664],[28.76946,45.88515],[28.78589,45.83286],[28.69852,45.81753],[28.71036,45.78021],[28.60977,45.76524],[28.58402,45.72475],[28.52823,45.73803],[28.47879,45.66994],[28.51587,45.6613],[28.54196,45.58062],[28.49252,45.56716],[28.51449,45.49982],[28.43072,45.48538],[28.41836,45.51715],[28.30201,45.54744],[28.21139,45.46895],[28.28504,45.43907],[28.34554,45.32102],[28.5735,45.24759],[28.71358,45.22631],[28.78911,45.24179],[28.81383,45.3384],[28.94292,45.28045],[28.96077,45.33164],[29.24779,45.43388],[29.42632,45.44545],[29.59798,45.38857],[29.68175,45.26885],[29.65428,45.25629],[29.69272,45.19227],[30.04414,45.08461],[32.14262,45.66011],[33.54017,46.0123],[33.61467,46.13561],[33.63854,46.14147],[33.61517,46.22615],[33.646,46.23028],[33.74047,46.18555],[33.82255,46.21028],[33.9971,46.10671],[34.07311,46.11769],[34.204,46.06017],[34.36177,46.05994],[34.44155,45.95995],[34.50256,45.9367],[34.56575,45.99728],[34.80118,45.89282],[34.79905,45.81009],[34.96015,45.75634],[35.23066,45.79231],[38.3384,46.98085],[38.22955,47.12069],[38.23482,47.23145],[38.32112,47.2585],[38.33074,47.30508],[38.22074,47.30542],[38.28954,47.39255],[38.30108,47.47417],[38.2864,47.53499],[38.35086,47.57664],[38.35052,47.61599],[38.45695,47.61773],[38.62827,47.66238],[38.67247,47.69997],[38.77229,47.68457],[38.79628,47.81109],[38.87838,47.87329],[39.39113,47.86408],[39.74819,47.82767],[39.82003,47.95946],[39.77462,48.03964],[39.88672,48.04314],[39.83724,48.06501],[39.94847,48.22811],[40.00752,48.22445],[39.99229,48.31693],[39.97325,48.31399],[39.97062,48.30734],[39.96594,48.30617],[39.94856,48.29452],[39.93646,48.2901],[39.92611,48.27165],[39.91294,48.26971],[39.91161,48.28987],[39.90041,48.3049],[39.84273,48.30947],[39.84136,48.33321],[39.94594,48.35225],[39.93221,48.36468],[39.94285,48.38282],[39.88794,48.44226],[39.86196,48.46633],[39.8517,48.56627],[39.8002,48.58819],[39.67226,48.59368],[39.71765,48.68673],[39.72587,48.73468],[39.80037,48.84054],[39.97182,48.79398],[40.08168,48.87443],[40.03349,48.92092],[39.98967,48.86901],[39.78368,48.91596],[39.74874,48.98675],[39.72649,48.9754],[39.71353,48.98959],[39.6683,48.99454],[39.6836,49.05121],[39.93437,49.05709],[40.01988,49.1761],[40.22129,49.25391],[40.19545,49.3429],[40.14953,49.37656],[40.11383,49.38656],[40.03087,49.45452],[40.03636,49.52321],[40.16683,49.56865],[40.13249,49.61672],[39.84548,49.56064],[39.65047,49.61761],[39.59142,49.73758],[39.44496,49.76067],[39.27968,49.75976],[39.1808,49.88911],[38.9391,49.79524],[38.90477,49.86787],[38.73311,49.90238],[38.68677,50.00904],[38.65688,49.97176],[38.35408,50.00664],[38.32524,50.08866],[38.17954,50.07873],[38.21388,49.97683],[38.04874,49.9205],[37.96394,49.9839],[37.90432,50.05075],[37.76498,50.07829],[37.63778,50.18107],[37.61113,50.21976],[37.62879,50.24481],[37.62486,50.29966],[37.47243,50.36277],[37.46664,50.44712],[37.336,50.43586],[37.12949,50.34578],[36.92882,50.35077],[36.69377,50.26982],[36.64824,50.21766],[36.5667,50.24451],[36.57949,50.27518],[36.4504,50.31828],[36.30101,50.29088],[36.16699,50.43421],[36.06893,50.45205],[35.91464,50.43498],[35.88932,50.4387],[35.82057,50.41781],[35.73659,50.35489],[35.61711,50.35707],[35.58003,50.45117],[35.47296,50.49017],[35.39091,50.65229],[35.49047,50.66099],[35.45888,50.68966],[35.47828,50.77283],[35.41367,50.80227],[35.39307,50.92145],[35.32598,50.94524],[35.40837,51.04119],[35.31774,51.08434],[35.20465,51.04576],[35.12685,51.16191],[35.14058,51.23162],[34.97304,51.2342],[34.82472,51.17483],[34.6874,51.18],[34.6613,51.25053],[34.49329,51.24343],[34.38802,51.2746],[34.31661,51.23936],[34.23009,51.26429],[34.33446,51.363],[34.22048,51.4187],[34.30562,51.5205],[34.17599,51.63253],[34.07765,51.67065],[34.42308,51.72367],[34.4399,51.74828],[34.41136,51.82793],[34.28352,51.89068],[34.09297,52.02302],[34.11199,52.14087],[34.05239,52.20132],[33.78789,52.37204],[33.55718,52.30324],[33.48027,52.31499],[33.51323,52.35779],[33.1636,52.35861],[32.89937,52.2461],[32.85405,52.27888],[32.71247,52.25428],[32.54751,52.32442],[32.49103,52.31729],[32.3528,52.32842],[32.38988,52.24946],[32.33083,52.23685],[32.34044,52.1434],[32.2777,52.10266],[32.23331,52.08085],[32.08813,52.03319],[31.92159,52.05144],[31.96141,52.08015],[31.85863,52.11251],[31.81722,52.09955],[31.7822,52.11406],[31.38326,52.12991],[31.26022,52.03982],[31.13332,52.1004],[30.95589,52.07775],[30.90897,52.00699],[30.76443,51.89739],[30.68804,51.82806],[30.51946,51.59649],[30.64992,51.35014],[30.56203,51.25655],[30.36153,51.33984],[30.34642,51.42555],[30.17888,51.51025],[29.77376,51.4461],[29.7408,51.53417],[29.54372,51.48372],[29.49773,51.39814],[29.42357,51.4187],[29.32881,51.37843],[29.25191,51.49828],[29.25603,51.57089],[29.20659,51.56918],[29.16402,51.64679],[29.1187,51.65872],[28.99098,51.56833],[28.95528,51.59222],[28.81795,51.55552],[28.77451,51.48759],[28.75958,51.42362],[28.67959,51.45219],[28.64429,51.5664],[28.47051,51.59734],[28.37592,51.54505],[28.23452,51.66988],[28.10658,51.57857],[27.95827,51.56065],[27.91844,51.61952],[27.85253,51.62293],[27.76052,51.47604],[27.67125,51.50854],[27.71932,51.60672],[27.25136,51.60651],[27.26613,51.65957],[27.20948,51.66713],[27.20602,51.77291],[26.99422,51.76933],[26.9489,51.73788],[26.80043,51.75777],[26.69759,51.82284],[26.46962,51.80501],[26.39367,51.87315],[26.19084,51.86781],[26.00408,51.92967],[25.83217,51.92587],[25.80574,51.94556],[25.73673,51.91973],[25.43077,51.92193],[25.20228,51.97143],[24.98784,51.91273],[24.37123,51.88222],[24.29021,51.80841],[24.3163,51.75063],[24.13075,51.66979],[23.99907,51.58369],[23.8741,51.59734],[23.91118,51.63316],[23.75381,51.65754],[23.60906,51.62122],[23.67776,51.50233],[23.62751,51.50512],[23.69905,51.40871],[23.63858,51.32182],[23.80678,51.18405],[23.90376,51.07697],[23.92217,51.00836],[24.04576,50.90196],[24.14524,50.86128],[24.0952,50.83262],[23.99254,50.83847],[23.95925,50.79271],[24.0595,50.71625],[24.0996,50.60752],[24.07048,50.5071],[24.03668,50.44507],[23.99563,50.41289],[23.79445,50.40481],[23.71382,50.38248],[23.68858,50.33417],[23.57974,50.26706],[23.28221,50.0957],[22.99329,49.84249],[22.83179,49.69875],[22.80261,49.69098],[22.78304,49.65543],[22.64534,49.53094],[22.69444,49.49378],[22.748,49.32759],[22.72009,49.20288],[22.86336,49.10513],[22.89122,49.00725],[22.56155,49.08865],[22.54338,49.01424],[22.48296,48.99172],[22.42721,48.90986],[22.38532,48.86448],[22.34567,48.69877],[22.21379,48.6218],[22.16023,48.56548],[22.14689,48.4005]]]}},{"type":"Feature","properties":{"id":"UY"},"geometry":{"type":"Polygon","coordinates":[[[-58.44442,-33.84033],[-58.34425,-34.15035],[-57.83001,-34.69099],[-55.71154,-35.78518],[-53.54511,-34.54062],[-53.18243,-33.86894],[-53.37138,-33.74313],[-53.39593,-33.75169],[-53.44031,-33.69344],[-53.52794,-33.68908],[-53.53459,-33.16843],[-53.09417,-32.69024],[-53.37535,-32.56931],[-53.39183,-32.58862],[-53.46496,-32.49528],[-53.58856,-32.44633],[-53.76024,-32.0751],[-54.15744,-31.87391],[-55.50821,-30.91349],[-55.50841,-30.9027],[-55.51862,-30.89828],[-55.52712,-30.89997],[-55.53276,-30.90218],[-55.53431,-30.89714],[-55.54572,-30.89051],[-55.55218,-30.88193],[-55.55373,-30.8732],[-55.5634,-30.8686],[-55.58866,-30.84117],[-55.8356,-31.04381],[-56.00658,-31.07704],[-56.00692,-30.83621],[-56.17309,-30.632],[-56.4619,-30.38457],[-56.47543,-30.38846],[-56.48796,-30.39908],[-56.82128,-30.09434],[-57.05715,-30.08246],[-57.21405,-30.28723],[-57.5584,-30.21398],[-57.65384,-30.18549],[-57.61478,-30.25165],[-57.64859,-30.35095],[-57.89115,-30.49572],[-57.8024,-30.77193],[-57.89476,-30.95994],[-57.86729,-31.06352],[-57.9908,-31.34924],[-57.98127,-31.3872],[-58.07569,-31.44916],[-58.0023,-31.53084],[-58.00076,-31.65016],[-58.20252,-31.86966],[-58.10036,-32.25338],[-58.22362,-32.52416],[-58.1224,-32.98842],[-58.40475,-33.11777],[-58.44442,-33.84033]]]}},{"type":"Feature","properties":{"id":"VA"},"geometry":{"type":"Polygon","coordinates":[[[12.44582,41.90194],[12.44834,41.90095],[12.45181,41.90056],[12.45446,41.90028],[12.45435,41.90143],[12.45626,41.90172],[12.45691,41.90125],[12.4577,41.90115],[12.45834,41.90174],[12.45826,41.90281],[12.45755,41.9033],[12.45762,41.9058],[12.45561,41.90629],[12.45543,41.90738],[12.45091,41.90625],[12.44984,41.90545],[12.44815,41.90326],[12.44582,41.90194]]]}},{"type":"Feature","properties":{"id":"VE"},"geometry":{"type":"Polygon","coordinates":[[[-73.36905,9.16636],[-73.00792,9.28612],[-72.94052,9.10663],[-72.77415,9.10165],[-72.65415,8.61456],[-72.4042,8.36513],[-72.36987,8.19976],[-72.34934,8.00636],[-72.39137,8.03534],[-72.41415,8.03432],[-72.4205,7.99089],[-72.43577,7.99132],[-72.44689,7.96828],[-72.47277,7.96144],[-72.48801,7.94329],[-72.48435,7.93139],[-72.47042,7.92306],[-72.45732,7.91031],[-72.4617,7.90376],[-72.44539,7.85845],[-72.45028,7.81874],[-72.46763,7.79518],[-72.47827,7.65604],[-72.45321,7.57232],[-72.47415,7.48928],[-72.43132,7.40034],[-72.19437,7.37034],[-72.04895,7.03837],[-71.94053,7.0169],[-71.82441,7.04314],[-71.62536,7.05387],[-71.45662,7.01375],[-71.43516,7.02699],[-71.42924,7.037],[-71.39173,7.03632],[-71.37234,7.01588],[-71.02128,6.97942],[-70.85374,7.07414],[-70.78469,7.08219],[-70.7596,7.09799],[-70.75118,7.09297],[-70.68251,7.0962],[-70.10716,6.96516],[-69.42672,6.10641],[-67.60654,6.2891],[-67.44987,6.1938],[-67.49107,6.13987],[-67.4176,6.00024],[-67.58558,5.84537],[-67.63914,5.64963],[-67.59141,5.5369],[-67.84984,5.30737],[-67.85358,4.53249],[-67.79285,4.22247],[-67.61467,3.75086],[-67.50067,3.75812],[-67.30945,3.38393],[-67.85911,2.85011],[-67.85862,2.79173],[-67.7053,2.81068],[-67.25349,2.41902],[-67.06466,1.91709],[-66.85051,1.22968],[-66.30249,0.74361],[-65.6727,1.01353],[-65.50158,0.92086],[-65.57288,0.62856],[-65.11657,1.12046],[-64.38932,1.5125],[-64.34654,1.35569],[-64.08274,1.64792],[-64.06135,1.94722],[-63.39827,2.16098],[-63.39114,2.4317],[-64.0257,2.48156],[-64.02908,2.79797],[-64.16839,3.52575],[-64.70123,3.99852],[-64.84028,4.24665],[-64.74414,4.28068],[-64.61814,4.12591],[-64.14512,4.12932],[-63.99183,3.90172],[-63.86082,3.94796],[-63.70218,3.91417],[-63.67099,4.01731],[-63.50611,3.83592],[-63.42233,3.89995],[-63.4464,3.9693],[-63.21111,3.96219],[-62.98296,3.59935],[-62.7655,3.73099],[-62.74411,4.03331],[-62.57656,4.04754],[-62.44822,4.18621],[-62.13094,4.08309],[-61.54629,4.2822],[-61.48569,4.43149],[-61.29675,4.44216],[-61.31457,4.54167],[-61.18835,4.52063],[-61.15299,4.49599],[-61.14758,4.48093],[-61.11548,4.5155],[-60.96399,4.5457],[-60.86539,4.70512],[-60.5802,4.94312],[-60.73204,5.20931],[-61.3883,5.94689],[-61.13324,6.20035],[-61.20762,6.58174],[-61.14767,6.70706],[-61.08943,6.70386],[-61.07909,6.72828],[-60.54873,6.8631],[-60.39419,6.94847],[-60.28074,7.1162],[-60.44116,7.20817],[-60.54098,7.14804],[-60.63367,7.25061],[-60.59802,7.33194],[-60.71923,7.55817],[-60.64793,7.56877],[-60.51959,7.83373],[-60.38056,7.8302],[-60.02407,8.04557],[-59.97059,8.20791],[-59.83156,8.23261],[-59.80661,8.28906],[-59.85562,8.35213],[-59.99771,8.54639],[-59.54058,8.6862],[-60.993,9.88751],[-62.08693,10.04435],[-61.70744,10.98489],[-63.73917,11.92623],[-63.25348,15.97077],[-63.85239,16.0256],[-65.4181,12.36848],[-67.89186,12.4116],[-68.01417,11.77722],[-68.33524,11.78151],[-68.99639,11.79035],[-69.4514,12.18025],[-70.24399,12.38063],[-70.34259,12.92535],[-71.19849,12.65801],[-70.92579,11.96275],[-71.3275,11.85],[-71.97212,11.64719],[-72.24983,11.14138],[-72.4767,11.1117],[-72.88002,10.44309],[-72.9499,9.85183],[-73.36905,9.16636]]]}},{"type":"Feature","properties":{"id":"VN"},"geometry":{"type":"Polygon","coordinates":[[[102.14099,22.40092],[102.18712,22.30403],[102.51734,22.02676],[102.49092,21.99002],[102.62301,21.91447],[102.67145,21.65894],[102.74189,21.66713],[102.82115,21.73667],[102.81894,21.83888],[102.85637,21.84501],[102.86077,21.71213],[102.97965,21.74076],[102.98846,21.58936],[102.86297,21.4255],[102.94223,21.46034],[102.88939,21.3107],[102.80794,21.25736],[102.89825,21.24707],[102.97745,21.05821],[103.03469,21.05821],[103.12055,20.89994],[103.21497,20.89832],[103.232,20.83442],[103.4004,20.77986],[103.46477,20.82672],[103.68235,20.66177],[103.73478,20.6669],[103.73539,20.73123],[103.77428,20.73074],[103.80912,20.84982],[103.98053,20.9078],[104.10541,20.97386],[104.13579,20.94789],[104.21888,20.93827],[104.25853,20.89818],[104.29286,20.92079],[104.32763,20.858],[104.42873,20.7907],[104.4465,20.79744],[104.48161,20.7659],[104.49053,20.72609],[104.52341,20.69743],[104.55516,20.71902],[104.64597,20.65848],[104.48907,20.5325],[104.38024,20.4751],[104.40917,20.38252],[104.47886,20.37459],[104.65773,20.47558],[104.72102,20.40554],[104.62195,20.36633],[104.62142,20.29665],[104.61035,20.2452],[104.86852,20.14121],[104.91695,20.15567],[104.99221,20.09567],[104.8465,19.91783],[104.8355,19.80395],[104.68359,19.72729],[104.64837,19.62365],[104.53169,19.61743],[104.41281,19.70035],[104.2,19.69221],[104.06498,19.66926],[104.05617,19.61743],[104.10832,19.51575],[104.06058,19.43484],[103.87125,19.31854],[104.5361,18.97747],[104.64617,18.85668],[105.12829,18.70453],[105.19654,18.64196],[105.1327,18.58355],[105.10408,18.43533],[105.15942,18.38691],[105.38366,18.15315],[105.46292,18.22008],[105.64784,17.96687],[105.60381,17.89356],[105.76612,17.67147],[105.85744,17.63221],[106.09019,17.36399],[106.18991,17.28227],[106.24444,17.24714],[106.29287,17.3018],[106.31929,17.20509],[106.43383,17.00491],[106.50862,16.9673],[106.55045,17.0031],[106.54824,16.92729],[106.51963,16.92097],[106.51485,16.89243],[106.55265,16.86831],[106.55691,16.6477],[106.59013,16.62259],[106.58267,16.6012],[106.65081,16.59144],[106.64823,16.54324],[106.65832,16.47816],[106.74418,16.41904],[106.84104,16.55415],[106.88727,16.52671],[106.88067,16.43594],[106.96638,16.34938],[106.97385,16.30204],[107.02597,16.31132],[107.09091,16.3092],[107.15035,16.26271],[107.14595,16.17816],[107.25822,16.13587],[107.33968,16.05549],[107.44975,16.08511],[107.46296,16.01106],[107.39471,15.88829],[107.34188,15.89464],[107.21419,15.83747],[107.21859,15.74638],[107.27143,15.71459],[107.27583,15.62769],[107.34408,15.62345],[107.3815,15.49832],[107.50699,15.48771],[107.53341,15.40496],[107.62367,15.42193],[107.60605,15.37524],[107.62587,15.2266],[107.58844,15.20111],[107.61926,15.13949],[107.61486,15.0566],[107.46516,15.00982],[107.48277,14.93751],[107.59285,14.87795],[107.51579,14.79282],[107.54361,14.69092],[107.55371,14.628],[107.52102,14.59034],[107.52569,14.54665],[107.48521,14.40346],[107.44941,14.41552],[107.39493,14.32655],[107.40427,14.24509],[107.33577,14.11832],[107.37158,14.07906],[107.35757,14.02319],[107.38247,13.99147],[107.44318,13.99751],[107.46498,13.91593],[107.45252,13.78897],[107.53503,13.73908],[107.61909,13.52577],[107.62843,13.3668],[107.49144,13.01215],[107.49611,12.88926],[107.55993,12.7982],[107.5755,12.52177],[107.54447,12.35744],[107.4463,12.29373],[107.42917,12.24657],[107.34511,12.33327],[107.15831,12.27547],[106.99953,12.08983],[106.92325,12.06548],[106.79405,12.0807],[106.7126,11.97031],[106.4111,11.97413],[106.4687,11.86751],[106.44068,11.86294],[106.44535,11.8279],[106.41577,11.76999],[106.45158,11.68616],[106.44691,11.66787],[106.37219,11.69836],[106.30525,11.67549],[106.26478,11.72122],[106.18539,11.75171],[106.13158,11.73283],[106.06708,11.77761],[106.02038,11.77457],[106.00792,11.7197],[105.95188,11.63738],[105.88962,11.67854],[105.8507,11.66635],[105.80867,11.60536],[105.81645,11.56876],[105.87328,11.55953],[105.88962,11.43605],[105.86782,11.28343],[106.10444,11.07879],[106.1527,11.10476],[106.1757,11.07301],[106.20397,10.97557],[106.15453,10.98096],[106.14166,10.91607],[106.19247,10.79519],[106.06708,10.8098],[105.94535,10.9168],[105.93403,10.83853],[105.8464,10.86272],[105.86376,10.89839],[105.77533,11.03808],[105.66015,10.98602],[105.49836,10.95324],[105.42832,10.9743],[105.34011,10.86179],[105.24404,10.90647],[105.10285,10.92121],[105.11795,10.94473],[105.11778,10.96209],[105.08326,10.95656],[105.02722,10.89236],[105.06431,10.79182],[105.10345,10.72268],[104.95762,10.64053],[104.87933,10.52833],[104.59267,10.53296],[104.49869,10.4057],[104.47963,10.43046],[104.43778,10.42386],[103.99198,10.48391],[102.47649,9.66162],[104.81582,8.03101],[109.55486,8.10026],[110.2534,15.19951],[107.44022,18.66249],[108.26073,20.07614],[108.10003,21.47338],[108.0569,21.53604],[108.03212,21.54782],[107.97932,21.54503],[107.97539,21.53955],[107.97074,21.54072],[107.96745,21.53604],[107.95341,21.53756],[107.94264,21.56522],[107.92397,21.58936],[107.89814,21.59072],[107.8766,21.64043],[107.8593,21.65427],[107.8387,21.64314],[107.8257,21.65355],[107.80755,21.6591],[107.72094,21.62751],[107.71674,21.62188],[107.67674,21.60808],[107.59541,21.60417],[107.58254,21.61697],[107.56005,21.61306],[107.54173,21.5904],[107.5001,21.59547],[107.48268,21.59862],[107.49658,21.61322],[107.48637,21.64362],[107.47197,21.6672],[107.41593,21.64839],[107.38636,21.59774],[107.35989,21.60063],[107.35834,21.6672],[107.29728,21.74586],[107.24625,21.7077],[107.20734,21.71493],[107.10631,21.79855],[107.02558,21.81969],[107.00348,21.84556],[107.06101,21.88982],[107.05634,21.92303],[106.99252,21.95191],[106.97387,21.92282],[106.9295,21.93197],[106.9178,21.97357],[106.81217,21.97385],[106.77706,22.00672],[106.74671,22.00817],[106.71887,21.97421],[106.70913,21.97369],[106.69381,21.95721],[106.68274,21.99811],[106.70917,22.0255],[106.71243,22.09375],[106.69389,22.13295],[106.67029,22.18565],[106.69535,22.20647],[106.70239,22.22014],[106.6516,22.33977],[106.55605,22.34309],[106.58523,22.38039],[106.55794,22.4637],[106.5721,22.47655],[106.57592,22.46945],[106.58502,22.47552],[106.58188,22.51842],[106.60875,22.60481],[106.65316,22.5757],[106.71698,22.58432],[106.72582,22.63587],[106.76293,22.73491],[106.82404,22.7881],[106.83766,22.80672],[106.81271,22.8226],[106.7817,22.81455],[106.70702,22.86708],[106.71387,22.88296],[106.6734,22.89587],[106.6516,22.86862],[106.60179,22.92884],[106.56274,22.92068],[106.53631,22.94242],[106.51306,22.94891],[106.49749,22.91164],[106.37632,22.88273],[106.34817,22.85695],[106.32568,22.87451],[106.30868,22.86399],[106.26869,22.87459],[106.20088,22.98557],[106.00622,22.99268],[105.99815,22.94116],[105.89498,22.93815],[105.87558,22.91887],[105.72401,23.06425],[105.56247,23.07073],[105.56505,23.15961],[105.54822,23.19259],[105.5211,23.18612],[105.48986,23.22983],[105.43767,23.303],[105.42411,23.28219],[105.37905,23.31128],[105.32376,23.39684],[105.30807,23.38196],[105.27996,23.3419],[105.24971,23.33555],[105.25846,23.31813],[105.24078,23.28826],[105.23357,23.26035],[105.17267,23.28826],[105.11281,23.24686],[105.07002,23.26248],[104.99547,23.20277],[104.96509,23.20182],[104.9438,23.15235],[104.90638,23.18084],[104.88072,23.16585],[104.87497,23.12915],[104.79892,23.1192],[104.8334,23.01484],[104.86765,22.95178],[104.84942,22.93631],[104.77114,22.90017],[104.72828,22.81906],[104.65507,22.83797],[104.60134,22.81637],[104.58005,22.8564],[104.47225,22.75813],[104.41371,22.73249],[104.39835,22.70161],[104.36797,22.68696],[104.3532,22.69369],[104.33947,22.72172],[104.27201,22.74539],[104.25683,22.76534],[104.27084,22.8457],[104.14155,22.81083],[104.12116,22.81261],[104.04108,22.72647],[104.00705,22.54216],[104.01147,22.52385],[104.00666,22.5187],[103.99247,22.51958],[103.97499,22.50609],[103.9692,22.51243],[103.96352,22.50584],[103.95191,22.5134],[103.94513,22.52553],[103.93286,22.52703],[103.87667,22.56598],[103.64175,22.79881],[103.56056,22.69884],[103.57812,22.65764],[103.52675,22.59155],[103.43646,22.70648],[103.43179,22.75816],[103.33602,22.80933],[103.28933,22.7366],[103.28079,22.68063],[103.18513,22.64498],[103.16604,22.60913],[103.15741,22.59463],[103.17961,22.55705],[103.07843,22.50097],[103.0722,22.44775],[102.9321,22.48659],[102.8636,22.60735],[102.60675,22.73376],[102.57095,22.7036],[102.51802,22.77969],[102.46665,22.77108],[102.42618,22.69212],[102.38415,22.67919],[102.41061,22.64184],[102.25339,22.4607],[102.26428,22.41321],[102.16621,22.43336],[102.14099,22.40092]]]}},{"type":"Feature","properties":{"id":"YE"},"geometry":{"type":"Polygon","coordinates":[[[41.29956,15.565],[42.63806,13.58268],[43.29075,12.79154],[43.32909,12.59711],[43.90659,12.3823],[43.42013,11.71655],[51.12877,12.56479],[52.253,11.68582],[55.69862,12.12478],[53.32998,16.16312],[53.09917,16.67084],[52.81185,17.28568],[52.74267,17.29519],[52.78009,17.35124],[52.00311,19.00083],[50.774,18.78866],[49.12261,18.62021],[48.18534,18.17129],[47.59551,17.45416],[47.46642,17.11716],[47.18215,16.94909],[47.00571,16.94765],[46.75231,17.29033],[46.36504,17.23656],[44.56878,17.45219],[43.68198,17.3621],[43.43496,17.56482],[43.28029,17.51719],[43.22768,17.38569],[43.33514,17.30516],[43.20156,17.25901],[43.17515,17.13012],[43.25351,17.0169],[43.18013,17.02995],[43.1813,16.98438],[43.19523,16.94458],[43.13567,16.92093],[43.13936,16.90188],[43.15215,16.87966],[43.18338,16.84852],[43.22012,16.83932],[43.23257,16.80692],[43.24801,16.80613],[43.26149,16.80084],[43.253,16.76871],[43.26647,16.7429],[43.22545,16.74315],[43.21085,16.70377],[43.23815,16.63997],[43.13575,16.67475],[43.14039,16.60033],[43.11601,16.53166],[42.97215,16.51093],[42.94351,16.49467],[42.94625,16.39721],[42.76801,16.40371],[42.15205,16.40211],[41.37609,16.19728],[41.29956,15.565]]]}},{"type":"Feature","properties":{"id":"ZM"},"geometry":{"type":"Polygon","coordinates":[[[21.97988,-13.00148],[22.00323,-16.18028],[22.17217,-16.50269],[23.20038,-17.47563],[23.47474,-17.62877],[24.23619,-17.47489],[24.32811,-17.49082],[24.38712,-17.46818],[24.5621,-17.52963],[24.70864,-17.49501],[25.00198,-17.58221],[25.26433,-17.79571],[25.51646,-17.86232],[25.6827,-17.81987],[25.85738,-17.91403],[25.85892,-17.97726],[26.08925,-17.98168],[26.0908,-17.93021],[26.21601,-17.88608],[26.55918,-17.99638],[26.68403,-18.07411],[26.74314,-18.0199],[26.89926,-17.98756],[27.14196,-17.81398],[27.30736,-17.60487],[27.61377,-17.34378],[27.62795,-17.24365],[27.83141,-16.96274],[28.73725,-16.5528],[28.76199,-16.51575],[28.81454,-16.48611],[28.8501,-16.04537],[28.9243,-15.93987],[29.01298,-15.93805],[29.21955,-15.76589],[29.4437,-15.68702],[29.8317,-15.6126],[30.35574,-15.6513],[30.41902,-15.62269],[30.21995,-14.98259],[31.4978,-14.60916],[31.95304,-14.41373],[32.18959,-14.3394],[32.45429,-14.28368],[32.69325,-14.18551],[33.24128,-13.99861],[33.20651,-14.00186],[33.19055,-13.97954],[33.18643,-13.95847],[33.15399,-13.94073],[33.12832,-13.95822],[33.09837,-13.96355],[33.03417,-14.06298],[32.98782,-13.93281],[32.94439,-13.94997],[32.90551,-13.85533],[32.87117,-13.79982],[32.80431,-13.80024],[32.76962,-13.77224],[32.79594,-13.75164],[32.80526,-13.73521],[32.84697,-13.72191],[32.78314,-13.64279],[32.67881,-13.634],[32.68604,-13.56473],[32.73771,-13.58592],[32.84176,-13.52794],[32.84517,-13.46308],[32.87899,-13.46625],[32.94507,-13.28088],[33.01374,-13.20953],[32.98353,-13.14685],[33.02181,-12.88707],[32.96733,-12.88251],[32.94397,-12.76868],[33.05917,-12.59554],[33.18837,-12.61377],[33.28177,-12.54692],[33.37517,-12.54085],[33.54709,-12.36347],[33.47636,-12.32498],[33.3705,-12.34931],[33.25998,-12.14242],[33.32479,-11.91841],[33.30574,-11.59035],[33.24252,-11.59302],[33.23663,-11.40637],[33.28737,-11.43762],[33.29267,-11.3789],[33.40393,-11.15633],[33.25998,-10.88862],[33.29355,-10.83583],[33.48049,-10.79469],[33.70675,-10.56896],[33.54797,-10.36077],[33.53863,-10.20148],[33.31297,-10.05133],[33.38762,-9.90882],[33.38075,-9.86603],[33.3653,-9.84506],[33.35929,-9.81546],[33.32565,-9.81766],[33.29672,-9.79288],[33.29938,-9.78578],[33.27587,-9.75922],[33.24334,-9.70838],[33.23776,-9.69044],[33.23827,-9.6637],[33.2095,-9.61099],[33.12,-9.59474],[33.09751,-9.68672],[33.06309,-9.61699],[33.00256,-9.63053],[32.98713,-9.5812],[33.01408,-9.50942],[32.93486,-9.43111],[32.95389,-9.40138],[32.80946,-9.34287],[32.79041,-9.32195],[32.76543,-9.32314],[32.75611,-9.28583],[32.71891,-9.27723],[32.64844,-9.27528],[32.53137,-9.23597],[32.49147,-9.14754],[32.43543,-9.11988],[32.25486,-9.13371],[32.16146,-9.05993],[32.08206,-9.04609],[31.98866,-9.07069],[31.94196,-9.02303],[31.94663,-8.93846],[31.81587,-8.88618],[31.7007,-8.91797],[31.57147,-8.81388],[31.57147,-8.70619],[31.45952,-8.67453],[31.37533,-8.60769],[31.0065,-8.571],[30.80326,-8.29201],[30.41702,-8.2774],[28.96339,-8.4632],[28.38526,-9.23393],[28.36562,-9.30091],[28.52636,-9.35379],[28.51627,-9.44726],[28.56208,-9.49122],[28.68532,-9.78],[28.62144,-9.93909],[28.65032,-10.65133],[28.37241,-11.57848],[28.49063,-11.87507],[28.73319,-11.98357],[29.0736,-12.36314],[29.52575,-12.45769],[29.48404,-12.23604],[29.8139,-12.14898],[29.81551,-13.44683],[29.63527,-13.43403],[29.60918,-13.20886],[29.00356,-13.42001],[28.81885,-12.98682],[28.64067,-12.78903],[28.57131,-12.9005],[28.45355,-12.71704],[28.53149,-12.63464],[28.34043,-12.43322],[27.94921,-12.35073],[27.92827,-12.24372],[27.8124,-12.21956],[27.81635,-12.25395],[27.77137,-12.27224],[27.76296,-12.29287],[27.6234,-12.26955],[27.21914,-11.75073],[27.22541,-11.60323],[27.04351,-11.61312],[26.87736,-11.99297],[26.01777,-11.91488],[25.33058,-11.65767],[25.34069,-11.19707],[24.42612,-11.44975],[24.34528,-11.06816],[24.00027,-10.89356],[24.02603,-11.15368],[23.98804,-12.13149],[24.06672,-12.29058],[23.90937,-12.844],[24.03339,-12.99091],[21.97988,-13.00148]]]}},{"type":"Feature","properties":{"id":"ZW"},"geometry":{"type":"Polygon","coordinates":[[[25.23909,-17.90832],[25.31799,-18.07091],[25.39972,-18.12691],[25.53465,-18.39041],[25.68859,-18.56165],[25.79217,-18.6355],[25.82353,-18.82808],[25.94326,-18.90362],[25.99837,-19.02943],[25.96226,-19.08152],[26.17227,-19.53709],[26.72246,-19.92707],[27.21278,-20.08244],[27.29831,-20.28935],[27.28643,-20.48523],[27.69361,-20.48531],[27.72972,-20.51735],[27.69241,-21.06559],[27.91407,-21.31621],[28.01669,-21.57624],[28.29416,-21.59037],[28.49942,-21.66634],[28.58114,-21.63455],[29.07763,-21.81877],[29.04023,-21.85864],[29.02191,-21.90647],[29.02191,-21.95665],[29.04108,-22.00563],[29.08495,-22.04867],[29.14501,-22.07275],[29.1974,-22.07472],[29.24648,-22.05967],[29.3533,-22.18363],[29.37364,-22.1957],[29.64609,-22.12917],[29.76848,-22.14128],[29.92242,-22.19408],[30.13147,-22.30841],[30.2265,-22.2961],[30.28351,-22.35587],[30.382,-22.34944],[30.48686,-22.31368],[30.6294,-22.32599],[30.86696,-22.28907],[31.08932,-22.34884],[31.16344,-22.32599],[31.30743,-22.42308],[31.37597,-22.38188],[32.41001,-21.31656],[32.4785,-21.32791],[32.37115,-21.133],[32.51644,-20.91929],[32.48122,-20.63319],[32.56656,-20.55436],[32.66767,-20.55597],[32.86766,-20.26944],[32.86388,-20.1599],[32.93907,-20.04077],[33.02747,-20.03174],[33.06461,-19.77787],[32.95013,-19.67219],[32.84666,-19.68462],[32.84446,-19.48343],[32.78282,-19.47513],[32.77633,-19.36313],[32.85107,-19.29238],[32.87088,-19.09279],[32.84006,-19.0262],[32.71882,-19.02528],[32.69977,-18.94054],[32.73439,-18.92628],[32.70137,-18.84712],[32.82465,-18.77419],[32.9017,-18.7992],[32.95323,-18.68641],[32.88629,-18.58023],[32.88629,-18.51344],[33.02278,-18.4696],[33.05511,-18.35257],[32.94133,-17.99705],[33.0492,-17.60298],[32.98536,-17.55891],[32.96554,-17.48964],[33.04944,-17.33507],[32.99829,-17.30573],[32.98902,-17.1831],[32.84113,-16.92259],[32.91051,-16.89446],[32.9861,-16.70427],[32.80569,-16.70788],[32.70441,-16.68043],[32.71017,-16.59932],[32.42838,-16.4727],[32.28529,-16.43892],[32.02772,-16.43892],[31.91324,-16.41569],[31.90223,-16.34388],[31.71169,-16.20165],[31.42451,-16.15154],[31.30563,-16.01193],[31.1331,-15.98459],[30.97761,-16.05848],[30.91597,-15.99924],[30.42568,-15.9962],[30.41902,-15.62269],[30.35574,-15.6513],[29.8317,-15.6126],[29.4437,-15.68702],[29.21955,-15.76589],[29.01298,-15.93805],[28.9243,-15.93987],[28.8501,-16.04537],[28.81454,-16.48611],[28.76199,-16.51575],[28.73725,-16.5528],[27.83141,-16.96274],[27.62795,-17.24365],[27.61377,-17.34378],[27.30736,-17.60487],[27.14196,-17.81398],[26.89926,-17.98756],[26.74314,-18.0199],[26.68403,-18.07411],[26.55918,-17.99638],[26.21601,-17.88608],[26.0908,-17.93021],[26.08925,-17.98168],[25.85892,-17.97726],[25.85738,-17.91403],[25.6827,-17.81987],[25.51646,-17.86232],[25.26433,-17.79571],[25.23909,-17.90832]]]}},{"type":"Feature","properties":{"id":"US-AK"},"geometry":{"type":"Polygon","coordinates":[[[-179.55295,50.81807],[-133.92876,54.62289],[-130.61931,54.70835],[-130.65082,54.7709],[-130.53591,54.80215],[-130.27203,54.97174],[-130.18765,55.07744],[-130.08035,55.21556],[-129.97513,55.28029],[-130.15373,55.74895],[-130.00857,55.91344],[-130.00093,56.00325],[-130.10173,56.12178],[-130.33965,56.10849],[-130.77769,56.36185],[-131.8271,56.62247],[-133.38523,58.42773],[-133.84645,58.73543],[-134.27175,58.8634],[-134.49912,59.12103],[-134.7047,59.2458],[-135.00267,59.28745],[-135.03069,59.56208],[-135.48007,59.79937],[-136.31566,59.59083],[-136.22381,59.55526],[-136.33727,59.44466],[-136.47323,59.46617],[-136.52365,59.16752],[-136.82619,59.16198],[-137.4925,58.89415],[-137.60623,59.24465],[-138.62145,59.76431],[-138.71149,59.90728],[-139.05365,59.99655],[-139.20603,60.08896],[-139.05831,60.35205],[-139.68991,60.33693],[-139.98024,60.18027],[-140.45648,60.30919],[-140.5227,60.22077],[-141.00116,60.30648],[-140.97259,70.50112],[-140.97259,70.50112],[-156.4893,71.58054],[-168.65315,71.12923],[-168.9812,65.50181],[-172.76104,63.77445],[-179.55295,57.62081],[-179.55295,50.81807]]]}},{"type":"Feature","properties":{"id":"GL"},"geometry":{"type":"Polygon","coordinates":[[[-74.12379,76.56358],[-58.9307,74.79891],[-53.68108,62.9266],[-44.85369,58.6179],[-20.82201,70.31885],[-8.70622,81.30703],[-30.36949,84.23527],[-59.93819,82.31398],[-66.45595,80.82603],[-67.1326,80.75493],[-73.91222,78.42484],[-74.12379,76.56358]]]}},{"type":"Feature","properties":{"id":"LK"},"geometry":{"type":"Polygon","coordinates":[[[79.37385,8.98767],[79.9245,5.46963],[82.74996,6.54691],[80.42924,10.17542],[79.42124,9.80115],[79.45362,9.159],[79.37385,8.98767]]]}},{"type":"Feature","properties":{"id":"IS"},"geometry":{"type":"Polygon","coordinates":[[[-24.97875,67.37356],[-24.85634,62.99867],[-12.70155,63.05877],[-12.82396,67.4245],[-24.97875,67.37356]]]}},{"type":"Feature","properties":{"id":"BS"},"geometry":{"type":"Polygon","coordinates":[[[-80.16442,23.44484],[-73.62304,20.6935],[-72.94479,20.79216],[-72.41726,22.40371],[-76.80329,26.86841],[-78.4311,27.53866],[-79.36558,27.02964],[-80.16442,23.44484]]]}},{"type":"Feature","properties":{"id":"VI"},"geometry":{"type":"Polygon","coordinates":[[[-65.27974,17.56928],[-64.35558,17.48384],[-64.646,18.10286],[-64.64067,18.36478],[-64.86049,18.39954],[-65.02435,18.73231],[-65.27974,17.56928]]]}},{"type":"Feature","properties":{"id":"MV"},"geometry":{"type":"Polygon","coordinates":[[[72.35455,7.23086],[72.86548,-0.88075],[73.89733,-0.84288],[73.95999,7.24798],[72.35455,7.23086]]]}},{"type":"Feature","properties":{"id":"IO"},"geometry":{"type":"Polygon","coordinates":[[[70.64391,-7.71751],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[73.19979,-4.96078],[70.64754,-4.95745],[70.64391,-7.71751]]]}},{"type":"Feature","properties":{"id":"RE"},"geometry":{"type":"Polygon","coordinates":[[[55.04586,-20.69416],[55.06684,-21.59486],[56.6872,-21.56213],[56.66623,-20.66123],[55.04586,-20.69416]]]}},{"type":"Feature","properties":{"id":"MU"},"geometry":{"type":"Polygon","coordinates":[[[56.35572,-9.61359],[56.66623,-20.66123],[56.6872,-21.56213],[62.98922,-20.11596],[63.91009,-19.90996],[63.32998,-19.13235],[56.35572,-9.61359]]]}},{"type":"Feature","properties":{"id":"SC"},"geometry":{"type":"Polygon","coordinates":[[[45.39948,-9.09441],[46.52682,-10.83678],[48.86266,-10.8109],[51.51407,-10.78153],[57.144,-7.697],[56.94974,-2.9998],[53.06458,-3.53165],[45.39948,-9.09441]]]}},{"type":"Feature","properties":{"id":"KM"},"geometry":{"type":"Polygon","coordinates":[[[42.73663,-11.99939],[43.36759,-12.38139],[43.85516,-12.6762],[44.38701,-12.99739],[44.95392,-12.10401],[44.42208,-11.78171],[43.9345,-11.48591],[43.30354,-11.10267],[42.73663,-11.99939]]]}},{"type":"Feature","properties":{"id":"FO"},"geometry":{"type":"Polygon","coordinates":[[[-8.51774,62.35338],[-6.51083,60.95272],[-5.70102,62.77194],[-8.51774,62.35338]]]}},{"type":"Feature","properties":{"id":"PM"},"geometry":{"type":"Polygon","coordinates":[[[-56.70773,46.51478],[-55.8643,46.64935],[-56.25228,47.31192],[-56.67989,47.3339],[-56.70773,46.51478]]]}},{"type":"Feature","properties":{"id":"JP"},"geometry":{"type":"Polygon","coordinates":[[[122.50978,24.21247],[136.0511,20.05526],[155.16731,23.60141],[145.97944,43.07828],[145.76215,43.50342],[145.23667,43.76813],[145.82343,44.571],[140.9182,45.92937],[137.30539,38.21716],[129.2669,34.87122],[122.50978,24.21247]]]}},{"type":"Feature","properties":{"id":"TV"},"geometry":{"type":"Polygon","coordinates":[[[174,-11.5],[180,-11.5],[180,-5],[174,-5],[174,-11.5]]]}},{"type":"Feature","properties":{"id":"VU"},"geometry":{"type":"Polygon","coordinates":[[[162.93363,-17.28904],[173.07304,-22.54607],[168.14096,-12.74443],[165.31108,-12.67903],[162.93363,-17.28904]]]}},{"type":"Feature","properties":{"id":"SB"},"geometry":{"type":"Polygon","coordinates":[[[154.74815,-7.33315],[160.37269,-13.44534],[165.31108,-12.67903],[168.14096,-12.74443],[171.12712,-12.81344],[171.21374,-9.22564],[159.32766,-4.77078],[157.60997,-5.69776],[156.07484,-6.52458],[155.94342,-6.85209],[155.29973,-7.02898],[154.74815,-7.33315]]]}},{"type":"Feature","properties":{"id":"MP"},"geometry":{"type":"Polygon","coordinates":[[[143.82485,13.92273],[146.25931,13.85876],[146.6755,21.00809],[144.18594,21.03576],[143.82485,13.92273]]]}},{"type":"Feature","properties":{"id":"MH"},"geometry":{"type":"Polygon","coordinates":[[[159.04653,10.59067],[161.58988,8.89263],[165.35175,6.367],[169,3.9],[173.53711,5.70687],[169.29099,15.77133],[159.04653,10.59067]]]}},{"type":"Feature","properties":{"id":"FM"},"geometry":{"type":"Polygon","coordinates":[[[136.04605,12.45908],[136.27107,6.73747],[148.49862,1.9204],[154.49668,-0.44964],[156.88247,-1.39237],[161.87397,3.18678],[165.35175,6.367],[161.58988,8.89263],[159.04653,10.59067],[153.83475,11.01511],[148.42679,11.45488],[136.04605,12.45908]]]}},{"type":"Feature","properties":{"id":"MY"},"geometry":{"type":"Polygon","coordinates":[[[99.31854,5.99868],[99.75778,3.86466],[103.03657,1.30383],[103.55749,1.19701],[103.67107,1.4296],[103.7219,1.46108],[103.75227,1.44239],[103.81575,1.48124],[103.89565,1.42841],[104.00131,1.42405],[104.03341,1.45684],[104.09815,1.41191],[104.03527,1.2689],[104.34728,1.33529],[104.56723,1.44271],[105.43734,3.24387],[108.0658,5.08493],[109.6941,2.68827],[109.64506,2.08014],[109.62558,1.99182],[109.53794,1.91771],[109.57923,1.80624],[109.66397,1.79972],[109.66397,1.60425],[109.85692,1.43545],[109.95134,1.4164],[109.97503,1.3227],[110.35354,0.98869],[110.49182,0.88088],[110.62374,0.873],[111.22979,1.08326],[111.53491,0.95902],[111.66847,1.0378],[111.82846,0.99349],[111.94553,1.12016],[112.15679,1.17004],[112.2127,1.44135],[112.48648,1.56516],[113.021,1.57819],[113.01448,1.42832],[113.64677,1.23933],[114.03788,1.44787],[114.57892,1.5],[114.80706,1.92351],[114.80706,2.21665],[115.1721,2.49671],[115.11343,2.82879],[115.53713,3.14776],[115.59642,3.91666],[115.90217,4.37708],[116.14265,4.33888],[117.25801,4.35108],[117.48789,4.16597],[117.89538,4.16637],[118.42088,3.91562],[119.01488,4.00674],[119.00684,4.70611],[119.58617,5.27831],[117.97393,6.26069],[117.43832,7.3895],[117.17735,7.52841],[116.79524,7.43869],[114.82635,5.58592],[115.16236,5.01011],[115.15092,4.87604],[115.20737,4.8256],[115.27819,4.63661],[115.2851,4.42295],[115.36346,4.33563],[115.31275,4.30806],[115.09978,4.39123],[115.07737,4.53418],[115.04064,4.63706],[115.02977,4.73503],[115.03509,4.82124],[115.05964,4.87187],[115.00402,4.88949],[114.97346,4.80893],[114.86858,4.80961],[114.8266,4.75062],[114.77303,4.72871],[114.83189,4.42387],[114.88039,4.4257],[114.78539,4.12205],[114.64211,4.00694],[114.49922,4.13108],[114.4416,4.27588],[114.32176,4.2552],[114.32176,4.34942],[114.26876,4.49878],[114.15813,4.57],[114.07589,4.58395],[114.07859,4.64657],[109.55486,8.10026],[104.81582,8.03101],[102.46318,7.22462],[102.09086,6.23546],[102.08127,6.22679],[102.078,6.19469],[102.08448,6.16257],[102.09148,6.14874],[102.05534,6.08149],[102.03376,6.07001],[102.01835,6.05407],[101.99209,6.04075],[101.97119,6.01961],[101.9714,6.00575],[101.94712,5.98421],[101.92411,5.91871],[101.9393,5.86561],[101.91776,5.84269],[101.88694,5.83845],[101.80144,5.74505],[101.75074,5.79091],[101.69773,5.75881],[101.58019,5.93534],[101.25524,5.78633],[101.25755,5.71065],[101.14062,5.61613],[101.09104,5.70396],[101.03593,5.74034],[100.98815,5.79464],[101.02708,5.91013],[101.087,5.9193],[101.12388,6.11411],[101.06165,6.14161],[101.12618,6.19431],[101.10313,6.25617],[100.85884,6.24929],[100.81045,6.45086],[100.74822,6.46231],[100.74361,6.50811],[100.66986,6.45086],[100.43027,6.52389],[100.42351,6.51762],[100.41791,6.5189],[100.41152,6.52299],[100.35413,6.54932],[100.31929,6.65413],[100.32607,6.65933],[100.32671,6.66526],[100.31884,6.66423],[100.31618,6.66781],[100.30828,6.66462],[100.29651,6.68439],[100.19511,6.72559],[100.12,6.42105],[100.0756,6.4045],[99.91873,6.50233],[99.50117,6.44501],[99.31854,5.99868]]]}},{"type":"Feature","properties":{"id":"ID"},"geometry":{"type":"Polygon","coordinates":[[[94.88572,6.34977],[95.14454,2.93297],[96.37521,1.33288],[103.58581,-7.54082],[109.90957,-8.16239],[110.77351,-8.43418],[115.13879,-9.59623],[122.91521,-11.65621],[125.68138,-9.85176],[125.08792,-9.46277],[125.06166,-9.36082],[124.99746,-9.28172],[124.98227,-9.19149],[125.05672,-9.17022],[125.09434,-9.19669],[125.17993,-9.16819],[125.18632,-9.03142],[125.11196,-8.96494],[125.0814,-9.01699],[124.95574,-9.06277],[124.95128,-8.9585],[124.94011,-8.85617],[124.48554,-9.12883],[124.46288,-9.30339],[124.38451,-9.36395],[124.35258,-9.43002],[124.3535,-9.48493],[124.28115,-9.50453],[124.28115,-9.42189],[124.21247,-9.36904],[124.14517,-9.42324],[124.10539,-9.41206],[124.03753,-9.33294],[125.62512,-8.11166],[127.49908,-8.26585],[127.55165,-9.05052],[137.03241,-8.98668],[139.41724,-8.97063],[140.88922,-9.34945],[141.00782,-9.1242],[141.01763,-6.90181],[140.85295,-6.72996],[140.99813,-6.3233],[140.9987,-5.17984],[140.99934,-3.92043],[141.00167,0.68547],[134.40878,1.79674],[128.97621,3.08804],[127.84676,3.66842],[126.69413,6.02692],[125.13682,4.79327],[124.35423,1.62576],[119.59714,1.47751],[118.42088,3.91562],[117.89538,4.16637],[117.48789,4.16597],[117.25801,4.35108],[116.14265,4.33888],[115.90217,4.37708],[115.59642,3.91666],[115.53713,3.14776],[115.11343,2.82879],[115.1721,2.49671],[114.80706,2.21665],[114.80706,1.92351],[114.57892,1.5],[114.03788,1.44787],[113.64677,1.23933],[113.01448,1.42832],[113.021,1.57819],[112.48648,1.56516],[112.2127,1.44135],[112.15679,1.17004],[111.94553,1.12016],[111.82846,0.99349],[111.66847,1.0378],[111.53491,0.95902],[111.22979,1.08326],[110.62374,0.873],[110.49182,0.88088],[110.35354,0.98869],[109.97503,1.3227],[109.95134,1.4164],[109.85692,1.43545],[109.66397,1.60425],[109.66397,1.79972],[109.57923,1.80624],[109.53794,1.91771],[109.62558,1.99182],[109.64506,2.08014],[109.6941,2.68827],[108.0658,5.08493],[105.43734,3.24387],[104.56723,1.44271],[104.34728,1.33529],[104.03527,1.2689],[103.75628,1.1424],[103.55749,1.19701],[103.03657,1.30383],[99.75778,3.86466],[98.6357,4.47525],[97.67511,5.454],[94.88572,6.34977]]]}},{"type":"Feature","properties":{"id":"PW"},"geometry":{"type":"Polygon","coordinates":[[[128.97621,3.08804],[134.40878,1.79674],[136.27107,6.73747],[136.04605,12.45908],[128.97621,3.08804]]]}},{"type":"Feature","properties":{"id":"PH"},"geometry":{"type":"Polygon","coordinates":[[[116.28201,8.2483],[116.79524,7.43869],[117.17735,7.52841],[117.43832,7.3895],[117.97393,6.26069],[119.58617,5.27831],[119.00684,4.70611],[119.01488,4.00674],[123.56322,6.55548],[125.13682,4.79327],[126.69413,6.02692],[127.43178,8.15605],[121.87695,21.78449],[116.28201,8.2483]]]}},{"type":"Feature","properties":{"id":"JE"},"geometry":{"type":"Polygon","coordinates":[[[-2.56423,49.22209],[-2.5234,48.91595],[-2.00491,48.86706],[-1.83944,49.23037],[-2.09454,49.46288],[-2.56423,49.22209]]]}},{"type":"Feature","properties":{"id":"GG"},"geometry":{"type":"Polygon","coordinates":[[[-3.06097,49.47231],[-2.9511,49.31141],[-2.56423,49.22209],[-2.09454,49.46288],[-2.02963,49.91866],[-2.39388,49.94612],[-3.06097,49.47231]]]}},{"type":"Feature","properties":{"id":"IM"},"geometry":{"type":"Polygon","coordinates":[[[-5.81146,53.88396],[-5.37267,53.63269],[-3.64906,54.12723],[-4.1819,54.57861],[-4.86305,54.44148],[-5.81146,53.88396]]]}},{"type":"Feature","properties":{"id":"AX"},"geometry":{"type":"Polygon","coordinates":[[[19.08191,60.19152],[20.5104,59.15546],[21.35468,59.67511],[21.02509,60.12142],[21.15143,60.54555],[20.96741,60.71528],[19.23413,60.61414],[19.08191,60.19152]]]}},{"type":"Feature","properties":{"id":"VG"},"geometry":{"type":"Polygon","coordinates":[[[-65.02435,18.73231],[-64.86049,18.39954],[-64.64067,18.36478],[-64.646,18.10286],[-63.95092,18.07976],[-63.90607,18.93262],[-64.62855,18.98678],[-65.02435,18.73231]]]}},{"type":"Feature","properties":{"id":"AW"},"geometry":{"type":"Polygon","coordinates":[[[-70.34259,12.92535],[-70.24399,12.38063],[-69.4514,12.18025],[-69.5195,12.75292],[-70.34259,12.92535]]]}},{"type":"Feature","properties":{"id":"CX"},"geometry":{"type":"Polygon","coordinates":[[[105.29647,-10.80676],[105.9699,-10.81333],[105.97626,-10.18257],[105.30283,-10.176],[105.29647,-10.80676]]]}},{"type":"Feature","properties":{"id":"CC"},"geometry":{"type":"Polygon","coordinates":[[[96.53724,-12.46709],[97.24513,-12.47233],[97.25212,-11.57036],[96.54423,-11.5651],[96.53724,-12.46709]]]}},{"type":"Feature","properties":{"id":"TK"},"geometry":{"type":"Polygon","coordinates":[[[-174.18707,-7.54408],[-174.17993,-10.13616],[-167.75195,-10.12005],[-167.75329,-7.52784],[-174.18707,-7.54408]]]}},{"type":"Feature","properties":{"id":"CK"},"geometry":{"type":"Polygon","coordinates":[[[-167.75329,-7.52784],[-167.75195,-10.12005],[-167.73854,-14.92809],[-167.73129,-23.22266],[-156.46451,-23.21255],[-156.4957,-12.32002],[-156.50903,-7.4975],[-167.75329,-7.52784]]]}},{"type":"Feature","properties":{"id":"FK"},"geometry":{"type":"Polygon","coordinates":[[[-62.38818,-51.70652],[-59.03327,-53.28879],[-56.99965,-51.29617],[-62.10074,-50.6407],[-62.38818,-51.70652]]]}},{"type":"Feature","properties":{"id":"GS"},"geometry":{"type":"Polygon","coordinates":[[[-42.54719,-53.2784],[-42.2676,-53.8363],[-26.56899,-59.99267],[-23.74554,-55.04457],[-42.54719,-53.2784]]]}},{"type":"Feature","properties":{"id":"GU"},"geometry":{"type":"Polygon","coordinates":[[[143.82485,13.92273],[144.61642,12.82462],[146.25931,13.85876],[143.82485,13.92273]]]}},{"type":"Feature","properties":{"id":"HM"},"geometry":{"type":"Polygon","coordinates":[[[71.83423,-53.46357],[74.60136,-53.32773],[73.06787,-52.24306],[71.83423,-53.46357]]]}},{"type":"Feature","properties":{"id":"HK"},"geometry":{"type":"Polygon","coordinates":[[[113.81621,22.2163],[113.83338,22.1826],[113.92195,22.13873],[114.50148,22.15017],[114.44998,22.55977],[114.25154,22.55977],[114.22841,22.5409],[114.22538,22.54549],[114.22693,22.54801],[114.22418,22.55086],[114.21916,22.55493],[114.20873,22.55687],[114.18685,22.55475],[114.17818,22.55542],[114.17683,22.5599],[114.17176,22.55956],[114.16779,22.56143],[114.16408,22.55885],[114.1609,22.56202],[114.15633,22.55455],[114.15228,22.55481],[114.1496,22.54827],[114.15198,22.54738],[114.14835,22.54367],[114.14867,22.54185],[114.14455,22.54113],[114.13831,22.54351],[114.1343,22.54157],[114.13089,22.54197],[114.12775,22.53957],[114.12477,22.53965],[114.12018,22.53503],[114.11659,22.53402],[114.11569,22.53122],[114.11134,22.52924],[114.10413,22.53588],[114.09692,22.53435],[114.09048,22.53716],[114.08606,22.53276],[114.07817,22.52997],[114.07424,22.51874],[114.06413,22.51681],[114.05812,22.51148],[114.05615,22.50252],[114.03113,22.5065],[113.86771,22.42972],[113.81621,22.2163]]]}},{"type":"Feature","properties":{"id":"PR"},"geometry":{"type":"Polygon","coordinates":[[[-68.20301,17.83927],[-65.27974,17.56928],[-65.02435,18.73231],[-67.99519,18.97186],[-68.20301,17.83927]]]}},{"type":"Feature","properties":{"id":"MO"},"geometry":{"type":"Polygon","coordinates":[[[113.52659,22.18271],[113.54093,22.15497],[113.54942,22.14519],[113.54839,22.10909],[113.57191,22.07696],[113.63011,22.10782],[113.60504,22.20464],[113.57123,22.20416],[113.56865,22.20973],[113.5508,22.21672],[113.54333,22.21688],[113.54093,22.21314],[113.53593,22.2137],[113.53301,22.21235],[113.53552,22.20607],[113.52659,22.18271]]]}},{"type":"Feature","properties":{"id":"YT"},"geometry":{"type":"Polygon","coordinates":[[[44.82592,-12.46164],[44.83095,-13.17072],[45.42508,-13.16671],[45.42004,-12.45762],[44.82592,-12.46164]]]}},{"type":"Feature","properties":{"id":"NU"},"geometry":{"type":"Polygon","coordinates":[[[-173.13438,-14.94228],[-173.11048,-23.23027],[-167.73129,-23.22266],[-167.73854,-14.92809],[-171.14262,-14.93704],[-173.13438,-14.94228]]]}},{"type":"Feature","properties":{"id":"NF"},"geometry":{"type":"Polygon","coordinates":[[[165.46901,-28.32101],[169.35326,-30.60259],[169.6687,-29.09191],[166.75333,-27.25416],[165.46901,-28.32101]]]}},{"type":"Feature","properties":{"id":"MF"},"geometry":{"type":"Polygon","coordinates":[[[-63.35989,18.06012],[-63.33064,17.9615],[-63.13584,18.0541],[-63.11096,18.05368],[-63.09686,18.04608],[-63.07759,18.04943],[-63.0591,18.06744],[-63.03998,18.05596],[-63.03669,18.05786],[-63.02886,18.05482],[-63.02323,18.05757],[-62.93924,18.02904],[-62.75637,18.13489],[-62.86666,18.19278],[-63.35989,18.06012]]]}},{"type":"Feature","properties":{"id":"BL"},"geometry":{"type":"Polygon","coordinates":[[[-63.07669,17.79659],[-62.76692,17.64353],[-62.54836,17.8636],[-62.75637,18.13489],[-62.93924,18.02904],[-63.07669,17.79659]]]}},{"type":"Feature","properties":{"id":"AC"},"geometry":{"type":"Polygon","coordinates":[[[-14.60614,-7.38624],[-14.56953,-8.25719],[-13.99188,-7.95424],[-14.60614,-7.38624]]]}},{"type":"Feature","properties":{"id":"IC"},"geometry":{"type":"Polygon","coordinates":[[[-18.58553,28.95791],[-18.29528,27.07966],[-13.84006,27.86813],[-12.87179,29.54492],[-13.43249,29.9224],[-18.58553,28.95791]]]}},{"type":"Feature","properties":{"id":"ES-CE"},"geometry":{"type":"Polygon","coordinates":[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]]}},{"type":"Feature","properties":{"id":"ES-ML"},"geometry":{"type":"Polygon","coordinates":[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]]}},{"type":"Feature","properties":{"id":"DG"},"geometry":{"type":"Polygon","coordinates":[[[72.0768,-6.94304],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[72.0768,-6.94304]]]}},{"type":"Feature","properties":{"id":"TA"},"geometry":{"type":"Polygon","coordinates":[[[-13.48367,-36.6746],[-13.41694,-37.88844],[-11.48092,-37.8367],[-11.55782,-36.60319],[-13.48367,-36.6746]]]}},{"type":"Feature","properties":{"id":"AU-WA"},"geometry":{"type":"Polygon","coordinates":[[[111.88472,-23.52365],[115.28483,-36.59878],[129.11535,-33.13244],[129.00183,-25.99861],[129.00057,-25.99861],[129,-14.42902],[127.55165,-9.05052],[125.68138,-9.85176],[122.91521,-11.65621],[111.88472,-23.52365]]]}},{"type":"Feature","properties":{"id":"AU-NT"},"geometry":{"type":"Polygon","coordinates":[[[127.55165,-9.05052],[129,-14.42902],[129.00057,-25.99861],[129.00183,-25.99861],[137.99993,-25.99819],[138.00067,-16.23841],[139.41724,-8.97063],[137.03241,-8.98668],[127.55165,-9.05052]]]}},{"type":"Feature","properties":{"id":"AU-SA"},"geometry":{"type":"Polygon","coordinates":[[[129.00183,-25.99861],[129.11535,-33.13244],[140.96881,-39.24482],[140.9638,-33.98067],[141.00268,-34.02172],[140.99934,-28.99903],[141.00013,-26.00101],[137.99993,-25.99819],[129.00183,-25.99861]]]}},{"type":"Feature","properties":{"id":"AU-TAS"},"geometry":{"type":"Polygon","coordinates":[[[140.96881,-39.24482],[158.81148,-55.541],[159.49376,-54.48258],[149.33352,-39.17352],[140.96881,-39.24482]]]}},{"type":"Feature","properties":{"id":"AU-VIC"},"geometry":{"type":"Polygon","coordinates":[[[140.9638,-33.98067],[140.96881,-39.24482],[149.33352,-39.17352],[150.19141,-37.59274],[148.19405,-36.79602],[148.10753,-36.79272],[148.20366,-36.59782],[148.04573,-36.39248],[147.9915,-36.04909],[147.71083,-35.92992],[147.55823,-35.99772],[147.40991,-35.93298],[147.31926,-36.05458],[147.10503,-36.00683],[147.03569,-36.11541],[147.00496,-36.08455],[146.91329,-36.11069],[146.90463,-36.08351],[146.85097,-36.08788],[146.58645,-35.97383],[146.42387,-35.96794],[146.36894,-36.03571],[146.02203,-35.99884],[145.90702,-35.9655],[145.79578,-35.98314],[145.51872,-35.80417],[145.34447,-35.86005],[145.1165,-35.81774],[144.9778,-35.86673],[144.94896,-36.05236],[144.77697,-36.12872],[144.77182,-36.11638],[144.7477,-36.12075],[144.74298,-36.10785],[144.72487,-36.11229],[144.08241,-35.57238],[143.56743,-35.33634],[143.57155,-35.20741],[143.39027,-35.18047],[143.3271,-34.99618],[143.34221,-34.79344],[142.76268,-34.56871],[142.61711,-34.77765],[142.50999,-34.74267],[142.37404,-34.34563],[142.24495,-34.3014],[142.22023,-34.18334],[142.0211,-34.12651],[141.71349,-34.0924],[141.5377,-34.18902],[141.00268,-34.02172],[140.9638,-33.98067]]]}},{"type":"Feature","properties":{"id":"AU-QLD"},"geometry":{"type":"Polygon","coordinates":[[[137.99993,-25.99819],[141.00013,-26.00101],[140.99934,-28.99903],[148.94654,-28.9989],[148.99108,-28.97285],[149.19248,-28.77479],[149.37788,-28.68628],[149.48705,-28.58202],[149.58044,-28.57056],[149.67519,-28.62723],[150.28146,-28.5426],[150.29159,-28.53672],[150.30022,-28.54807],[150.32043,-28.55757],[150.36206,-28.5904],[150.37021,-28.62039],[150.43737,-28.66098],[150.74499,-28.63446],[151.27508,-28.94017],[151.31092,-29.16115],[151.39318,-29.17186],[151.55248,-28.94858],[151.72552,-28.86683],[151.7777,-28.9606],[152.00511,-28.90547],[152.07052,-28.68832],[152.0131,-28.66091],[151.95173,-28.53702],[152.37899,-28.3633],[152.50345,-28.24663],[152.58018,-28.33822],[152.60902,-28.28185],[152.74738,-28.36315],[152.85209,-28.31208],[153.10649,-28.35817],[153.18121,-28.25289],[153.36639,-28.24708],[153.48092,-28.15509],[153.53414,-28.17635],[153.54177,-28.1687],[155.32243,-27.36405],[153.5901,-24.72709],[149.03078,-19.80828],[144.30183,-9.48146],[142.81927,-9.31709],[142.5723,-9.35994],[142.31447,-9.24611],[142.23304,-9.19253],[142.1462,-9.19923],[142.0953,-9.23534],[142.0601,-9.56571],[140.88922,-9.34945],[139.41724,-8.97063],[138.00067,-16.23841],[137.99993,-25.99819]]]}},{"type":"Feature","properties":{"id":"AU-ACT"},"geometry":{"type":"Polygon","coordinates":[[[148.76247,-35.49504],[148.78891,-35.69995],[148.85723,-35.76043],[148.8768,-35.715],[148.89431,-35.75095],[148.89602,-35.82504],[148.96194,-35.8971],[149.04811,-35.91684],[149.09824,-35.81223],[149.09549,-35.6411],[149.07936,-35.58193],[149.14219,-35.59337],[149.12983,-35.55288],[149.15283,-35.50566],[149.13429,-35.45338],[149.2057,-35.34732],[149.25136,-35.33024],[149.33719,-35.33976],[149.35058,-35.3518],[149.39796,-35.32435],[149.39418,-35.30362],[149.23488,-35.24336],[149.2469,-35.2285],[149.18956,-35.20157],[149.19746,-35.18502],[149.12159,-35.1241],[148.80951,-35.30698],[148.76247,-35.49504]]]}},{"type":"Feature","properties":{"id":"NL-BQ3"},"geometry":{"type":"Polygon","coordinates":[[[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-63.22932,17.32592]]]}},{"type":"Feature","properties":{"id":"CA-BC"},"geometry":{"type":"Polygon","coordinates":[[[-139.05365,59.99655],[-138.71149,59.90728],[-138.62145,59.76431],[-137.60623,59.24465],[-137.4925,58.89415],[-136.82619,59.16198],[-136.52365,59.16752],[-136.47323,59.46617],[-136.33727,59.44466],[-136.22381,59.55526],[-136.31566,59.59083],[-135.48007,59.79937],[-135.03069,59.56208],[-135.00267,59.28745],[-134.7047,59.2458],[-134.49912,59.12103],[-134.27175,58.8634],[-133.84645,58.73543],[-133.38523,58.42773],[-131.8271,56.62247],[-130.77769,56.36185],[-130.33965,56.10849],[-130.10173,56.12178],[-130.00093,56.00325],[-130.00857,55.91344],[-130.15373,55.74895],[-129.97513,55.28029],[-130.08035,55.21556],[-130.18765,55.07744],[-130.27203,54.97174],[-130.53591,54.80215],[-130.65082,54.7709],[-130.61931,54.70835],[-133.92876,54.62289],[-132.11664,51.5313],[-125.03842,48.53282],[-123.15614,48.22053],[-123.26565,48.6959],[-122.98526,48.79206],[-123.3218,49.00227],[-117.03266,49.00056],[-116.04938,48.99999],[-114.0683,48.99885],[-114.74352,49.57064],[-114.70812,50.29534],[-120.00143,53.79861],[-120.00135,60.00043],[-123.86203,59.99964],[-139.05365,59.99655]]]}},{"type":"Feature","properties":{"id":"CA-AB"},"geometry":{"type":"Polygon","coordinates":[[[-120.00143,53.79861],[-114.70812,50.29534],[-114.74352,49.57064],[-114.0683,48.99885],[-110.0051,48.99901],[-110.00637,59.99944],[-120.00135,60.00043],[-120.00143,53.79861]]]}},{"type":"Feature","properties":{"id":"CA-SK"},"geometry":{"type":"Polygon","coordinates":[[[-110.00637,59.99944],[-110.0051,48.99901],[-104.05004,48.99925],[-101.36198,48.99935],[-101.98961,55.81762],[-102.00759,59.99922],[-110.00637,59.99944]]]}},{"type":"Feature","properties":{"id":"CA-MB"},"geometry":{"type":"Polygon","coordinates":[[[-102.00759,59.99922],[-101.98961,55.81762],[-101.36198,48.99935],[-97.24024,48.99952],[-95.15355,48.9996],[-95.15357,49.37852],[-95.15374,52.84191],[-88.99084,56.84662],[-94.78348,59.99917],[-102.00759,59.99922]]]}},{"type":"Feature","properties":{"id":"CA-ON"},"geometry":{"type":"Polygon","coordinates":[[[-95.15374,52.84191],[-95.15357,49.37852],[-95.05825,49.35311],[-94.95766,49.37046],[-94.85381,49.32492],[-94.8159,49.32299],[-94.82487,49.29483],[-94.68751,48.84286],[-94.70477,48.82975],[-94.68691,48.77498],[-94.58903,48.71803],[-94.54885,48.71543],[-94.53826,48.70216],[-94.44258,48.69223],[-94.4174,48.71049],[-94.26485,48.70188],[-94.2464,48.65422],[-93.84515,48.63011],[-93.82292,48.62313],[-93.80279,48.51892],[-93.66382,48.51845],[-93.45374,48.54834],[-93.46377,48.58567],[-93.40693,48.60948],[-93.39758,48.60364],[-93.3712,48.60599],[-93.33946,48.62787],[-93.22713,48.64334],[-92.94973,48.60866],[-92.62836,48.52564],[-92.62747,48.50278],[-92.69927,48.49573],[-92.71323,48.46081],[-92.65606,48.43471],[-92.50712,48.44921],[-92.45588,48.40624],[-92.48147,48.36609],[-92.37185,48.22259],[-92.27167,48.25046],[-92.30939,48.31251],[-92.26662,48.35651],[-92.05339,48.35958],[-91.98929,48.25409],[-91.71231,48.18936],[-91.70451,48.11805],[-91.55649,48.10611],[-91.58025,48.04339],[-91.45829,48.07454],[-91.43248,48.04912],[-91.25025,48.08522],[-91.08016,48.18096],[-90.87588,48.2484],[-90.75045,48.09143],[-90.56444,48.12184],[-90.56312,48.09488],[-90.07418,48.11043],[-89.89974,47.98109],[-89.77248,48.02607],[-89.57972,48.00023],[-89.48837,48.01412],[-88.37033,48.30586],[-85.04547,46.88317],[-84.55635,46.45974],[-84.47607,46.45225],[-84.4402,46.49657],[-84.34174,46.50683],[-84.29893,46.49127],[-84.26351,46.49508],[-84.2264,46.53337],[-84.1945,46.54061],[-84.17723,46.52753],[-84.12953,46.53233],[-84.11196,46.50248],[-84.14875,46.40366],[-84.11254,46.32329],[-84.11615,46.2681],[-84.09756,46.25512],[-84.1096,46.23987],[-83.94231,46.05681],[-83.90453,46.05922],[-83.82299,46.12002],[-83.57017,46.105],[-83.43746,45.99749],[-83.59589,45.82131],[-81.54485,44.86396],[-82.42469,42.992],[-82.4146,42.97626],[-82.4253,42.95423],[-82.45331,42.93139],[-82.4826,42.8068],[-82.46613,42.76615],[-82.51063,42.66025],[-82.51617,42.61668],[-82.59645,42.5468],[-82.64242,42.55594],[-82.83152,42.37811],[-82.97944,42.33438],[-83.07871,42.31244],[-83.12724,42.2376],[-83.14962,42.04089],[-83.08506,41.89693],[-82.71775,41.66281],[-80.55605,42.3348],[-79.78216,42.57325],[-78.89518,42.84543],[-78.91213,42.93838],[-79.02074,42.98444],[-79.02424,43.01983],[-78.99941,43.05612],[-79.01055,43.06659],[-79.07636,43.07797],[-79.06276,43.09706],[-79.05782,43.11153],[-79.06881,43.12029],[-79.0427,43.13934],[-79.04652,43.16396],[-79.05178,43.17029],[-79.05512,43.25375],[-79.06921,43.26183],[-79.25796,43.54052],[-76.79706,43.63099],[-76.43859,44.09393],[-76.35324,44.13493],[-76.31222,44.19894],[-76.244,44.19643],[-76.1664,44.23051],[-76.16285,44.28262],[-76.00018,44.34896],[-75.95947,44.34463],[-75.8217,44.43176],[-75.76813,44.51537],[-75.41441,44.76614],[-75.2193,44.87821],[-75.01363,44.95608],[-74.99101,44.98051],[-74.8447,45.00606],[-74.66689,45.00646],[-74.50073,45.06927],[-74.32151,45.18844],[-74.47257,45.30253],[-74.38125,45.56562],[-74.48081,45.59638],[-74.52819,45.59157],[-74.61059,45.62183],[-74.64286,45.64008],[-74.71427,45.62856],[-74.94087,45.64536],[-75.02601,45.59589],[-75.13038,45.57715],[-75.24162,45.58677],[-75.33981,45.53581],[-75.4804,45.51368],[-75.58151,45.47361],[-75.61876,45.47192],[-75.68562,45.45826],[-75.69334,45.45187],[-75.70287,45.43712],[-75.70424,45.42706],[-75.70957,45.42345],[-75.71772,45.42158],[-75.7239,45.422],[-75.72381,45.41766],[-75.72982,45.41736],[-75.75935,45.4114],[-75.80535,45.3762],[-75.8493,45.37716],[-75.91522,45.41477],[-75.97152,45.47643],[-76.091,45.51735],[-76.19468,45.52023],[-76.23245,45.51109],[-76.24343,45.46873],[-76.36772,45.45814],[-76.5023,45.51638],[-76.60873,45.53226],[-76.65885,45.56015],[-76.67602,45.58466],[-76.6719,45.62357],[-76.71653,45.66438],[-76.68975,45.69172],[-76.77558,45.75308],[-76.7646,45.84978],[-76.77696,45.87273],[-76.893,45.90141],[-76.93077,45.89137],[-76.91085,45.80624],[-76.94107,45.789],[-76.98982,45.78613],[-77.01866,45.80863],[-77.04681,45.8072],[-77.07565,45.83543],[-77.12097,45.84165],[-77.19238,45.86556],[-77.2734,45.93962],[-77.28782,45.98258],[-77.27683,46.0174],[-77.31803,46.02741],[-77.35717,46.05696],[-77.67852,46.19925],[-77.69363,46.18452],[-78.24981,46.27714],[-78.31642,46.25388],[-78.38508,46.29138],[-78.703,46.32173],[-78.72703,46.3407],[-78.73115,46.38619],[-78.88496,46.45624],[-78.98796,46.54604],[-79.01817,46.63664],[-79.04563,46.64607],[-79.08958,46.68518],[-79.17335,46.82678],[-79.21317,46.83335],[-79.44869,47.10512],[-79.42535,47.25307],[-79.48921,47.30711],[-79.58877,47.42941],[-79.55581,47.51342],[-79.51787,47.53313],[-79.51659,51.46031],[-82.09357,52.92137],[-82.10455,55.13419],[-88.99084,56.84662],[-95.15374,52.84191]]]}},{"type":"Feature","properties":{"id":"CA-NS"},"geometry":{"type":"Polygon","coordinates":[[[-67.16117,44.20069],[-65.81187,42.91911],[-59.437,43.71683],[-57.60106,47.38123],[-60.49316,47.38172],[-61.75622,46.42845],[-62.49954,45.85952],[-63.58306,46.10096],[-64.03625,46.01901],[-64.04861,45.97703],[-64.1571,45.9799],[-64.28619,45.83274],[-64.33563,45.8557],[-64.54574,45.68615],[-67.16117,44.20069]]]}},{"type":"Feature","properties":{"id":"CA-PE"},"geometry":{"type":"Polygon","coordinates":[[[-64.66796,46.6005],[-63.75335,46.22936],[-63.58306,46.10096],[-62.49954,45.85952],[-61.75622,46.42845],[-64.01702,47.11511],[-64.66796,46.6005]]]}},{"type":"Feature","properties":{"id":"CA-NB"},"geometry":{"type":"Polygon","coordinates":[[[-69.05073,47.30076],[-69.05039,47.2456],[-68.89222,47.1807],[-68.79784,47.21665],[-68.69424,47.24148],[-68.62206,47.24142],[-68.60069,47.25063],[-68.58687,47.28272],[-68.55228,47.28243],[-68.50009,47.30088],[-68.44216,47.28307],[-68.37598,47.28668],[-68.38431,47.32567],[-68.37203,47.35126],[-68.32998,47.36028],[-68.23565,47.35464],[-68.16578,47.32422],[-68.14012,47.29972],[-67.96382,47.20172],[-67.93155,47.15995],[-67.86495,47.09981],[-67.79027,47.06731],[-67.78111,45.9392],[-67.75196,45.91814],[-67.80961,45.87531],[-67.75955,45.82748],[-67.80653,45.80022],[-67.80705,45.69528],[-67.6049,45.60725],[-67.45536,45.60851],[-67.42017,45.57415],[-67.42863,45.56872],[-67.42144,45.50584],[-67.50578,45.48971],[-67.42394,45.37969],[-67.48789,45.28207],[-67.40215,45.16097],[-67.34351,45.1246],[-67.29623,45.14751],[-67.30074,45.16588],[-67.29168,45.17229],[-67.29359,45.17737],[-67.28924,45.18799],[-67.28456,45.19153],[-67.26508,45.1902],[-67.23321,45.16882],[-67.15904,45.16312],[-67.0216,44.95333],[-66.96824,44.90965],[-66.98249,44.87071],[-66.96824,44.83078],[-66.93432,44.82597],[-67.16117,44.20069],[-64.54574,45.68615],[-64.33563,45.8557],[-64.28619,45.83274],[-64.1571,45.9799],[-64.04861,45.97703],[-64.03625,46.01901],[-63.58306,46.10096],[-63.75335,46.22936],[-64.66796,46.6005],[-64.01702,47.11511],[-64.29855,48.12037],[-65.19393,47.92013],[-66.28295,48.03872],[-66.37908,48.08919],[-66.50405,48.0791],[-66.52465,48.04698],[-66.69012,48.00944],[-66.93664,47.97716],[-66.96548,47.89159],[-67.06161,47.93117],[-67.37884,47.85751],[-67.60406,47.93209],[-67.60543,48.00014],[-68.12316,48.00014],[-68.12454,47.91461],[-68.38409,47.91553],[-68.38546,47.55439],[-68.57223,47.42727],[-68.80844,47.34824],[-69.05073,47.30076]]]}},{"type":"Feature","properties":{"id":"CA-NL"},"geometry":{"type":"Polygon","coordinates":[[[-67.83623,54.45267],[-67.35283,52.90413],[-64.55407,51.57984],[-63.8082,51.99787],[-57.10774,51.99856],[-57.10928,51.37809],[-60.49316,47.38172],[-57.60106,47.38123],[-56.67989,47.3339],[-56.25228,47.31192],[-55.8643,46.64935],[-52.04856,46.06197],[-53.04203,51.48141],[-64.00822,60.49118],[-64.79302,60.27404],[-63.7603,54.63112],[-66.84746,55.09899],[-67.83623,54.45267]]]}},{"type":"Feature","properties":{"id":"CA-QC"},"geometry":{"type":"Polygon","coordinates":[[[-79.92119,54.66449],[-78.74291,52.08899],[-79.56139,51.58666],[-79.51659,51.46031],[-79.51787,47.53313],[-79.55581,47.51342],[-79.58877,47.42941],[-79.48921,47.30711],[-79.42535,47.25307],[-79.44869,47.10512],[-79.21317,46.83335],[-79.17335,46.82678],[-79.08958,46.68518],[-79.04563,46.64607],[-79.01817,46.63664],[-78.98796,46.54604],[-78.88496,46.45624],[-78.73115,46.38619],[-78.72703,46.3407],[-78.703,46.32173],[-78.38508,46.29138],[-78.31642,46.25388],[-78.24981,46.27714],[-77.69363,46.18452],[-77.67852,46.19925],[-77.35717,46.05696],[-77.31803,46.02741],[-77.27683,46.0174],[-77.28782,45.98258],[-77.2734,45.93962],[-77.19238,45.86556],[-77.12097,45.84165],[-77.07565,45.83543],[-77.04681,45.8072],[-77.01866,45.80863],[-76.98982,45.78613],[-76.94107,45.789],[-76.91085,45.80624],[-76.93077,45.89137],[-76.893,45.90141],[-76.77696,45.87273],[-76.7646,45.84978],[-76.77558,45.75308],[-76.68975,45.69172],[-76.71653,45.66438],[-76.6719,45.62357],[-76.67602,45.58466],[-76.65885,45.56015],[-76.60873,45.53226],[-76.5023,45.51638],[-76.36772,45.45814],[-76.24343,45.46873],[-76.23245,45.51109],[-76.19468,45.52023],[-76.091,45.51735],[-75.97152,45.47643],[-75.91522,45.41477],[-75.8493,45.37716],[-75.80535,45.3762],[-75.75935,45.4114],[-75.72982,45.41736],[-75.72381,45.41766],[-75.7239,45.422],[-75.71772,45.42158],[-75.70957,45.42345],[-75.70424,45.42706],[-75.70287,45.43712],[-75.69334,45.45187],[-75.68562,45.45826],[-75.61876,45.47192],[-75.58151,45.47361],[-75.4804,45.51368],[-75.33981,45.53581],[-75.24162,45.58677],[-75.13038,45.57715],[-75.02601,45.59589],[-74.94087,45.64536],[-74.71427,45.62856],[-74.64286,45.64008],[-74.61059,45.62183],[-74.52819,45.59157],[-74.48081,45.59638],[-74.38125,45.56562],[-74.47257,45.30253],[-74.32151,45.18844],[-74.50073,45.06927],[-74.66689,45.00646],[-74.32699,44.99029],[-73.35073,45.01056],[-72.69824,45.01566],[-72.52504,45.00826],[-72.08662,45.00571],[-71.50067,45.01357],[-71.48735,45.07784],[-71.42778,45.12624],[-71.40364,45.21382],[-71.44252,45.2361],[-71.37133,45.24624],[-71.29371,45.29996],[-71.22338,45.25184],[-71.19723,45.25438],[-71.14568,45.24128],[-71.08364,45.30623],[-70.9406,45.34341],[-70.89864,45.2398],[-70.81726,45.2219],[-70.80715,45.4143],[-70.78372,45.43269],[-70.65383,45.37592],[-70.62518,45.42286],[-70.72651,45.49771],[-70.59168,45.64987],[-70.38934,45.73215],[-70.41523,45.79497],[-70.25976,45.89675],[-70.24694,45.95138],[-70.31025,45.96424],[-70.23855,46.1453],[-70.29078,46.18832],[-70.18547,46.35357],[-70.05812,46.41768],[-69.99966,46.69543],[-69.22485,47.4596],[-69.05148,47.42012],[-69.05073,47.30076],[-68.80844,47.34824],[-68.57223,47.42727],[-68.38546,47.55439],[-68.38409,47.91553],[-68.12454,47.91461],[-68.12316,48.00014],[-67.60543,48.00014],[-67.60406,47.93209],[-67.37884,47.85751],[-67.06161,47.93117],[-66.96548,47.89159],[-66.93664,47.97716],[-66.69012,48.00944],[-66.52465,48.04698],[-66.50405,48.0791],[-66.37908,48.08919],[-66.28295,48.03872],[-65.19393,47.92013],[-64.29855,48.12037],[-64.01702,47.11511],[-61.75622,46.42845],[-60.49316,47.38172],[-57.10928,51.37809],[-57.10774,51.99856],[-63.8082,51.99787],[-64.55407,51.57984],[-67.35283,52.90413],[-67.83623,54.45267],[-66.84746,55.09899],[-63.7603,54.63112],[-64.79302,60.27404],[-68.84423,60.00396],[-69.3496,61.12682],[-73.67821,62.57769],[-78.62206,62.65853],[-79.23729,58.64783],[-77.32567,57.55654],[-77.17186,56.02851],[-78.98049,54.90049],[-79.92119,54.66449]]]}},{"type":"Feature","properties":{"id":"CA-YT"},"geometry":{"type":"Polygon","coordinates":[[[-141.00116,60.30648],[-140.5227,60.22077],[-140.45648,60.30919],[-139.98024,60.18027],[-139.68991,60.33693],[-139.05831,60.35205],[-139.20603,60.08896],[-139.05365,59.99655],[-123.86203,59.99964],[-124.71898,60.9114],[-126.80638,60.77223],[-134.09581,67.00286],[-136.15918,67.00031],[-136.20381,67.0511],[-136.21788,67.59731],[-136.4486,67.65377],[-136.44585,69.52838],[-140.97259,70.50112],[-140.97259,70.50112],[-141.00116,60.30648]]]}},{"type":"Feature","properties":{"id":"CA-NT"},"geometry":{"type":"Polygon","coordinates":[[[-136.4486,67.65377],[-136.21788,67.59731],[-136.20381,67.0511],[-136.15918,67.00031],[-134.09581,67.00286],[-126.80638,60.77223],[-124.71898,60.9114],[-123.86203,59.99964],[-120.00135,60.00043],[-110.00637,59.99944],[-102.00759,59.99922],[-102.08165,64.2419],[-111.26622,65.10654],[-120.71446,68.0217],[-120.71446,69.63965],[-110.07969,70.00345],[-110.08928,79.34392],[-123.66215,76.4243],[-127.92083,71.23557],[-136.44585,69.52838],[-136.4486,67.65377]]]}},{"type":"Feature","properties":{"id":"CA-NU"},"geometry":{"type":"Polygon","coordinates":[[[-120.71446,68.0217],[-111.26622,65.10654],[-102.08165,64.2419],[-102.00759,59.99922],[-94.78348,59.99917],[-88.99084,56.84662],[-82.10455,55.13419],[-82.09357,52.92137],[-79.51659,51.46031],[-79.56139,51.58666],[-78.74291,52.08899],[-79.92119,54.66449],[-78.98049,54.90049],[-77.17186,56.02851],[-77.32567,57.55654],[-79.23729,58.64783],[-78.62206,62.65853],[-73.67821,62.57769],[-69.3496,61.12682],[-68.84423,60.00396],[-64.79302,60.27404],[-64.00822,60.49118],[-60.64457,66.70518],[-78.6941,75.13595],[-73.91222,78.42484],[-67.1326,80.75493],[-66.45595,80.82603],[-59.93819,82.31398],[-64.90918,83.25283],[-80.79441,83.23241],[-110.08928,79.34392],[-110.07969,70.00345],[-120.71446,69.63965],[-120.71446,68.0217]]]}},{"type":"Feature","properties":{"id":"US-DC"},"geometry":{"type":"Polygon","coordinates":[[[-77.11962,38.93441],[-77.11536,38.92787],[-77.10592,38.91912],[-77.10201,38.91264],[-77.08897,38.90436],[-77.07047,38.90106],[-77.06759,38.89895],[-77.05957,38.88152],[-77.05493,38.87964],[-77.0491,38.87343],[-77.04592,38.87557],[-77.03021,38.86133],[-77.03906,38.79153],[-76.90939,38.89301],[-77.04117,38.99552],[-77.11962,38.93441]]]}},{"type":"Feature","properties":{"id":"US-AL"},"geometry":{"type":"Polygon","coordinates":[[[-88.47496,31.89065],[-88.37952,30.00457],[-87.51915,30.07055],[-87.51803,30.28416],[-87.44516,30.30425],[-87.5109,30.31411],[-87.3777,30.44658],[-87.42285,30.46285],[-87.44774,30.52113],[-87.39418,30.62518],[-87.40654,30.67362],[-87.52739,30.74093],[-87.63588,30.86596],[-87.59056,30.96375],[-87.5988,30.99743],[-85.99737,30.9932],[-85.00191,31.0026],[-85.00191,31.02143],[-85.03778,31.10909],[-85.09804,31.16255],[-85.11159,31.27811],[-85.09271,31.28999],[-85.04311,31.52965],[-85.06439,31.62619],[-85.14284,31.81864],[-85.05547,32.01766],[-85.06233,32.13519],[-84.88518,32.26185],[-85.0074,32.33034],[-84.96268,32.42409],[-84.99581,32.45379],[-84.99684,32.47102],[-84.99358,32.48261],[-84.99916,32.51697],[-85.06919,32.58529],[-85.13288,32.78337],[-85.16215,32.81137],[-85.15657,32.85291],[-85.18386,32.86264],[-85.60478,34.98639],[-88.20305,35.00664],[-88.20029,34.99532],[-88.14949,34.9211],[-88.09868,34.89407],[-88.47496,31.89065]]]}},{"type":"Feature","properties":{"id":"US-AZ"},"geometry":{"type":"Polygon","coordinates":[[[-114.82011,32.49609],[-114.63109,32.43959],[-112.34627,31.73488],[-111.07523,31.33232],[-109.56227,31.33402],[-109.05235,31.3333],[-109.04686,37.00061],[-114.05113,37.00171],[-114.04564,36.1991],[-114.12254,36.11372],[-114.14864,36.02936],[-114.2379,36.01715],[-114.30931,36.06379],[-114.37523,36.147],[-114.57298,36.15254],[-114.61281,36.13036],[-114.63066,36.14367],[-114.75014,36.09486],[-114.72404,36.03159],[-114.74052,36.0127],[-114.74602,35.98271],[-114.66774,35.87039],[-114.70482,35.85592],[-114.68971,35.65197],[-114.65263,35.60956],[-114.68147,35.49783],[-114.59633,35.33331],[-114.5692,35.17051],[-114.57796,35.12636],[-114.62791,35.12129],[-114.64731,35.10165],[-114.5995,35.07011],[-114.63632,35.03147],[-114.6346,35.00244],[-114.63203,34.86704],[-114.58672,34.83773],[-114.5565,34.77233],[-114.47411,34.71478],[-114.42879,34.58939],[-114.37969,34.53102],[-114.38072,34.45009],[-114.3409,34.44682],[-114.1924,34.35534],[-114.14185,34.30767],[-114.13112,34.25764],[-114.16142,34.25941],[-114.17198,34.24813],[-114.22339,34.20505],[-114.22905,34.18773],[-114.25197,34.17509],[-114.29175,34.16749],[-114.31815,34.14136],[-114.3608,34.1263],[-114.43136,34.10128],[-114.43634,34.02549],[-114.53728,33.93223],[-114.50432,33.87182],[-114.52766,33.85015],[-114.49745,33.69718],[-114.52904,33.68461],[-114.53316,33.55196],[-114.64731,33.41346],[-114.72954,33.40763],[-114.69795,33.35947],[-114.72816,33.3044],[-114.67186,33.22517],[-114.70894,33.0918],[-114.62559,33.03169],[-114.5208,33.03196],[-114.46337,32.91093],[-114.46938,32.84433],[-114.53281,32.79333],[-114.52757,32.75782],[-114.61461,32.73451],[-114.61576,32.72826],[-114.70482,32.7425],[-114.71871,32.71894],[-114.76736,32.64094],[-114.80584,32.62028],[-114.81141,32.55543],[-114.79524,32.55731],[-114.82011,32.49609]]]}},{"type":"Feature","properties":{"id":"US-AR"},"geometry":{"type":"Polygon","coordinates":[[[-94.61906,36.49995],[-94.43092,35.38371],[-94.48448,33.64004],[-94.38423,33.56455],[-94.04313,33.56568],[-94.04366,33.01929],[-91.16798,33.00432],[-91.14052,33.29866],[-91.08696,33.96299],[-90.58846,34.49776],[-90.56236,34.72946],[-90.24925,34.93349],[-90.3004,34.9951],[-90.13389,35.12802],[-90.07072,35.1314],[-90.10299,35.33359],[-89.8843,35.64139],[-89.93579,35.67124],[-89.93819,35.71276],[-89.76688,35.77492],[-89.70131,35.83366],[-89.77409,35.87261],[-89.73564,35.91044],[-89.67384,35.8804],[-89.64638,35.91489],[-89.73701,36.00048],[-90.37834,35.99826],[-90.11467,36.26446],[-90.07759,36.27442],[-90.07347,36.39833],[-90.13939,36.41711],[-90.1545,36.49885],[-94.61906,36.49995]]]}},{"type":"Feature","properties":{"id":"US-CA"},"geometry":{"type":"Polygon","coordinates":[[[-125.69978,42.00605],[-121.26019,33.77126],[-118.48109,32.5991],[-117.12426,32.53431],[-116.87852,32.55531],[-116.58627,32.57969],[-115.88053,32.63624],[-115.49377,32.66517],[-114.71871,32.71894],[-114.70482,32.7425],[-114.61576,32.72826],[-114.61461,32.73451],[-114.52757,32.75782],[-114.53281,32.79333],[-114.46938,32.84433],[-114.46337,32.91093],[-114.5208,33.03196],[-114.62559,33.03169],[-114.70894,33.0918],[-114.67186,33.22517],[-114.72816,33.3044],[-114.69795,33.35947],[-114.72954,33.40763],[-114.64731,33.41346],[-114.53316,33.55196],[-114.52904,33.68461],[-114.49745,33.69718],[-114.52766,33.85015],[-114.50432,33.87182],[-114.53728,33.93223],[-114.43634,34.02549],[-114.43136,34.10128],[-114.3608,34.1263],[-114.31815,34.14136],[-114.29175,34.16749],[-114.25197,34.17509],[-114.22905,34.18773],[-114.22339,34.20505],[-114.17198,34.24813],[-114.16142,34.25941],[-114.13112,34.25764],[-114.14185,34.30767],[-114.1924,34.35534],[-114.3409,34.44682],[-114.38072,34.45009],[-114.37969,34.53102],[-114.42879,34.58939],[-114.47411,34.71478],[-114.5565,34.77233],[-114.58672,34.83773],[-114.63203,34.86704],[-114.6346,35.00244],[-120.00023,38.99862],[-120.0016,41.99495],[-125.69978,42.00605]]]}},{"type":"Feature","properties":{"id":"US-CO"},"geometry":{"type":"Polygon","coordinates":[[[-109.05003,41.00069],[-109.04686,37.00061],[-103.00462,36.99417],[-102.04214,36.99314],[-102.04923,40.00324],[-102.05165,41.00235],[-104.0521,41.00188],[-109.05003,41.00069]]]}},{"type":"Feature","properties":{"id":"US-CT"},"geometry":{"type":"Polygon","coordinates":[[[-73.72761,41.10079],[-73.655,41.01184],[-73.66013,41.00063],[-73.65947,40.99373],[-73.65723,40.99062],[-73.65981,40.98854],[-73.62136,40.9494],[-71.85736,41.32188],[-71.82955,41.34199],[-71.83856,41.36466],[-71.83195,41.3701],[-71.83273,41.37584],[-71.83097,41.37885],[-71.83333,41.38245],[-71.83294,41.38756],[-71.84191,41.39455],[-71.84363,41.40948],[-71.81926,41.41952],[-71.79866,41.41592],[-71.78724,41.65617],[-71.8001,42.00779],[-71.80067,42.0236],[-72.75464,42.03756],[-72.76837,42.00491],[-72.81712,41.9993],[-72.81369,42.03654],[-73.48746,42.0497],[-73.5508,41.2957],[-73.48283,41.21298],[-73.72761,41.10079]]]}},{"type":"Feature","properties":{"id":"US-DE"},"geometry":{"type":"Polygon","coordinates":[[[-75.78987,39.72204],[-75.78918,39.65388],[-75.69382,38.46017],[-74.98718,38.4507],[-75.01808,38.79724],[-75.56876,39.48105],[-75.55641,39.63352],[-75.46131,39.77457],[-75.41621,39.80165],[-75.43796,39.81414],[-75.46783,39.82548],[-75.49942,39.83365],[-75.53959,39.83945],[-75.57632,39.83945],[-75.59898,39.83734],[-75.62713,39.83259],[-75.65975,39.82337],[-75.68172,39.81387],[-75.71434,39.79515],[-75.72532,39.78644],[-75.74043,39.77378],[-75.76,39.75002],[-75.77476,39.7223],[-75.78987,39.72204]]]}},{"type":"Feature","properties":{"id":"US-FL"},"geometry":{"type":"Polygon","coordinates":[[[-87.63588,30.86596],[-87.52739,30.74093],[-87.40654,30.67362],[-87.39418,30.62518],[-87.44774,30.52113],[-87.42285,30.46285],[-87.3777,30.44658],[-87.5109,30.31411],[-87.44516,30.30425],[-87.51803,30.28416],[-87.51915,30.07055],[-83.18732,24.36791],[-82.02215,24.23074],[-80.16442,23.44484],[-79.36558,27.02964],[-81.34929,30.71298],[-81.56078,30.71535],[-81.95354,30.83098],[-81.97826,30.77554],[-82.01946,30.7897],[-82.05105,30.66575],[-82.00847,30.56765],[-82.04555,30.36287],[-82.17052,30.3605],[-82.20897,30.40908],[-82.2282,30.56765],[-83.54501,30.64751],[-84.86355,30.7118],[-84.92122,30.76256],[-84.9377,30.88285],[-85.00637,30.97591],[-85.00191,31.0026],[-85.99737,30.9932],[-87.5988,30.99743],[-87.59056,30.96375],[-87.63588,30.86596]]]}},{"type":"Feature","properties":{"id":"US-GA"},"geometry":{"type":"Polygon","coordinates":[[[-85.60478,34.98639],[-85.18386,32.86264],[-85.15657,32.85291],[-85.16215,32.81137],[-85.13288,32.78337],[-85.06919,32.58529],[-84.99916,32.51697],[-84.99358,32.48261],[-84.99684,32.47102],[-84.99581,32.45379],[-84.96268,32.42409],[-85.0074,32.33034],[-84.88518,32.26185],[-85.06233,32.13519],[-85.05547,32.01766],[-85.14284,31.81864],[-85.06439,31.62619],[-85.04311,31.52965],[-85.09271,31.28999],[-85.11159,31.27811],[-85.09804,31.16255],[-85.03778,31.10909],[-85.00191,31.02143],[-85.00191,31.0026],[-85.00637,30.97591],[-84.9377,30.88285],[-84.92122,30.76256],[-84.86355,30.7118],[-83.54501,30.64751],[-82.2282,30.56765],[-82.20897,30.40908],[-82.17052,30.3605],[-82.04555,30.36287],[-82.00847,30.56765],[-82.05105,30.66575],[-82.01946,30.7897],[-81.97826,30.77554],[-81.95354,30.83098],[-81.56078,30.71535],[-81.34929,30.71298],[-80.49837,32.0326],[-80.92066,32.03667],[-81.00511,32.1001],[-81.05386,32.08497],[-81.11635,32.11638],[-81.11978,32.1937],[-81.15823,32.24017],[-81.11772,32.29127],[-81.20767,32.42409],[-81.19257,32.46292],[-81.27771,32.53531],[-81.28595,32.55904],[-81.3244,32.55788],[-81.38277,32.59086],[-81.41847,32.63193],[-81.39581,32.65101],[-81.42809,32.84101],[-81.45555,32.84851],[-81.50087,32.93557],[-81.49538,33.00988],[-81.62103,33.09564],[-81.64163,33.09276],[-81.70824,33.11807],[-81.76248,33.15947],[-81.7721,33.18303],[-81.75974,33.19912],[-81.77553,33.2198],[-81.78858,33.20716],[-81.85037,33.24622],[-81.85587,33.30248],[-81.93964,33.34609],[-81.91354,33.43953],[-81.92968,33.46674],[-81.9877,33.48794],[-81.99766,33.51342],[-82.05019,33.56464],[-82.10752,33.59782],[-82.1343,33.59095],[-82.19747,33.63013],[-82.20022,33.66214],[-82.3015,33.80062],[-82.39008,33.85651],[-82.56414,33.95567],[-82.59538,34.02855],[-82.64105,34.06809],[-82.64311,34.0951],[-82.71658,34.14882],[-82.74095,34.20789],[-82.75057,34.2709],[-82.78043,34.29672],[-82.79657,34.34039],[-82.83365,34.36419],[-82.87519,34.47408],[-82.90231,34.48625],[-83.00085,34.47238],[-83.03655,34.48625],[-83.16873,34.59259],[-83.17148,34.60728],[-83.22916,34.61096],[-83.34829,34.69455],[-83.35447,34.72814],[-83.32048,34.75861],[-83.32357,34.78878],[-83.2374,34.87417],[-83.11689,34.94033],[-83.12616,34.9544],[-83.09869,34.99098],[-83.10796,35.0011],[-84.32144,34.98837],[-85.60478,34.98639]]]}},{"type":"Feature","properties":{"id":"US-ID"},"geometry":{"type":"Polygon","coordinates":[[[-117.23982,44.3835],[-117.18841,44.33993],[-117.22223,44.30069],[-117.20249,44.27501],[-117.15022,44.25577],[-117.10953,44.28054],[-117.05091,44.22896],[-117.03752,44.24882],[-116.97521,44.24329],[-116.97155,44.19565],[-116.90457,44.18171],[-116.8947,44.1556],[-116.94126,44.09485],[-116.97632,44.08897],[-116.97358,44.0491],[-116.94465,44.0366],[-116.93444,44.02346],[-116.93859,43.98654],[-116.97426,43.96681],[-116.9625,43.92769],[-116.9837,43.86621],[-117.01314,43.86213],[-117.02679,43.83282],[-117.02619,42.00013],[-114.04073,42.00031],[-111.04628,42.00049],[-111.04897,44.47412],[-111.38328,44.75395],[-111.51237,44.64267],[-111.48765,44.539],[-112.31163,44.55662],[-112.39403,44.44888],[-112.71812,44.50571],[-112.83897,44.43712],[-112.82249,44.36255],[-113.00926,44.45476],[-113.13286,44.7754],[-113.25645,44.82802],[-113.3361,44.7871],[-113.5009,44.93506],[-113.44047,44.97005],[-113.45421,45.05742],[-113.75084,45.34385],[-113.82225,45.59809],[-113.98155,45.70752],[-114.33861,45.46148],[-114.54735,45.5731],[-114.55559,45.77653],[-114.39628,45.88752],[-114.49516,46.04407],[-114.31938,46.64887],[-114.61052,46.64322],[-115.33562,47.25631],[-115.75859,47.43311],[-115.62286,47.47243],[-115.75859,47.55002],[-115.67942,47.59921],[-115.84774,47.81661],[-116.04835,47.97742],[-116.04938,48.99999],[-117.03266,49.00056],[-117.04021,46.4257],[-117.03644,46.42309],[-117.03575,46.40794],[-117.04914,46.37787],[-117.06287,46.3665],[-117.06184,46.34873],[-117.04811,46.34281],[-117.03472,46.34138],[-116.92348,46.16048],[-116.98116,46.08242],[-116.94958,46.07004],[-116.91799,45.99567],[-116.77585,45.82129],[-116.6978,45.81994],[-116.54789,45.7533],[-116.46274,45.60746],[-116.5306,45.53791],[-116.67291,45.32167],[-116.72458,45.16606],[-116.75388,45.11342],[-116.7711,45.10818],[-116.79376,45.06485],[-116.84864,45.02321],[-116.84552,45.00431],[-116.85968,44.97597],[-116.83213,44.97624],[-116.84835,44.95744],[-116.83353,44.92995],[-116.9379,44.78588],[-117.05051,44.74298],[-117.15626,44.53093],[-117.21742,44.48511],[-117.23982,44.3835]]]}},{"type":"Feature","properties":{"id":"US-IL"},"geometry":{"type":"Polygon","coordinates":[[[-91.51508,40.18595],[-91.42238,39.92606],[-91.44676,39.86811],[-91.36242,39.80026],[-91.37615,39.73481],[-91.04656,39.45756],[-90.72933,39.25847],[-90.67989,39.09612],[-90.71491,39.05828],[-90.66822,38.94035],[-90.59034,38.8643],[-90.55767,38.87088],[-90.50262,38.90639],[-90.46429,38.96705],[-90.41141,38.96438],[-90.31826,38.92816],[-90.2247,38.90506],[-90.1086,38.84789],[-90.12234,38.79761],[-90.16079,38.7778],[-90.2116,38.73015],[-90.21366,38.70604],[-90.18894,38.68138],[-90.17795,38.64385],[-90.19031,38.60094],[-90.2532,38.54118],[-90.29045,38.43073],[-90.36031,38.35956],[-90.37233,38.27349],[-90.34915,38.20513],[-90.18139,38.07258],[-89.83806,37.90374],[-89.51522,37.68979],[-89.52221,37.52786],[-89.43706,37.4385],[-89.42922,37.35282],[-89.48776,37.33454],[-89.52381,37.28047],[-89.46064,37.24454],[-89.46728,37.20698],[-89.37669,37.04175],[-89.28325,36.99011],[-89.25584,37.00776],[-89.30923,37.06558],[-89.28056,37.09065],[-89.16641,36.97293],[-89.13568,36.98088],[-89.1798,37.0338],[-89.05998,37.18712],[-88.97552,37.22103],[-88.61229,37.10502],[-88.55266,37.06247],[-88.46477,37.06247],[-88.42357,37.15884],[-88.51696,37.27476],[-88.47301,37.39487],[-88.40984,37.4276],[-88.35491,37.40142],[-88.30822,37.44286],[-88.06652,37.47121],[-88.15716,37.66279],[-88.03356,37.80181],[-88.09124,37.90807],[-88.02257,37.8864],[-88.03356,38.04231],[-87.92976,38.144],[-87.98572,38.22955],[-87.74462,38.4078],[-87.74394,38.47596],[-87.65381,38.50606],[-87.60944,38.66567],[-87.52867,38.68396],[-87.49523,38.76121],[-87.54901,38.86497],[-87.5172,38.9537],[-87.57213,38.98786],[-87.57213,39.05188],[-87.66145,39.14164],[-87.58283,39.20831],[-87.60549,39.32433],[-87.53185,39.35195],[-87.51995,41.76174],[-87.20959,41.75764],[-87.02282,42.49706],[-90.64245,42.50785],[-90.65258,42.48406],[-90.43121,42.35448],[-90.40958,42.24224],[-90.1744,42.12063],[-90.13595,42.0141],[-90.18566,41.809],[-90.30651,41.75012],[-90.34325,41.65143],[-90.34187,41.58803],[-90.3956,41.57481],[-90.46581,41.52162],[-90.50667,41.51828],[-90.54116,41.5258],[-90.59781,41.51018],[-90.60572,41.49565],[-90.65197,41.46344],[-90.90963,41.43037],[-91.04129,41.41955],[-91.1067,41.24657],[-90.95546,41.10444],[-90.95409,40.94956],[-91.09382,40.81874],[-91.10069,40.68532],[-91.40487,40.57328],[-91.36848,40.39022],[-91.44307,40.37471],[-91.51508,40.18595]]]}},{"type":"Feature","properties":{"id":"US-IN"},"geometry":{"type":"Polygon","coordinates":[[[-88.09124,37.90807],[-88.03356,37.80181],[-87.95578,37.76983],[-87.90222,37.82273],[-87.94102,37.88427],[-87.89089,37.92842],[-87.83527,37.87478],[-87.67666,37.90079],[-87.67735,37.82273],[-87.60662,37.82761],[-87.58328,37.87912],[-87.62447,37.92463],[-87.59014,37.97336],[-87.5771,37.94629],[-87.5255,37.90316],[-87.414,37.94494],[-87.30371,37.89598],[-87.20535,37.84381],[-87.15351,37.83229],[-87.12827,37.78089],[-87.08364,37.78374],[-87.04227,37.83514],[-87.03866,37.90113],[-86.81217,37.99717],[-86.73345,37.89219],[-86.65637,37.90912],[-86.65912,37.84124],[-86.5999,37.8594],[-86.59149,37.9186],[-86.50772,37.9251],[-86.52385,38.03308],[-86.43013,38.05457],[-86.46755,38.1305],[-86.39802,38.10484],[-86.37451,38.12996],[-86.32215,38.13293],[-86.38549,38.20513],[-86.27384,38.13989],[-86.27728,38.05937],[-86.18568,38.01144],[-86.10191,38.01334],[-86.04303,37.9538],[-86.02174,37.99602],[-85.93574,38.00847],[-85.91267,38.0664],[-85.90511,38.17552],[-85.83576,38.23379],[-85.82948,38.2739],[-85.79182,38.28717],[-85.7452,38.26163],[-85.65567,38.31095],[-85.62469,38.40679],[-85.41269,38.53487],[-85.44582,38.72141],[-85.26403,38.73949],[-85.22146,38.69689],[-85.16628,38.68597],[-84.99188,38.77543],[-84.93925,38.77309],[-84.89582,38.79102],[-84.80991,38.78881],[-84.82845,38.83054],[-84.7705,38.88074],[-84.88064,38.90486],[-84.82433,38.97696],[-84.89702,39.05651],[-84.81884,39.10708],[-84.80645,41.69747],[-84.80614,41.761],[-87.20959,41.75764],[-87.51995,41.76174],[-87.53185,39.35195],[-87.60549,39.32433],[-87.58283,39.20831],[-87.66145,39.14164],[-87.57213,39.05188],[-87.57213,38.98786],[-87.5172,38.9537],[-87.54901,38.86497],[-87.49523,38.76121],[-87.52867,38.68396],[-87.60944,38.66567],[-87.65381,38.50606],[-87.74394,38.47596],[-87.74462,38.4078],[-87.98572,38.22955],[-87.92976,38.144],[-88.03356,38.04231],[-88.02257,37.8864],[-88.09124,37.90807]]]}},{"type":"Feature","properties":{"id":"US-IA"},"geometry":{"type":"Polygon","coordinates":[[[-96.63614,42.74513],[-96.49532,42.57672],[-96.47695,42.5539],[-96.47931,42.52579],[-96.49231,42.52019],[-96.49472,42.5157],[-96.47523,42.50627],[-96.47669,42.49171],[-96.44039,42.48963],[-96.39842,42.48649],[-96.38254,42.47073],[-96.37979,42.44816],[-96.39318,42.42586],[-96.41601,42.40235],[-96.41464,42.34623],[-96.3367,42.26536],[-96.35009,42.17587],[-96.27061,42.11325],[-96.27199,42.04687],[-96.2374,41.99764],[-96.14032,41.97397],[-96.16092,41.90176],[-96.06548,41.79614],[-96.10839,41.70188],[-96.09286,41.53421],[-96.00934,41.47643],[-95.91928,41.45728],[-95.95664,41.34459],[-95.93507,41.32489],[-95.88701,41.31922],[-95.8719,41.30426],[-95.87602,41.28569],[-95.92203,41.26917],[-95.91173,41.23046],[-95.93095,41.20566],[-95.92203,41.18655],[-95.86092,41.18861],[-95.84512,41.18035],[-95.84412,41.16773],[-95.87534,41.16587],[-95.88426,41.14985],[-95.86189,41.08763],[-95.88171,41.05845],[-95.85399,40.99246],[-95.83648,40.82965],[-95.88935,40.73034],[-95.84404,40.67822],[-95.77692,40.64938],[-95.76559,40.58514],[-94.12811,40.57224],[-91.73074,40.61201],[-91.44307,40.37471],[-91.36848,40.39022],[-91.40487,40.57328],[-91.10069,40.68532],[-91.09382,40.81874],[-90.95409,40.94956],[-90.95546,41.10444],[-91.1067,41.24657],[-91.04129,41.41955],[-90.90963,41.43037],[-90.65197,41.46344],[-90.60572,41.49565],[-90.59781,41.51018],[-90.54116,41.5258],[-90.50667,41.51828],[-90.46581,41.52162],[-90.3956,41.57481],[-90.34187,41.58803],[-90.34325,41.65143],[-90.30651,41.75012],[-90.18566,41.809],[-90.13595,42.0141],[-90.1744,42.12063],[-90.40958,42.24224],[-90.43121,42.35448],[-90.65258,42.48406],[-90.64245,42.50785],[-90.63583,42.51918],[-90.70901,42.65026],[-90.98018,42.69707],[-91.08804,42.7774],[-91.09354,42.8761],[-91.14669,42.92098],[-91.16769,43.17528],[-91.06155,43.2532],[-91.09628,43.31932],[-91.21439,43.38123],[-91.21776,43.50025],[-96.45334,43.50083],[-96.59797,43.5005],[-96.58407,43.48219],[-96.59917,43.43783],[-96.52416,43.39245],[-96.52862,43.30481],[-96.58458,43.29382],[-96.55162,43.22606],[-96.48233,43.22133],[-96.43713,43.1204],[-96.63614,42.74513]]]}},{"type":"Feature","properties":{"id":"US-KS"},"geometry":{"type":"Polygon","coordinates":[[[-102.04923,40.00324],[-102.04214,36.99314],[-94.61795,36.9986],[-94.60713,39.1194],[-94.5909,39.13759],[-94.58953,39.15384],[-94.60601,39.16129],[-94.64686,39.1533],[-94.66128,39.15863],[-94.66094,39.17673],[-94.67845,39.18498],[-94.72239,39.16874],[-94.75157,39.17194],[-94.77011,39.18684],[-94.77664,39.20174],[-94.90298,39.30383],[-94.91122,39.35216],[-94.87895,39.37446],[-94.89405,39.39622],[-94.92839,39.38561],[-95.10348,39.53404],[-95.10279,39.57851],[-95.02177,39.67371],[-94.97337,39.68327],[-94.95706,39.73121],[-94.87071,39.73068],[-94.8575,39.75319],[-94.86865,39.773],[-94.90779,39.75875],[-94.93491,39.77221],[-94.93251,39.78804],[-94.88272,39.79542],[-94.88273,39.84281],[-95.14263,39.89735],[-95.31497,40.00166],[-102.04923,40.00324]]]}},{"type":"Feature","properties":{"id":"US-KY"},"geometry":{"type":"Polygon","coordinates":[[[-89.41635,36.49942],[-89.29996,36.50742],[-88.05987,36.49674],[-88.06536,36.67979],[-87.80993,36.63792],[-87.15969,36.64197],[-86.58943,36.65272],[-86.56436,36.63343],[-86.50651,36.65244],[-85.48805,36.61552],[-85.23811,36.62655],[-84.78492,36.60367],[-83.69109,36.58281],[-83.67455,36.60056],[-83.5239,36.66716],[-83.42125,36.66798],[-83.28134,36.72003],[-83.13466,36.74328],[-83.1277,36.77684],[-83.08032,36.84701],[-82.88806,36.88188],[-82.85862,36.92821],[-82.86059,36.98316],[-82.72447,37.04243],[-82.72447,37.12145],[-82.34698,37.2744],[-81.96873,37.53756],[-82.06297,37.53545],[-82.10237,37.55185],[-82.14391,37.56567],[-82.16936,37.61015],[-82.16494,37.62283],[-82.18116,37.61644],[-82.17786,37.64013],[-82.20193,37.61712],[-82.21957,37.63313],[-82.21683,37.63924],[-82.2394,37.66153],[-82.25532,37.65684],[-82.27189,37.6635],[-82.28334,37.67604],[-82.29184,37.66605],[-82.29467,37.67835],[-82.30433,37.67583],[-82.30682,37.70765],[-82.3336,37.74119],[-82.33772,37.77607],[-82.40179,37.81036],[-82.39969,37.83019],[-82.41702,37.84593],[-82.50792,37.94108],[-82.46406,37.98317],[-82.52268,38.0134],[-82.5862,38.10734],[-82.60233,38.11943],[-82.61881,38.12226],[-82.63263,38.13922],[-82.64361,38.1673],[-82.60791,38.17378],[-82.59692,38.21156],[-82.61435,38.2404],[-82.57873,38.2497],[-82.57495,38.32261],[-82.59761,38.34576],[-82.59692,38.42382],[-82.61032,38.4685],[-82.65907,38.49726],[-82.69426,38.53769],[-82.72601,38.55603],[-82.79879,38.56355],[-82.84549,38.58502],[-82.87844,38.69121],[-82.8702,38.73943],[-82.89492,38.7555],[-82.97784,38.72516],[-83.02951,38.72872],[-83.0556,38.69336],[-83.10778,38.67621],[-83.14933,38.62143],[-83.20117,38.61614],[-83.24511,38.63116],[-83.28615,38.59728],[-83.31378,38.5997],[-83.32803,38.6394],[-83.52424,38.70292],[-83.62414,38.67621],[-83.66088,38.62518],[-83.77092,38.65522],[-83.78688,38.69703],[-83.83323,38.7119],[-83.85915,38.75368],[-83.94601,38.78299],[-84.06858,38.77135],[-84.21003,38.8044],[-84.23355,38.82673],[-84.23388,38.88176],[-84.28934,38.9522],[-84.30117,38.99445],[-84.33088,39.02758],[-84.41035,39.0446],[-84.43182,39.05745],[-84.43396,39.09436],[-84.45181,39.1178],[-84.47731,39.11987],[-84.49524,39.09889],[-84.51919,39.09016],[-84.54769,39.09909],[-84.56365,39.0865],[-84.62252,39.07392],[-84.68432,39.09844],[-84.73634,39.1447],[-84.81884,39.10708],[-84.89702,39.05651],[-84.82433,38.97696],[-84.88064,38.90486],[-84.7705,38.88074],[-84.82845,38.83054],[-84.80991,38.78881],[-84.89582,38.79102],[-84.93925,38.77309],[-84.99188,38.77543],[-85.16628,38.68597],[-85.22146,38.69689],[-85.26403,38.73949],[-85.44582,38.72141],[-85.41269,38.53487],[-85.62469,38.40679],[-85.65567,38.31095],[-85.7452,38.26163],[-85.79182,38.28717],[-85.82948,38.2739],[-85.83576,38.23379],[-85.90511,38.17552],[-85.91267,38.0664],[-85.93574,38.00847],[-86.02174,37.99602],[-86.04303,37.9538],[-86.10191,38.01334],[-86.18568,38.01144],[-86.27728,38.05937],[-86.27384,38.13989],[-86.38549,38.20513],[-86.32215,38.13293],[-86.37451,38.12996],[-86.39802,38.10484],[-86.46755,38.1305],[-86.43013,38.05457],[-86.52385,38.03308],[-86.50772,37.9251],[-86.59149,37.9186],[-86.5999,37.8594],[-86.65912,37.84124],[-86.65637,37.90912],[-86.73345,37.89219],[-86.81217,37.99717],[-87.03866,37.90113],[-87.04227,37.83514],[-87.08364,37.78374],[-87.12827,37.78089],[-87.15351,37.83229],[-87.20535,37.84381],[-87.30371,37.89598],[-87.414,37.94494],[-87.5255,37.90316],[-87.5771,37.94629],[-87.59014,37.97336],[-87.62447,37.92463],[-87.58328,37.87912],[-87.60662,37.82761],[-87.67735,37.82273],[-87.67666,37.90079],[-87.83527,37.87478],[-87.89089,37.92842],[-87.94102,37.88427],[-87.90222,37.82273],[-87.95578,37.76983],[-88.03356,37.80181],[-88.15716,37.66279],[-88.06652,37.47121],[-88.30822,37.44286],[-88.35491,37.40142],[-88.40984,37.4276],[-88.47301,37.39487],[-88.51696,37.27476],[-88.42357,37.15884],[-88.46477,37.06247],[-88.55266,37.06247],[-88.61229,37.10502],[-88.97552,37.22103],[-89.05998,37.18712],[-89.1798,37.0338],[-89.13568,36.98088],[-89.09636,36.9678],[-89.22168,36.56737],[-89.36175,36.63352],[-89.41635,36.49942]]]}},{"type":"Feature","properties":{"id":"US-LA"},"geometry":{"type":"Polygon","coordinates":[[[-94.04467,32.00379],[-93.89258,31.87297],[-93.52008,31.03684],[-93.76452,30.33771],[-93.70959,30.28791],[-93.71508,30.05521],[-93.92244,29.81124],[-93.83525,29.68178],[-93.77551,29.43998],[-88.93054,28.25639],[-88.37952,30.00457],[-89.58972,30.1835],[-89.69134,30.46089],[-89.77374,30.5177],[-89.85339,30.67373],[-89.73529,31.00389],[-91.63317,31.00153],[-91.44641,31.54147],[-90.90533,32.31769],[-91.16798,33.00432],[-94.04366,33.01929],[-94.04467,32.00379]]]}},{"type":"Feature","properties":{"id":"US-ME"},"geometry":{"type":"Polygon","coordinates":[[[-71.08364,45.30623],[-70.97613,43.56977],[-70.95278,43.55783],[-70.98849,43.3884],[-70.96377,43.33749],[-70.93493,43.33549],[-70.89648,43.29052],[-70.81271,43.23052],[-70.83331,43.13238],[-70.73786,43.07272],[-70.70422,43.07623],[-70.70216,43.04463],[-70.04999,42.81005],[-67.16117,44.20069],[-66.93432,44.82597],[-66.96824,44.83078],[-66.98249,44.87071],[-66.96824,44.90965],[-67.0216,44.95333],[-67.15904,45.16312],[-67.23321,45.16882],[-67.26508,45.1902],[-67.28456,45.19153],[-67.28924,45.18799],[-67.29359,45.17737],[-67.29168,45.17229],[-67.30074,45.16588],[-67.29623,45.14751],[-67.34351,45.1246],[-67.40215,45.16097],[-67.48789,45.28207],[-67.42394,45.37969],[-67.50578,45.48971],[-67.42144,45.50584],[-67.42863,45.56872],[-67.42017,45.57415],[-67.45536,45.60851],[-67.6049,45.60725],[-67.80705,45.69528],[-67.80653,45.80022],[-67.75955,45.82748],[-67.80961,45.87531],[-67.75196,45.91814],[-67.78111,45.9392],[-67.79027,47.06731],[-67.86495,47.09981],[-67.93155,47.15995],[-67.96382,47.20172],[-68.14012,47.29972],[-68.16578,47.32422],[-68.23565,47.35464],[-68.32998,47.36028],[-68.37203,47.35126],[-68.38431,47.32567],[-68.37598,47.28668],[-68.44216,47.28307],[-68.50009,47.30088],[-68.55228,47.28243],[-68.58687,47.28272],[-68.60069,47.25063],[-68.62206,47.24142],[-68.69424,47.24148],[-68.79784,47.21665],[-68.89222,47.1807],[-69.05039,47.2456],[-69.05073,47.30076],[-69.05148,47.42012],[-69.22485,47.4596],[-69.99966,46.69543],[-70.05812,46.41768],[-70.18547,46.35357],[-70.29078,46.18832],[-70.23855,46.1453],[-70.31025,45.96424],[-70.24694,45.95138],[-70.25976,45.89675],[-70.41523,45.79497],[-70.38934,45.73215],[-70.59168,45.64987],[-70.72651,45.49771],[-70.62518,45.42286],[-70.65383,45.37592],[-70.78372,45.43269],[-70.80715,45.4143],[-70.81726,45.2219],[-70.89864,45.2398],[-70.9406,45.34341],[-71.08364,45.30623]]]}},{"type":"Feature","properties":{"id":"US-MD"},"geometry":{"type":"Polygon","coordinates":[[[-79.48702,39.20187],[-79.43226,39.22334],[-79.37853,39.27261],[-79.35999,39.27526],[-79.3454,39.29365],[-79.31158,39.30502],[-79.29235,39.29865],[-79.25321,39.35575],[-79.21579,39.36424],[-79.19657,39.38733],[-79.18172,39.38533],[-79.11143,39.44487],[-79.09418,39.47264],[-79.06993,39.47386],[-79.06692,39.48036],[-79.05697,39.46886],[-79.05126,39.48489],[-79.0083,39.46048],[-78.9774,39.44822],[-78.96492,39.43828],[-78.94114,39.4788],[-78.91591,39.48761],[-78.84218,39.56824],[-78.81925,39.56065],[-78.81832,39.5951],[-78.79574,39.60687],[-78.79643,39.63742],[-78.78141,39.63719],[-78.77523,39.64614],[-78.76424,39.64888],[-78.76532,39.64419],[-78.77948,39.62192],[-78.73592,39.62235],[-78.73892,39.60775],[-78.77738,39.60866],[-78.76364,39.58262],[-78.73343,39.58644],[-78.72695,39.56424],[-78.66382,39.53741],[-78.59404,39.53582],[-78.56803,39.52238],[-78.521,39.52542],[-78.46422,39.51596],[-78.4619,39.53714],[-78.41903,39.54925],[-78.45817,39.58584],[-78.39706,39.5826],[-78.43156,39.62208],[-78.38959,39.61977],[-78.35706,39.64165],[-78.26643,39.61957],[-78.24471,39.64839],[-78.1812,39.6986],[-78.10086,39.68182],[-78.0515,39.65235],[-78.0055,39.60284],[-77.95117,39.6035],[-77.93632,39.6234],[-77.91872,39.60535],[-77.83083,39.60806],[-77.83392,39.56621],[-77.88885,39.55774],[-77.84216,39.49842],[-77.7656,39.49562],[-77.79659,39.47834],[-77.80028,39.43519],[-77.75289,39.42632],[-77.73736,39.39017],[-77.75736,39.33861],[-77.7232,39.32234],[-77.68054,39.32667],[-77.61462,39.3033],[-77.56655,39.30861],[-77.54596,39.27247],[-77.49102,39.25227],[-77.45532,39.22462],[-77.47729,39.18844],[-77.51136,39.17825],[-77.52449,39.14637],[-77.51849,39.12135],[-77.48416,39.11282],[-77.46081,39.07872],[-77.30975,39.05846],[-77.23971,39.02006],[-77.25619,39.00192],[-77.22186,38.97417],[-77.1477,38.9699],[-77.11962,38.93441],[-77.04117,38.99552],[-76.90939,38.89301],[-77.03906,38.79153],[-77.04196,38.70568],[-77.08041,38.70568],[-77.12023,38.68103],[-77.12161,38.63385],[-77.21225,38.6038],[-77.28915,38.50178],[-77.28778,38.38454],[-77.20813,38.35223],[-77.03784,38.42973],[-76.92248,38.23475],[-76.61074,38.15704],[-76.23034,37.8985],[-75.88153,37.90934],[-75.65768,37.94509],[-75.62472,37.99597],[-75.16879,38.02735],[-74.98718,38.4507],[-75.69382,38.46017],[-75.78918,39.65388],[-75.78987,39.72204],[-79.47663,39.72086],[-79.48702,39.20187]]]}},{"type":"Feature","properties":{"id":"US-MA"},"geometry":{"type":"Polygon","coordinates":[[[-73.50942,42.0867],[-73.48746,42.0497],[-72.81369,42.03654],[-72.81712,41.9993],[-72.76837,42.00491],[-72.75464,42.03756],[-71.80067,42.0236],[-71.8001,42.00779],[-71.38117,42.0194],[-71.3822,41.89277],[-71.33894,41.89916],[-71.341,41.79814],[-71.32898,41.7815],[-71.26135,41.75231],[-71.19577,41.67465],[-71.13432,41.65952],[-71.14153,41.60717],[-71.13123,41.591],[-71.10101,41.43444],[-69.97282,40.56828],[-69.42513,41.52748],[-70.04999,42.81005],[-70.81606,42.8739],[-70.84627,42.86182],[-70.88541,42.88446],[-70.92592,42.88698],[-70.96781,42.86887],[-71.04196,42.85981],[-71.06531,42.80744],[-71.13878,42.82305],[-71.18616,42.79484],[-71.18273,42.7399],[-71.25139,42.74343],[-71.29465,42.69651],[-72.45835,42.72702],[-73.26498,42.74494],[-73.50942,42.0867]]]}},{"type":"Feature","properties":{"id":"US-MI"},"geometry":{"type":"Polygon","coordinates":[[[-90.42047,46.56636],[-90.39026,46.53331],[-90.33396,46.5522],[-90.2186,46.50212],[-90.18985,46.45932],[-90.17801,46.45731],[-90.17234,46.43992],[-90.16505,46.43501],[-90.11148,46.3355],[-89.09242,46.1388],[-88.8096,46.02457],[-88.64618,45.98832],[-88.51846,46.02362],[-88.49126,45.9929],[-88.31565,45.96057],[-88.19669,45.95138],[-88.09301,45.91831],[-88.10202,45.88188],[-88.06958,45.87387],[-88.13721,45.81952],[-88.09455,45.7859],[-88.06571,45.78069],[-87.99044,45.79577],[-87.98031,45.77716],[-87.98933,45.77237],[-87.96186,45.7592],[-87.86752,45.75023],[-87.77826,45.67639],[-87.83182,45.65912],[-87.77414,45.6063],[-87.79474,45.56209],[-87.84143,45.57074],[-87.80435,45.53805],[-87.8071,45.47067],[-87.85791,45.43888],[-87.88375,45.35371],[-87.7569,45.34949],[-87.69355,45.39157],[-87.64377,45.35431],[-87.69012,45.2971],[-87.74076,45.20199],[-87.73595,45.17296],[-87.6872,45.14427],[-87.65741,45.10643],[-87.54961,45.09286],[-87.01334,45.54381],[-86.25116,45.23713],[-87.02282,42.49706],[-87.20959,41.75764],[-84.80614,41.761],[-84.80645,41.69747],[-83.42355,41.73233],[-83.08506,41.89693],[-83.14962,42.04089],[-83.12724,42.2376],[-83.07871,42.31244],[-82.97944,42.33438],[-82.83152,42.37811],[-82.64242,42.55594],[-82.59645,42.5468],[-82.51617,42.61668],[-82.51063,42.66025],[-82.46613,42.76615],[-82.4826,42.8068],[-82.45331,42.93139],[-82.4253,42.95423],[-82.4146,42.97626],[-82.42469,42.992],[-81.54485,44.86396],[-83.59589,45.82131],[-83.43746,45.99749],[-83.57017,46.105],[-83.82299,46.12002],[-83.90453,46.05922],[-83.94231,46.05681],[-84.1096,46.23987],[-84.09756,46.25512],[-84.11615,46.2681],[-84.11254,46.32329],[-84.14875,46.40366],[-84.11196,46.50248],[-84.12953,46.53233],[-84.17723,46.52753],[-84.1945,46.54061],[-84.2264,46.53337],[-84.26351,46.49508],[-84.29893,46.49127],[-84.34174,46.50683],[-84.4402,46.49657],[-84.47607,46.45225],[-84.55635,46.45974],[-85.04547,46.88317],[-88.37033,48.30586],[-89.48837,48.01412],[-90.42047,46.56636]]]}},{"type":"Feature","properties":{"id":"US-MN"},"geometry":{"type":"Polygon","coordinates":[[[-97.24024,48.99952],[-97.10334,48.67123],[-97.15372,48.59745],[-97.14685,48.17245],[-97.05369,47.94535],[-97.03674,47.93908],[-97.03614,47.93075],[-97.01777,47.9195],[-97.01863,47.87755],[-97.00129,47.86241],[-96.85589,47.60014],[-96.82182,47.10191],[-96.83511,47.0083],[-96.79684,46.96543],[-96.79856,46.94879],[-96.78628,46.93039],[-96.75667,46.92981],[-96.76431,46.90583],[-96.77646,46.89618],[-96.76946,46.89199],[-96.77418,46.88847],[-96.76774,46.88061],[-96.78088,46.87905],[-96.77598,46.8743],[-96.78028,46.87242],[-96.78358,46.86209],[-96.77641,46.8547],[-96.78586,46.85352],[-96.77311,46.85118],[-96.78165,46.84674],[-96.77526,46.83973],[-96.78744,46.83967],[-96.78341,46.83462],[-96.79409,46.83351],[-96.78199,46.82713],[-96.79276,46.82731],[-96.80216,46.81072],[-96.78839,46.78389],[-96.78337,46.76914],[-96.7898,46.63902],[-96.75006,46.57125],[-96.71942,46.44856],[-96.65067,46.36292],[-96.60303,46.32981],[-96.59424,46.2594],[-96.58943,46.24931],[-96.59754,46.24296],[-96.59587,46.22028],[-96.58892,46.21277],[-96.56732,45.93486],[-96.58106,45.82588],[-96.65109,45.74735],[-96.83847,45.64932],[-96.85838,45.60647],[-96.68621,45.41604],[-96.49591,45.36367],[-96.45334,45.29706],[-96.45334,43.50083],[-91.21776,43.50025],[-91.26411,43.61693],[-91.24582,43.77649],[-91.36299,43.95427],[-91.72554,44.09547],[-91.9874,44.3832],[-92.81215,44.74917],[-92.7658,44.83639],[-92.77507,44.89576],[-92.74589,44.93272],[-92.76838,44.98167],[-92.76426,45.02039],[-92.80374,45.06782],[-92.74023,45.11472],[-92.76632,45.19147],[-92.74314,45.2959],[-92.69748,45.33621],[-92.70486,45.35479],[-92.65028,45.39905],[-92.64538,45.44044],[-92.76056,45.56786],[-92.88416,45.56978],[-92.8718,45.72051],[-92.78803,45.76364],[-92.72348,45.889],[-92.29408,46.07775],[-92.2921,46.65833],[-92.20499,46.65155],[-92.203,46.69744],[-92.1055,46.75016],[-92.02448,46.70498],[-89.48837,48.01412],[-89.57972,48.00023],[-89.77248,48.02607],[-89.89974,47.98109],[-90.07418,48.11043],[-90.56312,48.09488],[-90.56444,48.12184],[-90.75045,48.09143],[-90.87588,48.2484],[-91.08016,48.18096],[-91.25025,48.08522],[-91.43248,48.04912],[-91.45829,48.07454],[-91.58025,48.04339],[-91.55649,48.10611],[-91.70451,48.11805],[-91.71231,48.18936],[-91.98929,48.25409],[-92.05339,48.35958],[-92.26662,48.35651],[-92.30939,48.31251],[-92.27167,48.25046],[-92.37185,48.22259],[-92.48147,48.36609],[-92.45588,48.40624],[-92.50712,48.44921],[-92.65606,48.43471],[-92.71323,48.46081],[-92.69927,48.49573],[-92.62747,48.50278],[-92.62836,48.52564],[-92.94973,48.60866],[-93.22713,48.64334],[-93.33946,48.62787],[-93.3712,48.60599],[-93.39758,48.60364],[-93.40693,48.60948],[-93.46377,48.58567],[-93.45374,48.54834],[-93.66382,48.51845],[-93.80279,48.51892],[-93.82292,48.62313],[-93.84515,48.63011],[-94.2464,48.65422],[-94.26485,48.70188],[-94.4174,48.71049],[-94.44258,48.69223],[-94.53826,48.70216],[-94.54885,48.71543],[-94.58903,48.71803],[-94.68691,48.77498],[-94.70477,48.82975],[-94.68751,48.84286],[-94.82487,49.29483],[-94.8159,49.32299],[-94.85381,49.32492],[-94.95766,49.37046],[-95.05825,49.35311],[-95.15357,49.37852],[-95.15355,48.9996],[-97.24024,48.99952]]]}},{"type":"Feature","properties":{"id":"US-WI"},"geometry":{"type":"Polygon","coordinates":[[[-92.88416,45.56978],[-92.76056,45.56786],[-92.64538,45.44044],[-92.65028,45.39905],[-92.70486,45.35479],[-92.69748,45.33621],[-92.74314,45.2959],[-92.76632,45.19147],[-92.74023,45.11472],[-92.80374,45.06782],[-92.76426,45.02039],[-92.76838,44.98167],[-92.74589,44.93272],[-92.77507,44.89576],[-92.7658,44.83639],[-92.81215,44.74917],[-91.9874,44.3832],[-91.72554,44.09547],[-91.36299,43.95427],[-91.24582,43.77649],[-91.26411,43.61693],[-91.21776,43.50025],[-91.21439,43.38123],[-91.09628,43.31932],[-91.06155,43.2532],[-91.16769,43.17528],[-91.14669,42.92098],[-91.09354,42.8761],[-91.08804,42.7774],[-90.98018,42.69707],[-90.70901,42.65026],[-90.63583,42.51918],[-90.64245,42.50785],[-87.02282,42.49706],[-86.25116,45.23713],[-87.01334,45.54381],[-87.54961,45.09286],[-87.65741,45.10643],[-87.6872,45.14427],[-87.73595,45.17296],[-87.74076,45.20199],[-87.69012,45.2971],[-87.64377,45.35431],[-87.69355,45.39157],[-87.7569,45.34949],[-87.88375,45.35371],[-87.85791,45.43888],[-87.8071,45.47067],[-87.80435,45.53805],[-87.84143,45.57074],[-87.79474,45.56209],[-87.77414,45.6063],[-87.83182,45.65912],[-87.77826,45.67639],[-87.86752,45.75023],[-87.96186,45.7592],[-87.98933,45.77237],[-87.98031,45.77716],[-87.99044,45.79577],[-88.06571,45.78069],[-88.09455,45.7859],[-88.13721,45.81952],[-88.06958,45.87387],[-88.10202,45.88188],[-88.09301,45.91831],[-88.19669,45.95138],[-88.31565,45.96057],[-88.49126,45.9929],[-88.51846,46.02362],[-88.64618,45.98832],[-88.8096,46.02457],[-89.09242,46.1388],[-90.11148,46.3355],[-90.16505,46.43501],[-90.17234,46.43992],[-90.17801,46.45731],[-90.18985,46.45932],[-90.2186,46.50212],[-90.33396,46.5522],[-90.39026,46.53331],[-90.42047,46.56636],[-89.48837,48.01412],[-92.02448,46.70498],[-92.1055,46.75016],[-92.203,46.69744],[-92.20499,46.65155],[-92.2921,46.65833],[-92.29408,46.07775],[-92.72348,45.889],[-92.78803,45.76364],[-92.8718,45.72051],[-92.88416,45.56978]]]}},{"type":"Feature","properties":{"id":"US-WY"},"geometry":{"type":"Polygon","coordinates":[[[-111.05503,44.99947],[-111.04897,44.47412],[-111.04628,42.00049],[-111.04675,40.99766],[-109.05003,41.00069],[-104.0521,41.00188],[-104.05548,43.00258],[-104.05897,44.99972],[-111.05503,44.99947]]]}},{"type":"Feature","properties":{"id":"US-MS"},"geometry":{"type":"Polygon","coordinates":[[[-91.63317,31.00153],[-89.73529,31.00389],[-89.85339,30.67373],[-89.77374,30.5177],[-89.69134,30.46089],[-89.58972,30.1835],[-88.37952,30.00457],[-88.47496,31.89065],[-88.09868,34.89407],[-88.14949,34.9211],[-88.20029,34.99532],[-90.3004,34.9951],[-90.24925,34.93349],[-90.56236,34.72946],[-90.58846,34.49776],[-91.08696,33.96299],[-91.14052,33.29866],[-91.16798,33.00432],[-90.90533,32.31769],[-91.44641,31.54147],[-91.63317,31.00153]]]}},{"type":"Feature","properties":{"id":"US-MO"},"geometry":{"type":"Polygon","coordinates":[[[-95.76559,40.58514],[-95.65168,40.39775],[-95.6092,40.3116],[-95.47445,40.23354],[-95.31497,40.00166],[-95.14263,39.89735],[-94.88273,39.84281],[-94.88272,39.79542],[-94.93251,39.78804],[-94.93491,39.77221],[-94.90779,39.75875],[-94.86865,39.773],[-94.8575,39.75319],[-94.87071,39.73068],[-94.95706,39.73121],[-94.97337,39.68327],[-95.02177,39.67371],[-95.10279,39.57851],[-95.10348,39.53404],[-94.92839,39.38561],[-94.89405,39.39622],[-94.87895,39.37446],[-94.91122,39.35216],[-94.90298,39.30383],[-94.77664,39.20174],[-94.77011,39.18684],[-94.75157,39.17194],[-94.72239,39.16874],[-94.67845,39.18498],[-94.66094,39.17673],[-94.66128,39.15863],[-94.64686,39.1533],[-94.60601,39.16129],[-94.58953,39.15384],[-94.5909,39.13759],[-94.60713,39.1194],[-94.61795,36.9986],[-94.61906,36.49995],[-90.1545,36.49885],[-90.13939,36.41711],[-90.07347,36.39833],[-90.07759,36.27442],[-90.11467,36.26446],[-90.37834,35.99826],[-89.73701,36.00048],[-89.6038,36.12012],[-89.58997,36.1478],[-89.70336,36.24039],[-89.57212,36.24754],[-89.5474,36.43117],[-89.52268,36.46762],[-89.57016,36.55667],[-89.51454,36.57997],[-89.46664,36.55005],[-89.49411,36.46478],[-89.44175,36.46367],[-89.41635,36.49942],[-89.36175,36.63352],[-89.22168,36.56737],[-89.09636,36.9678],[-89.13568,36.98088],[-89.16641,36.97293],[-89.28056,37.09065],[-89.30923,37.06558],[-89.25584,37.00776],[-89.28325,36.99011],[-89.37669,37.04175],[-89.46728,37.20698],[-89.46064,37.24454],[-89.52381,37.28047],[-89.48776,37.33454],[-89.42922,37.35282],[-89.43706,37.4385],[-89.52221,37.52786],[-89.51522,37.68979],[-89.83806,37.90374],[-90.18139,38.07258],[-90.34915,38.20513],[-90.37233,38.27349],[-90.36031,38.35956],[-90.29045,38.43073],[-90.2532,38.54118],[-90.19031,38.60094],[-90.17795,38.64385],[-90.18894,38.68138],[-90.21366,38.70604],[-90.2116,38.73015],[-90.16079,38.7778],[-90.12234,38.79761],[-90.1086,38.84789],[-90.2247,38.90506],[-90.31826,38.92816],[-90.41141,38.96438],[-90.46429,38.96705],[-90.50262,38.90639],[-90.55767,38.87088],[-90.59034,38.8643],[-90.66822,38.94035],[-90.71491,39.05828],[-90.67989,39.09612],[-90.72933,39.25847],[-91.04656,39.45756],[-91.37615,39.73481],[-91.36242,39.80026],[-91.44676,39.86811],[-91.42238,39.92606],[-91.51508,40.18595],[-91.44307,40.37471],[-91.73074,40.61201],[-94.12811,40.57224],[-95.76559,40.58514]]]}},{"type":"Feature","properties":{"id":"US-MT"},"geometry":{"type":"Polygon","coordinates":[[[-116.04938,48.99999],[-116.04835,47.97742],[-115.84774,47.81661],[-115.67942,47.59921],[-115.75859,47.55002],[-115.62286,47.47243],[-115.75859,47.43311],[-115.33562,47.25631],[-114.61052,46.64322],[-114.31938,46.64887],[-114.49516,46.04407],[-114.39628,45.88752],[-114.55559,45.77653],[-114.54735,45.5731],[-114.33861,45.46148],[-113.98155,45.70752],[-113.82225,45.59809],[-113.75084,45.34385],[-113.45421,45.05742],[-113.44047,44.97005],[-113.5009,44.93506],[-113.3361,44.7871],[-113.25645,44.82802],[-113.13286,44.7754],[-113.00926,44.45476],[-112.82249,44.36255],[-112.83897,44.43712],[-112.71812,44.50571],[-112.39403,44.44888],[-112.31163,44.55662],[-111.48765,44.539],[-111.51237,44.64267],[-111.38328,44.75395],[-111.04897,44.47412],[-111.05503,44.99947],[-104.05897,44.99972],[-104.05691,45.94728],[-104.05004,48.99925],[-110.0051,48.99901],[-114.0683,48.99885],[-116.04938,48.99999]]]}},{"type":"Feature","properties":{"id":"US-SD"},"geometry":{"type":"Polygon","coordinates":[[[-104.05897,44.99972],[-104.05548,43.00258],[-98.50401,43.00058],[-98.44908,42.93275],[-97.99383,42.76612],[-97.95057,42.77065],[-97.84826,42.86685],[-97.44314,42.84773],[-97.40125,42.86736],[-97.25019,42.85729],[-97.11836,42.76813],[-97.00575,42.76561],[-96.94875,42.71922],[-96.88627,42.73536],[-96.69264,42.64805],[-96.71461,42.60561],[-96.60475,42.50344],[-96.54304,42.51854],[-96.49652,42.48007],[-96.44039,42.48963],[-96.47669,42.49171],[-96.47523,42.50627],[-96.49472,42.5157],[-96.49231,42.52019],[-96.47931,42.52579],[-96.47695,42.5539],[-96.49532,42.57672],[-96.63614,42.74513],[-96.43713,43.1204],[-96.48233,43.22133],[-96.55162,43.22606],[-96.58458,43.29382],[-96.52862,43.30481],[-96.52416,43.39245],[-96.59917,43.43783],[-96.58407,43.48219],[-96.59797,43.5005],[-96.45334,43.50083],[-96.45334,45.29706],[-96.49591,45.36367],[-96.68621,45.41604],[-96.85838,45.60647],[-96.83847,45.64932],[-96.65109,45.74735],[-96.58106,45.82588],[-96.56732,45.93486],[-104.05691,45.94728],[-104.05897,44.99972]]]}},{"type":"Feature","properties":{"id":"US-NE"},"geometry":{"type":"Polygon","coordinates":[[[-104.05548,43.00258],[-104.0521,41.00188],[-102.05165,41.00235],[-102.04923,40.00324],[-95.31497,40.00166],[-95.47445,40.23354],[-95.6092,40.3116],[-95.65168,40.39775],[-95.76559,40.58514],[-95.77692,40.64938],[-95.84404,40.67822],[-95.88935,40.73034],[-95.83648,40.82965],[-95.85399,40.99246],[-95.88171,41.05845],[-95.86189,41.08763],[-95.88426,41.14985],[-95.87534,41.16587],[-95.84412,41.16773],[-95.84512,41.18035],[-95.86092,41.18861],[-95.92203,41.18655],[-95.93095,41.20566],[-95.91173,41.23046],[-95.92203,41.26917],[-95.87602,41.28569],[-95.8719,41.30426],[-95.88701,41.31922],[-95.93507,41.32489],[-95.95664,41.34459],[-95.91928,41.45728],[-96.00934,41.47643],[-96.09286,41.53421],[-96.10839,41.70188],[-96.06548,41.79614],[-96.16092,41.90176],[-96.14032,41.97397],[-96.2374,41.99764],[-96.27199,42.04687],[-96.27061,42.11325],[-96.35009,42.17587],[-96.3367,42.26536],[-96.41464,42.34623],[-96.41601,42.40235],[-96.39318,42.42586],[-96.37979,42.44816],[-96.38254,42.47073],[-96.39842,42.48649],[-96.44039,42.48963],[-96.49652,42.48007],[-96.54304,42.51854],[-96.60475,42.50344],[-96.71461,42.60561],[-96.69264,42.64805],[-96.88627,42.73536],[-96.94875,42.71922],[-97.00575,42.76561],[-97.11836,42.76813],[-97.25019,42.85729],[-97.40125,42.86736],[-97.44314,42.84773],[-97.84826,42.86685],[-97.95057,42.77065],[-97.99383,42.76612],[-98.44908,42.93275],[-98.50401,43.00058],[-104.05548,43.00258]]]}},{"type":"Feature","properties":{"id":"US-NV"},"geometry":{"type":"Polygon","coordinates":[[[-120.0016,41.99495],[-120.00023,38.99862],[-114.6346,35.00244],[-114.63632,35.03147],[-114.5995,35.07011],[-114.64731,35.10165],[-114.62791,35.12129],[-114.57796,35.12636],[-114.5692,35.17051],[-114.59633,35.33331],[-114.68147,35.49783],[-114.65263,35.60956],[-114.68971,35.65197],[-114.70482,35.85592],[-114.66774,35.87039],[-114.74602,35.98271],[-114.74052,36.0127],[-114.72404,36.03159],[-114.75014,36.09486],[-114.63066,36.14367],[-114.61281,36.13036],[-114.57298,36.15254],[-114.37523,36.147],[-114.30931,36.06379],[-114.2379,36.01715],[-114.14864,36.02936],[-114.12254,36.11372],[-114.04564,36.1991],[-114.05113,37.00171],[-114.04073,42.00031],[-117.02619,42.00013],[-120.0016,41.99495]]]}},{"type":"Feature","properties":{"id":"US-UT"},"geometry":{"type":"Polygon","coordinates":[[[-114.05113,37.00171],[-109.04686,37.00061],[-109.05003,41.00069],[-111.04675,40.99766],[-111.04628,42.00049],[-114.04073,42.00031],[-114.05113,37.00171]]]}},{"type":"Feature","properties":{"id":"US-ND"},"geometry":{"type":"Polygon","coordinates":[[[-104.05691,45.94728],[-96.56732,45.93486],[-96.58892,46.21277],[-96.59587,46.22028],[-96.59754,46.24296],[-96.58943,46.24931],[-96.59424,46.2594],[-96.60303,46.32981],[-96.65067,46.36292],[-96.71942,46.44856],[-96.75006,46.57125],[-96.7898,46.63902],[-96.78337,46.76914],[-96.78839,46.78389],[-96.80216,46.81072],[-96.79276,46.82731],[-96.78199,46.82713],[-96.79409,46.83351],[-96.78341,46.83462],[-96.78744,46.83967],[-96.77526,46.83973],[-96.78165,46.84674],[-96.77311,46.85118],[-96.78586,46.85352],[-96.77641,46.8547],[-96.78358,46.86209],[-96.78028,46.87242],[-96.77598,46.8743],[-96.78088,46.87905],[-96.76774,46.88061],[-96.77418,46.88847],[-96.76946,46.89199],[-96.77646,46.89618],[-96.76431,46.90583],[-96.75667,46.92981],[-96.78628,46.93039],[-96.79856,46.94879],[-96.79684,46.96543],[-96.83511,47.0083],[-96.82182,47.10191],[-96.85589,47.60014],[-97.00129,47.86241],[-97.01863,47.87755],[-97.01777,47.9195],[-97.03614,47.93075],[-97.03674,47.93908],[-97.05369,47.94535],[-97.14685,48.17245],[-97.15372,48.59745],[-97.10334,48.67123],[-97.24024,48.99952],[-101.36198,48.99935],[-104.05004,48.99925],[-104.05691,45.94728]]]}},{"type":"Feature","properties":{"id":"US-NH"},"geometry":{"type":"Polygon","coordinates":[[[-72.55332,42.85501],[-72.53963,42.81095],[-72.50753,42.78028],[-72.51465,42.76522],[-72.49114,42.77499],[-72.45835,42.72702],[-71.29465,42.69651],[-71.25139,42.74343],[-71.18273,42.7399],[-71.18616,42.79484],[-71.13878,42.82305],[-71.06531,42.80744],[-71.04196,42.85981],[-70.96781,42.86887],[-70.92592,42.88698],[-70.88541,42.88446],[-70.84627,42.86182],[-70.81606,42.8739],[-70.04999,42.81005],[-70.70216,43.04463],[-70.70422,43.07623],[-70.73786,43.07272],[-70.83331,43.13238],[-70.81271,43.23052],[-70.89648,43.29052],[-70.93493,43.33549],[-70.96377,43.33749],[-70.98849,43.3884],[-70.95278,43.55783],[-70.97613,43.56977],[-71.08364,45.30623],[-71.14568,45.24128],[-71.19723,45.25438],[-71.22338,45.25184],[-71.29371,45.29996],[-71.37133,45.24624],[-71.44252,45.2361],[-71.40364,45.21382],[-71.42778,45.12624],[-71.48735,45.07784],[-71.50067,45.01357],[-71.46503,45.0136],[-71.48649,45.00092],[-71.50713,45.00799],[-71.5282,45.00055],[-71.53841,44.99248],[-71.49618,44.90788],[-71.54966,44.86219],[-71.57369,44.79255],[-71.6324,44.75136],[-71.54287,44.58601],[-71.5597,44.56405],[-71.59231,44.56351],[-71.59438,44.4897],[-71.639,44.47343],[-71.67695,44.42758],[-71.70536,44.4125],[-71.79788,44.39699],[-71.8147,44.35552],[-71.86834,44.33706],[-71.90679,44.34688],[-71.9782,44.33411],[-72.01143,44.32114],[-72.05932,44.28103],[-72.05924,44.2613],[-72.0522,44.16872],[-72.03838,44.1572],[-72.04198,44.15031],[-72.04,44.08408],[-72.10859,43.99898],[-72.1182,43.92188],[-72.17124,43.88409],[-72.18206,43.84331],[-72.18395,43.80616],[-72.20549,43.77115],[-72.23433,43.74666],[-72.27081,43.73302],[-72.30171,43.70256],[-72.30342,43.66917],[-72.31321,43.6575],[-72.31287,43.64085],[-72.32866,43.63557],[-72.32883,43.6019],[-72.37278,43.57703],[-72.39784,43.51058],[-72.37913,43.49278],[-72.41491,43.36557],[-72.39362,43.35659],[-72.40419,43.27982],[-72.43466,43.25564],[-72.45611,43.14654],[-72.45015,43.13904],[-72.44264,43.13819],[-72.43174,43.11727],[-72.43483,43.0791],[-72.46092,43.05479],[-72.46384,42.97915],[-72.53208,42.9534],[-72.52504,42.92418],[-72.5313,42.89577],[-72.55182,42.88514],[-72.55332,42.85501]]]}},{"type":"Feature","properties":{"id":"US-NJ"},"geometry":{"type":"Polygon","coordinates":[[[-75.56876,39.48105],[-75.01808,38.79724],[-74.98718,38.4507],[-73.81773,39.66512],[-73.9166,40.514],[-74.23109,40.47954],[-74.2613,40.49625],[-74.25615,40.51427],[-74.24722,40.52236],[-74.25134,40.54532],[-74.23418,40.55914],[-74.21839,40.55836],[-74.19985,40.59956],[-74.20397,40.63162],[-74.18577,40.64647],[-74.12775,40.6436],[-74.05668,40.65402],[-74.02612,40.69959],[-74.01445,40.7589],[-73.92003,40.9023],[-73.89463,40.99461],[-74.32825,41.18583],[-74.6956,41.35872],[-74.76152,41.33913],[-74.83431,41.28136],[-74.88237,41.1848],[-75.02931,41.03995],[-75.13849,40.98347],[-75.05266,40.8657],[-75.06777,40.84804],[-75.09935,40.84804],[-75.08219,40.82726],[-75.133,40.77373],[-75.17076,40.77893],[-75.19617,40.75084],[-75.18175,40.73107],[-75.20372,40.691],[-75.19763,40.68233],[-75.17772,40.67783],[-75.18003,40.66939],[-75.20029,40.64881],[-75.19068,40.63865],[-75.19102,40.62093],[-75.19994,40.61259],[-75.19068,40.59435],[-75.1948,40.57844],[-75.18106,40.56619],[-75.16458,40.56332],[-75.14776,40.57453],[-75.12682,40.57453],[-75.10347,40.56984],[-75.06708,40.5388],[-75.06365,40.48581],[-75.07017,40.45525],[-75.06124,40.4218],[-75.03137,40.40534],[-74.98743,40.40664],[-74.96477,40.39592],[-74.94966,40.36611],[-74.94451,40.34152],[-74.91396,40.3177],[-74.89198,40.31246],[-74.86315,40.28837],[-74.84426,40.25013],[-74.78349,40.2234],[-74.76701,40.2095],[-74.75843,40.18774],[-74.73852,40.17855],[-74.72273,40.16045],[-74.72341,40.14864],[-74.74127,40.13473],[-74.75843,40.13447],[-74.7907,40.12187],[-74.82486,40.12829],[-74.83774,40.10244],[-74.8628,40.08432],[-74.91224,40.06987],[-74.92683,40.07105],[-75.04733,40.00955],[-75.0712,39.97969],[-75.13196,39.96154],[-75.13815,39.93627],[-75.12785,39.91073],[-75.13625,39.89097],[-75.26809,39.85014],[-75.33745,39.84842],[-75.41621,39.80165],[-75.46131,39.77457],[-75.55641,39.63352],[-75.56876,39.48105]]]}},{"type":"Feature","properties":{"id":"US-NM"},"geometry":{"type":"Polygon","coordinates":[[[-109.05235,31.3333],[-108.20979,31.33316],[-108.20899,31.78534],[-106.529,31.784],[-106.5436,31.80546],[-106.60333,31.82938],[-106.64522,31.89178],[-106.62187,31.92151],[-106.61981,32.00074],[-103.06402,31.99986],[-103.04239,36.49992],[-103.00325,36.50047],[-103.00462,36.99417],[-109.04686,37.00061],[-109.05235,31.3333]]]}},{"type":"Feature","properties":{"id":"US-NY"},"geometry":{"type":"Polygon","coordinates":[[[-79.78216,42.57325],[-79.76349,42.00107],[-75.3772,42.00515],[-75.08057,41.80278],[-75.0531,41.59155],[-74.98718,41.48258],[-74.74274,41.42288],[-74.6956,41.35872],[-74.32825,41.18583],[-73.89463,40.99461],[-73.92003,40.9023],[-74.01445,40.7589],[-74.02612,40.69959],[-74.05668,40.65402],[-74.12775,40.6436],[-74.18577,40.64647],[-74.20397,40.63162],[-74.19985,40.59956],[-74.21839,40.55836],[-74.23418,40.55914],[-74.25134,40.54532],[-74.24722,40.52236],[-74.25615,40.51427],[-74.2613,40.49625],[-74.23109,40.47954],[-73.9166,40.514],[-73.81773,39.66512],[-71.6391,40.94332],[-71.85736,41.32188],[-73.62136,40.9494],[-73.65981,40.98854],[-73.65723,40.99062],[-73.65947,40.99373],[-73.66013,41.00063],[-73.655,41.01184],[-73.72761,41.10079],[-73.48283,41.21298],[-73.5508,41.2957],[-73.48746,42.0497],[-73.50942,42.0867],[-73.26498,42.74494],[-73.25409,43.57117],[-73.33649,43.62686],[-73.43811,43.57117],[-73.35571,43.77182],[-73.4436,44.07055],[-73.3255,44.25969],[-73.35073,45.01056],[-74.32699,44.99029],[-74.66689,45.00646],[-74.8447,45.00606],[-74.99101,44.98051],[-75.01363,44.95608],[-75.2193,44.87821],[-75.41441,44.76614],[-75.76813,44.51537],[-75.8217,44.43176],[-75.95947,44.34463],[-76.00018,44.34896],[-76.16285,44.28262],[-76.1664,44.23051],[-76.244,44.19643],[-76.31222,44.19894],[-76.35324,44.13493],[-76.43859,44.09393],[-76.79706,43.63099],[-79.25796,43.54052],[-79.06921,43.26183],[-79.05512,43.25375],[-79.05178,43.17029],[-79.04652,43.16396],[-79.0427,43.13934],[-79.06881,43.12029],[-79.05782,43.11153],[-79.06276,43.09706],[-79.07636,43.07797],[-79.01055,43.06659],[-78.99941,43.05612],[-79.02424,43.01983],[-79.02074,42.98444],[-78.91213,42.93838],[-78.89518,42.84543],[-79.78216,42.57325]]]}},{"type":"Feature","properties":{"id":"US-NC"},"geometry":{"type":"Polygon","coordinates":[[[-84.32144,34.98837],[-83.10796,35.0011],[-82.39746,35.20044],[-81.04889,35.15105],[-81.04889,35.04993],[-80.9198,35.08815],[-80.79346,34.94193],[-80.78796,34.82252],[-79.68109,34.81124],[-77.99552,33.38485],[-74.86753,35.41538],[-75.79776,36.55091],[-80.30148,36.54837],[-81.67716,36.58851],[-81.72455,36.33806],[-81.82617,36.36682],[-82.03353,36.11957],[-82.12211,36.10598],[-82.14357,36.14549],[-82.21464,36.15783],[-82.38698,36.09821],[-82.56311,35.95313],[-82.64191,36.06989],[-82.97424,35.78744],[-83.13904,35.76961],[-83.50159,35.56433],[-83.74603,35.56209],[-84.0097,35.4324],[-84.03717,35.29129],[-84.1333,35.24419],[-84.22943,35.27335],[-84.28711,35.224],[-84.32144,34.98837]]]}},{"type":"Feature","properties":{"id":"US-PA"},"geometry":{"type":"Polygon","coordinates":[[[-80.55605,42.3348],[-80.51902,40.63803],[-80.52292,39.72222],[-79.47663,39.72086],[-75.78987,39.72204],[-75.77476,39.7223],[-75.76,39.75002],[-75.74043,39.77378],[-75.72532,39.78644],[-75.71434,39.79515],[-75.68172,39.81387],[-75.65975,39.82337],[-75.62713,39.83259],[-75.59898,39.83734],[-75.57632,39.83945],[-75.53959,39.83945],[-75.49942,39.83365],[-75.46783,39.82548],[-75.43796,39.81414],[-75.41621,39.80165],[-75.33745,39.84842],[-75.26809,39.85014],[-75.13625,39.89097],[-75.12785,39.91073],[-75.13815,39.93627],[-75.13196,39.96154],[-75.0712,39.97969],[-75.04733,40.00955],[-74.92683,40.07105],[-74.91224,40.06987],[-74.8628,40.08432],[-74.83774,40.10244],[-74.82486,40.12829],[-74.7907,40.12187],[-74.75843,40.13447],[-74.74127,40.13473],[-74.72341,40.14864],[-74.72273,40.16045],[-74.73852,40.17855],[-74.75843,40.18774],[-74.76701,40.2095],[-74.78349,40.2234],[-74.84426,40.25013],[-74.86315,40.28837],[-74.89198,40.31246],[-74.91396,40.3177],[-74.94451,40.34152],[-74.94966,40.36611],[-74.96477,40.39592],[-74.98743,40.40664],[-75.03137,40.40534],[-75.06124,40.4218],[-75.07017,40.45525],[-75.06365,40.48581],[-75.06708,40.5388],[-75.10347,40.56984],[-75.12682,40.57453],[-75.14776,40.57453],[-75.16458,40.56332],[-75.18106,40.56619],[-75.1948,40.57844],[-75.19068,40.59435],[-75.19994,40.61259],[-75.19102,40.62093],[-75.19068,40.63865],[-75.20029,40.64881],[-75.18003,40.66939],[-75.17772,40.67783],[-75.19763,40.68233],[-75.20372,40.691],[-75.18175,40.73107],[-75.19617,40.75084],[-75.17076,40.77893],[-75.133,40.77373],[-75.08219,40.82726],[-75.09935,40.84804],[-75.06777,40.84804],[-75.05266,40.8657],[-75.13849,40.98347],[-75.02931,41.03995],[-74.88237,41.1848],[-74.83431,41.28136],[-74.76152,41.33913],[-74.6956,41.35872],[-74.74274,41.42288],[-74.98718,41.48258],[-75.0531,41.59155],[-75.08057,41.80278],[-75.3772,42.00515],[-79.76349,42.00107],[-79.78216,42.57325],[-80.55605,42.3348]]]}},{"type":"Feature","properties":{"id":"US-OR"},"geometry":{"type":"Polygon","coordinates":[[[-125.69978,42.00605],[-120.0016,41.99495],[-117.02619,42.00013],[-117.02679,43.83282],[-117.01314,43.86213],[-116.9837,43.86621],[-116.9625,43.92769],[-116.97426,43.96681],[-116.93859,43.98654],[-116.93444,44.02346],[-116.94465,44.0366],[-116.97358,44.0491],[-116.97632,44.08897],[-116.94126,44.09485],[-116.8947,44.1556],[-116.90457,44.18171],[-116.97155,44.19565],[-116.97521,44.24329],[-117.03752,44.24882],[-117.05091,44.22896],[-117.10953,44.28054],[-117.15022,44.25577],[-117.20249,44.27501],[-117.22223,44.30069],[-117.18841,44.33993],[-117.23982,44.3835],[-117.21742,44.48511],[-117.15626,44.53093],[-117.05051,44.74298],[-116.9379,44.78588],[-116.83353,44.92995],[-116.84835,44.95744],[-116.83213,44.97624],[-116.85968,44.97597],[-116.84552,45.00431],[-116.84864,45.02321],[-116.79376,45.06485],[-116.7711,45.10818],[-116.75388,45.11342],[-116.72458,45.16606],[-116.67291,45.32167],[-116.5306,45.53791],[-116.46274,45.60746],[-116.54789,45.7533],[-116.6978,45.81994],[-116.77585,45.82129],[-116.91799,45.99567],[-118.97781,46.00101],[-119.12097,45.92584],[-119.26826,45.94086],[-119.4928,45.90647],[-119.6109,45.92654],[-119.67064,45.85772],[-119.96658,45.82375],[-120.16502,45.7663],[-120.21103,45.72701],[-120.50079,45.69537],[-120.56053,45.74043],[-120.611,45.74668],[-120.89493,45.65219],[-121.07431,45.65136],[-121.14899,45.60609],[-121.1907,45.60971],[-121.21095,45.66672],[-121.34674,45.70592],[-121.41214,45.69347],[-121.53008,45.7227],[-121.71375,45.69287],[-121.81228,45.70629],[-121.90097,45.67502],[-121.90429,45.657],[-121.98446,45.62386],[-122.23595,45.55275],[-122.32384,45.54697],[-122.38014,45.5739],[-122.43919,45.5638],[-122.47456,45.57942],[-122.67609,45.61786],[-122.76501,45.65747],[-122.77668,45.68745],[-122.76157,45.7378],[-122.76501,45.76654],[-122.79591,45.81203],[-122.78492,45.86704],[-122.81067,45.91089],[-122.80466,45.94086],[-122.87762,46.03414],[-122.8941,46.07797],[-122.96688,46.10654],[-123.00945,46.13605],[-123.13442,46.18647],[-123.17837,46.18694],[-123.28754,46.14367],[-123.38093,46.14842],[-123.43243,46.18219],[-123.42968,46.23351],[-123.47431,46.26864],[-123.58349,46.25583],[-123.87806,46.23588],[-123.92818,46.23968],[-124.02981,46.30281],[-124.03873,46.26295],[-124.14997,46.26295],[-125.2772,46.2631],[-125.69978,42.00605]]]}},{"type":"Feature","properties":{"id":"US-WA"},"geometry":{"type":"Polygon","coordinates":[[[-125.2772,46.2631],[-124.14997,46.26295],[-124.03873,46.26295],[-124.02981,46.30281],[-123.92818,46.23968],[-123.87806,46.23588],[-123.58349,46.25583],[-123.47431,46.26864],[-123.42968,46.23351],[-123.43243,46.18219],[-123.38093,46.14842],[-123.28754,46.14367],[-123.17837,46.18694],[-123.13442,46.18647],[-123.00945,46.13605],[-122.96688,46.10654],[-122.8941,46.07797],[-122.87762,46.03414],[-122.80466,45.94086],[-122.81067,45.91089],[-122.78492,45.86704],[-122.79591,45.81203],[-122.76501,45.76654],[-122.76157,45.7378],[-122.77668,45.68745],[-122.76501,45.65747],[-122.67609,45.61786],[-122.47456,45.57942],[-122.43919,45.5638],[-122.38014,45.5739],[-122.32384,45.54697],[-122.23595,45.55275],[-121.98446,45.62386],[-121.90429,45.657],[-121.90097,45.67502],[-121.81228,45.70629],[-121.71375,45.69287],[-121.53008,45.7227],[-121.41214,45.69347],[-121.34674,45.70592],[-121.21095,45.66672],[-121.1907,45.60971],[-121.14899,45.60609],[-121.07431,45.65136],[-120.89493,45.65219],[-120.611,45.74668],[-120.56053,45.74043],[-120.50079,45.69537],[-120.21103,45.72701],[-120.16502,45.7663],[-119.96658,45.82375],[-119.67064,45.85772],[-119.6109,45.92654],[-119.4928,45.90647],[-119.26826,45.94086],[-119.12097,45.92584],[-118.97781,46.00101],[-116.91799,45.99567],[-116.94958,46.07004],[-116.98116,46.08242],[-116.92348,46.16048],[-117.03472,46.34138],[-117.04811,46.34281],[-117.06184,46.34873],[-117.06287,46.3665],[-117.04914,46.37787],[-117.03575,46.40794],[-117.03644,46.42309],[-117.04021,46.4257],[-117.03266,49.00056],[-123.3218,49.00227],[-122.98526,48.79206],[-123.26565,48.6959],[-123.15614,48.22053],[-125.03842,48.53282],[-125.2772,46.2631]]]}},{"type":"Feature","properties":{"id":"US-OK"},"geometry":{"type":"Polygon","coordinates":[[[-103.00462,36.99417],[-103.00325,36.50047],[-100.00134,36.50078],[-100.00039,34.55895],[-99.93438,34.58107],[-99.76444,34.42841],[-99.71638,34.40462],[-99.71123,34.38365],[-99.61132,34.36708],[-99.57081,34.41721],[-99.50626,34.409],[-99.40515,34.36693],[-99.37854,34.46521],[-99.23916,34.36439],[-99.17942,34.21092],[-98.99265,34.21092],[-98.74923,34.11689],[-98.59697,34.15881],[-98.49277,34.06572],[-98.409,34.09074],[-98.37879,34.15384],[-98.16662,34.11462],[-98.12061,34.15725],[-98.0976,34.13594],[-98.12061,34.07653],[-98.08697,34.00483],[-97.95033,33.99459],[-97.98191,33.89547],[-97.86208,33.84415],[-97.68116,33.99117],[-97.59258,33.95701],[-97.59172,33.89618],[-97.48289,33.91798],[-97.45251,33.89718],[-97.45576,33.82433],[-97.3605,33.82648],[-97.30762,33.88521],[-97.26849,33.85899],[-97.23553,33.91713],[-97.1854,33.90402],[-97.16686,33.84473],[-97.20394,33.81735],[-97.18334,33.75115],[-97.15038,33.72317],[-97.08943,33.72503],[-97.09614,33.80252],[-97.05221,33.82252],[-97.08515,33.85614],[-97.02691,33.84657],[-96.98215,33.89376],[-96.99245,33.93365],[-96.93958,33.95871],[-96.90113,33.94163],[-96.86954,33.85386],[-96.81118,33.87267],[-96.75762,33.82648],[-96.70887,33.8396],[-96.6663,33.91599],[-96.58733,33.89262],[-96.62441,33.85157],[-96.57514,33.81821],[-96.52639,33.82305],[-96.507,33.77342],[-96.42735,33.78026],[-96.35044,33.68661],[-96.32023,33.7049],[-96.29002,33.7757],[-96.22632,33.75157],[-96.17363,33.76071],[-96.16367,33.82135],[-95.91373,33.88221],[-95.84129,33.8366],[-95.57041,33.93992],[-95.54295,33.89661],[-95.43034,33.87381],[-95.28168,33.88178],[-95.2216,33.96726],[-95.10349,33.92397],[-94.88651,33.76657],[-94.48448,33.64004],[-94.43092,35.38371],[-94.61906,36.49995],[-94.61795,36.9986],[-102.04214,36.99314],[-103.00462,36.99417]]]}},{"type":"Feature","properties":{"id":"US-TX"},"geometry":{"type":"Polygon","coordinates":[[[-106.64522,31.89178],[-106.60333,31.82938],[-106.5436,31.80546],[-106.529,31.784],[-106.52266,31.77509],[-106.51251,31.76922],[-106.50962,31.76155],[-106.50111,31.75714],[-106.48815,31.74769],[-106.47206,31.7509],[-106.46726,31.75998],[-106.45244,31.76523],[-106.43419,31.75478],[-106.41773,31.75196],[-106.38003,31.73151],[-106.3718,31.71165],[-106.34864,31.69663],[-106.33419,31.66303],[-106.30319,31.62214],[-106.30122,31.60989],[-106.27924,31.56061],[-106.24612,31.54193],[-106.23711,31.51262],[-106.21204,31.46981],[-106.08158,31.39907],[-106.00363,31.39181],[-105.78426,31.19518],[-104.87514,30.53003],[-104.67567,30.1469],[-104.5171,29.64671],[-104.3969,29.57105],[-104.39363,29.55396],[-104.37752,29.54255],[-103.15787,28.93865],[-102.60596,29.8192],[-101.47277,29.7744],[-101.05686,29.44738],[-101.01128,29.36947],[-100.96725,29.3477],[-100.94579,29.34523],[-100.94053,29.33399],[-100.87982,29.296],[-100.79696,29.24688],[-100.67294,29.09744],[-100.63689,28.90812],[-100.59809,28.88197],[-100.52313,28.75598],[-100.5075,28.74066],[-100.51222,28.70679],[-100.50029,28.66117],[-100.35255,28.48679],[-100.29247,28.27883],[-99.73972,27.69568],[-99.55409,27.61314],[-99.51478,27.55836],[-99.52955,27.49747],[-99.50208,27.50021],[-99.48045,27.49016],[-99.482,27.47128],[-99.49744,27.43746],[-99.53573,27.30926],[-99.43313,27.2096],[-99.44515,27.04032],[-99.26044,26.80936],[-99.08477,26.39849],[-99.03053,26.41249],[-99.00546,26.3925],[-98.82116,26.35465],[-98.66477,26.23984],[-98.60298,26.25462],[-98.49002,26.21335],[-98.44505,26.20627],[-98.34154,26.15058],[-98.31167,26.10781],[-98.28429,26.1055],[-98.24603,26.07191],[-97.97017,26.05232],[-97.95155,26.0625],[-97.86337,26.05948],[-97.64528,26.01544],[-97.51773,25.88671],[-97.50821,25.88911],[-97.49765,25.89934],[-97.49743,25.8866],[-97.45941,25.87841],[-97.4304,25.84516],[-97.37246,25.84373],[-97.35946,25.92189],[-97.27535,25.94592],[-97.13927,25.96583],[-96.92418,25.97377],[-93.77551,29.43998],[-93.83525,29.68178],[-93.92244,29.81124],[-93.71508,30.05521],[-93.70959,30.28791],[-93.76452,30.33771],[-93.52008,31.03684],[-93.89258,31.87297],[-94.04467,32.00379],[-94.04366,33.01929],[-94.04313,33.56568],[-94.38423,33.56455],[-94.48448,33.64004],[-94.88651,33.76657],[-95.10349,33.92397],[-95.2216,33.96726],[-95.28168,33.88178],[-95.43034,33.87381],[-95.54295,33.89661],[-95.57041,33.93992],[-95.84129,33.8366],[-95.91373,33.88221],[-96.16367,33.82135],[-96.17363,33.76071],[-96.22632,33.75157],[-96.29002,33.7757],[-96.32023,33.7049],[-96.35044,33.68661],[-96.42735,33.78026],[-96.507,33.77342],[-96.52639,33.82305],[-96.57514,33.81821],[-96.62441,33.85157],[-96.58733,33.89262],[-96.6663,33.91599],[-96.70887,33.8396],[-96.75762,33.82648],[-96.81118,33.87267],[-96.86954,33.85386],[-96.90113,33.94163],[-96.93958,33.95871],[-96.99245,33.93365],[-96.98215,33.89376],[-97.02691,33.84657],[-97.08515,33.85614],[-97.05221,33.82252],[-97.09614,33.80252],[-97.08943,33.72503],[-97.15038,33.72317],[-97.18334,33.75115],[-97.20394,33.81735],[-97.16686,33.84473],[-97.1854,33.90402],[-97.23553,33.91713],[-97.26849,33.85899],[-97.30762,33.88521],[-97.3605,33.82648],[-97.45576,33.82433],[-97.45251,33.89718],[-97.48289,33.91798],[-97.59172,33.89618],[-97.59258,33.95701],[-97.68116,33.99117],[-97.86208,33.84415],[-97.98191,33.89547],[-97.95033,33.99459],[-98.08697,34.00483],[-98.12061,34.07653],[-98.0976,34.13594],[-98.12061,34.15725],[-98.16662,34.11462],[-98.37879,34.15384],[-98.409,34.09074],[-98.49277,34.06572],[-98.59697,34.15881],[-98.74923,34.11689],[-98.99265,34.21092],[-99.17942,34.21092],[-99.23916,34.36439],[-99.37854,34.46521],[-99.40515,34.36693],[-99.50626,34.409],[-99.57081,34.41721],[-99.61132,34.36708],[-99.71123,34.38365],[-99.71638,34.40462],[-99.76444,34.42841],[-99.93438,34.58107],[-100.00039,34.55895],[-100.00134,36.50078],[-103.00325,36.50047],[-103.04239,36.49992],[-103.06402,31.99986],[-106.61981,32.00074],[-106.62187,31.92151],[-106.64522,31.89178]]]}},{"type":"Feature","properties":{"id":"US-OH"},"geometry":{"type":"Polygon","coordinates":[[[-84.81884,39.10708],[-84.73634,39.1447],[-84.68432,39.09844],[-84.62252,39.07392],[-84.56365,39.0865],[-84.54769,39.09909],[-84.51919,39.09016],[-84.49524,39.09889],[-84.47731,39.11987],[-84.45181,39.1178],[-84.43396,39.09436],[-84.43182,39.05745],[-84.41035,39.0446],[-84.33088,39.02758],[-84.30117,38.99445],[-84.28934,38.9522],[-84.23388,38.88176],[-84.23355,38.82673],[-84.21003,38.8044],[-84.06858,38.77135],[-83.94601,38.78299],[-83.85915,38.75368],[-83.83323,38.7119],[-83.78688,38.69703],[-83.77092,38.65522],[-83.66088,38.62518],[-83.62414,38.67621],[-83.52424,38.70292],[-83.32803,38.6394],[-83.31378,38.5997],[-83.28615,38.59728],[-83.24511,38.63116],[-83.20117,38.61614],[-83.14933,38.62143],[-83.10778,38.67621],[-83.0556,38.69336],[-83.02951,38.72872],[-82.97784,38.72516],[-82.89492,38.7555],[-82.8702,38.73943],[-82.87844,38.69121],[-82.84549,38.58502],[-82.79879,38.56355],[-82.72601,38.55603],[-82.69426,38.53769],[-82.65907,38.49726],[-82.61032,38.4685],[-82.59692,38.42382],[-82.58148,38.4084],[-82.54406,38.39937],[-82.40604,38.43718],[-82.37857,38.43288],[-82.31609,38.44874],[-82.28914,38.58105],[-82.26339,38.59701],[-82.20125,38.59017],[-82.16932,38.61029],[-82.18992,38.73801],[-82.21738,38.79583],[-82.14048,38.8375],[-82.14177,38.89737],[-82.08357,38.97582],[-82.04916,38.9953],[-82.03628,39.02391],[-82.01173,39.02971],[-81.99302,39.01918],[-81.98015,38.99217],[-81.94908,38.99577],[-81.92728,38.97982],[-81.89801,38.9289],[-81.93191,38.8951],[-81.895,38.87152],[-81.84368,38.9013],[-81.83793,38.93784],[-81.81793,38.94645],[-81.77716,38.91982],[-81.75355,38.9323],[-81.78128,38.96388],[-81.76128,39.01876],[-81.80806,39.05518],[-81.81166,39.08283],[-81.77441,39.0763],[-81.74291,39.09822],[-81.75218,39.1833],[-81.68875,39.22507],[-81.69519,39.25977],[-81.66232,39.27618],[-81.56541,39.26761],[-81.55571,39.34286],[-81.48233,39.38838],[-81.45495,39.41013],[-81.40886,39.38572],[-81.38431,39.34292],[-81.35599,39.3412],[-81.26217,39.38625],[-81.21351,39.38659],[-81.1845,39.43153],[-81.12948,39.44646],[-81.06854,39.51688],[-80.99249,39.57255],[-80.92726,39.61653],[-80.878,39.6197],[-80.86564,39.65202],[-80.86263,39.69292],[-80.82521,39.70751],[-80.86881,39.76711],[-80.82015,39.80457],[-80.82436,39.84477],[-80.79062,39.86396],[-80.78899,39.87323],[-80.80839,39.90953],[-80.79543,39.91921],[-80.76281,39.90651],[-80.75329,39.91222],[-80.76135,39.9535],[-80.7368,39.97383],[-80.73929,40.01374],[-80.72822,40.04292],[-80.73929,40.07688],[-80.70402,40.10541],[-80.70625,40.14633],[-80.66762,40.1988],[-80.65346,40.24448],[-80.61492,40.26623],[-80.61484,40.28934],[-80.59879,40.31729],[-80.61046,40.34301],[-80.60591,40.3742],[-80.63261,40.39081],[-80.61209,40.40506],[-80.61424,40.43216],[-80.59681,40.46275],[-80.59639,40.47971],[-80.61287,40.49485],[-80.62935,40.53452],[-80.66608,40.58043],[-80.6345,40.61458],[-80.59948,40.6237],[-80.57837,40.6125],[-80.51902,40.63803],[-80.55605,42.3348],[-82.71775,41.66281],[-83.08506,41.89693],[-83.42355,41.73233],[-84.80645,41.69747],[-84.81884,39.10708]]]}},{"type":"Feature","properties":{"id":"US-RI"},"geometry":{"type":"Polygon","coordinates":[[[-71.85736,41.32188],[-71.6391,40.94332],[-71.10101,41.43444],[-71.13123,41.591],[-71.14153,41.60717],[-71.13432,41.65952],[-71.19577,41.67465],[-71.26135,41.75231],[-71.32898,41.7815],[-71.341,41.79814],[-71.33894,41.89916],[-71.3822,41.89277],[-71.38117,42.0194],[-71.8001,42.00779],[-71.78724,41.65617],[-71.79866,41.41592],[-71.81926,41.41952],[-71.84363,41.40948],[-71.84191,41.39455],[-71.83294,41.38756],[-71.83333,41.38245],[-71.83097,41.37885],[-71.83273,41.37584],[-71.83195,41.3701],[-71.83856,41.36466],[-71.82955,41.34199],[-71.85736,41.32188]]]}},{"type":"Feature","properties":{"id":"US-SC"},"geometry":{"type":"Polygon","coordinates":[[[-83.35447,34.72814],[-83.34829,34.69455],[-83.22916,34.61096],[-83.17148,34.60728],[-83.16873,34.59259],[-83.03655,34.48625],[-83.00085,34.47238],[-82.90231,34.48625],[-82.87519,34.47408],[-82.83365,34.36419],[-82.79657,34.34039],[-82.78043,34.29672],[-82.75057,34.2709],[-82.74095,34.20789],[-82.71658,34.14882],[-82.64311,34.0951],[-82.64105,34.06809],[-82.59538,34.02855],[-82.56414,33.95567],[-82.39008,33.85651],[-82.3015,33.80062],[-82.20022,33.66214],[-82.19747,33.63013],[-82.1343,33.59095],[-82.10752,33.59782],[-82.05019,33.56464],[-81.99766,33.51342],[-81.9877,33.48794],[-81.92968,33.46674],[-81.91354,33.43953],[-81.93964,33.34609],[-81.85587,33.30248],[-81.85037,33.24622],[-81.78858,33.20716],[-81.77553,33.2198],[-81.75974,33.19912],[-81.7721,33.18303],[-81.76248,33.15947],[-81.70824,33.11807],[-81.64163,33.09276],[-81.62103,33.09564],[-81.49538,33.00988],[-81.50087,32.93557],[-81.45555,32.84851],[-81.42809,32.84101],[-81.39581,32.65101],[-81.41847,32.63193],[-81.38277,32.59086],[-81.3244,32.55788],[-81.28595,32.55904],[-81.27771,32.53531],[-81.19257,32.46292],[-81.20767,32.42409],[-81.11772,32.29127],[-81.15823,32.24017],[-81.11978,32.1937],[-81.11635,32.11638],[-81.05386,32.08497],[-81.00511,32.1001],[-80.92066,32.03667],[-80.49837,32.0326],[-77.99552,33.38485],[-79.68109,34.81124],[-80.78796,34.82252],[-80.79346,34.94193],[-80.9198,35.08815],[-81.04889,35.04993],[-81.04889,35.15105],[-82.39746,35.20044],[-83.10796,35.0011],[-83.09869,34.99098],[-83.12616,34.9544],[-83.11689,34.94033],[-83.2374,34.87417],[-83.32357,34.78878],[-83.32048,34.75861],[-83.35447,34.72814]]]}},{"type":"Feature","properties":{"id":"US-TN"},"geometry":{"type":"Polygon","coordinates":[[[-90.3004,34.9951],[-88.20029,34.99532],[-88.20305,35.00664],[-85.60478,34.98639],[-84.32144,34.98837],[-84.28711,35.224],[-84.22943,35.27335],[-84.1333,35.24419],[-84.03717,35.29129],[-84.0097,35.4324],[-83.74603,35.56209],[-83.50159,35.56433],[-83.13904,35.76961],[-82.97424,35.78744],[-82.64191,36.06989],[-82.56311,35.95313],[-82.38698,36.09821],[-82.21464,36.15783],[-82.14357,36.14549],[-82.12211,36.10598],[-82.03353,36.11957],[-81.82617,36.36682],[-81.72455,36.33806],[-81.67716,36.58851],[-81.64833,36.61206],[-81.92299,36.61647],[-81.93466,36.5947],[-83.67455,36.60056],[-83.69109,36.58281],[-84.78492,36.60367],[-85.23811,36.62655],[-85.48805,36.61552],[-86.50651,36.65244],[-86.56436,36.63343],[-86.58943,36.65272],[-87.15969,36.64197],[-87.80993,36.63792],[-88.06536,36.67979],[-88.05987,36.49674],[-89.29996,36.50742],[-89.41635,36.49942],[-89.44175,36.46367],[-89.49411,36.46478],[-89.46664,36.55005],[-89.51454,36.57997],[-89.57016,36.55667],[-89.52268,36.46762],[-89.5474,36.43117],[-89.57212,36.24754],[-89.70336,36.24039],[-89.58997,36.1478],[-89.6038,36.12012],[-89.73701,36.00048],[-89.64638,35.91489],[-89.67384,35.8804],[-89.73564,35.91044],[-89.77409,35.87261],[-89.70131,35.83366],[-89.76688,35.77492],[-89.93819,35.71276],[-89.93579,35.67124],[-89.8843,35.64139],[-90.10299,35.33359],[-90.07072,35.1314],[-90.13389,35.12802],[-90.3004,34.9951]]]}},{"type":"Feature","properties":{"id":"US-VT"},"geometry":{"type":"Polygon","coordinates":[[[-73.4436,44.07055],[-73.35571,43.77182],[-73.43811,43.57117],[-73.33649,43.62686],[-73.25409,43.57117],[-73.26498,42.74494],[-72.45835,42.72702],[-72.49114,42.77499],[-72.51465,42.76522],[-72.50753,42.78028],[-72.53963,42.81095],[-72.55332,42.85501],[-72.55182,42.88514],[-72.5313,42.89577],[-72.52504,42.92418],[-72.53208,42.9534],[-72.46384,42.97915],[-72.46092,43.05479],[-72.43483,43.0791],[-72.43174,43.11727],[-72.44264,43.13819],[-72.45015,43.13904],[-72.45611,43.14654],[-72.43466,43.25564],[-72.40419,43.27982],[-72.39362,43.35659],[-72.41491,43.36557],[-72.37913,43.49278],[-72.39784,43.51058],[-72.37278,43.57703],[-72.32883,43.6019],[-72.32866,43.63557],[-72.31287,43.64085],[-72.31321,43.6575],[-72.30342,43.66917],[-72.30171,43.70256],[-72.27081,43.73302],[-72.23433,43.74666],[-72.20549,43.77115],[-72.18395,43.80616],[-72.18206,43.84331],[-72.17124,43.88409],[-72.1182,43.92188],[-72.10859,43.99898],[-72.04,44.08408],[-72.04198,44.15031],[-72.03838,44.1572],[-72.0522,44.16872],[-72.05924,44.2613],[-72.05932,44.28103],[-72.01143,44.32114],[-71.9782,44.33411],[-71.90679,44.34688],[-71.86834,44.33706],[-71.8147,44.35552],[-71.79788,44.39699],[-71.70536,44.4125],[-71.67695,44.42758],[-71.639,44.47343],[-71.59438,44.4897],[-71.59231,44.56351],[-71.5597,44.56405],[-71.54287,44.58601],[-71.6324,44.75136],[-71.57369,44.79255],[-71.54966,44.86219],[-71.49618,44.90788],[-71.53841,44.99248],[-71.5282,45.00055],[-71.50713,45.00799],[-71.48649,45.00092],[-71.46503,45.0136],[-71.50067,45.01357],[-72.08662,45.00571],[-72.52504,45.00826],[-72.69824,45.01566],[-73.35073,45.01056],[-73.3255,44.25969],[-73.4436,44.07055]]]}},{"type":"Feature","properties":{"id":"US-VA"},"geometry":{"type":"Polygon","coordinates":[[[-83.67455,36.60056],[-81.93466,36.5947],[-81.92299,36.61647],[-81.64833,36.61206],[-81.67716,36.58851],[-80.30148,36.54837],[-75.79776,36.55091],[-75.16879,38.02735],[-75.62472,37.99597],[-75.65768,37.94509],[-75.88153,37.90934],[-76.23034,37.8985],[-76.61074,38.15704],[-76.92248,38.23475],[-77.03784,38.42973],[-77.20813,38.35223],[-77.28778,38.38454],[-77.28915,38.50178],[-77.21225,38.6038],[-77.12161,38.63385],[-77.12023,38.68103],[-77.08041,38.70568],[-77.04196,38.70568],[-77.03906,38.79153],[-77.03021,38.86133],[-77.04592,38.87557],[-77.0491,38.87343],[-77.05493,38.87964],[-77.05957,38.88152],[-77.06759,38.89895],[-77.07047,38.90106],[-77.08897,38.90436],[-77.10201,38.91264],[-77.10592,38.91912],[-77.11536,38.92787],[-77.11962,38.93441],[-77.1477,38.9699],[-77.22186,38.97417],[-77.25619,39.00192],[-77.23971,39.02006],[-77.30975,39.05846],[-77.46081,39.07872],[-77.48416,39.11282],[-77.51849,39.12135],[-77.52449,39.14637],[-77.51136,39.17825],[-77.47729,39.18844],[-77.45532,39.22462],[-77.49102,39.25227],[-77.54596,39.27247],[-77.56655,39.30861],[-77.61462,39.3033],[-77.68054,39.32667],[-77.7232,39.32234],[-77.82818,39.13337],[-78.34728,39.46705],[-78.34934,39.42676],[-78.3617,39.40872],[-78.34385,39.38909],[-78.3672,39.35883],[-78.33973,39.35352],[-78.35947,39.31969],[-78.42075,39.25736],[-78.40084,39.24566],[-78.4059,39.23079],[-78.43877,39.19807],[-78.40384,39.16773],[-78.5725,39.03156],[-78.55396,39.01716],[-78.6034,38.96539],[-78.62743,38.98408],[-78.69198,38.91519],[-78.71944,38.90557],[-78.71807,38.93602],[-78.7421,38.92748],[-78.75858,38.90183],[-78.78742,38.88794],[-78.86982,38.7633],[-78.99479,38.84998],[-79.09504,38.7092],[-79.08543,38.68133],[-79.10191,38.65345],[-79.12663,38.66418],[-79.21864,38.48918],[-79.30515,38.41282],[-79.47681,38.458],[-79.53587,38.55257],[-79.6471,38.59015],[-79.67319,38.53646],[-79.66221,38.51175],[-79.70066,38.49133],[-79.68967,38.45693],[-79.69517,38.42358],[-79.73362,38.37838],[-79.72538,38.36115],[-79.73911,38.35146],[-79.76383,38.35792],[-79.80915,38.30837],[-79.7858,38.26849],[-79.91901,38.18326],[-79.93412,38.10334],[-80.00278,37.99519],[-80.17994,37.85762],[-80.29924,37.68327],[-80.22251,37.62522],[-80.32413,37.56646],[-80.2953,37.51746],[-80.47245,37.42373],[-80.51228,37.48042],[-80.77011,37.37274],[-80.78384,37.39416],[-80.8059,37.39798],[-80.81225,37.40848],[-80.85835,37.43136],[-80.88306,37.38337],[-80.84873,37.34735],[-80.91602,37.30913],[-80.97782,37.29056],[-80.98606,37.30148],[-81.12339,37.27526],[-81.16321,37.26408],[-81.2257,37.23483],[-81.31977,37.29931],[-81.36337,37.33938],[-81.42002,37.27089],[-81.50242,37.2534],[-81.55735,37.20966],[-81.6782,37.20201],[-81.76334,37.27744],[-81.8581,37.28509],[-81.87595,37.32988],[-81.92814,37.36154],[-81.93775,37.43791],[-81.99268,37.4608],[-81.99543,37.4826],[-81.9405,37.50766],[-81.96873,37.53756],[-82.34698,37.2744],[-82.72447,37.12145],[-82.72447,37.04243],[-82.86059,36.98316],[-82.85862,36.92821],[-82.88806,36.88188],[-83.08032,36.84701],[-83.1277,36.77684],[-83.13466,36.74328],[-83.28134,36.72003],[-83.42125,36.66798],[-83.5239,36.66716],[-83.67455,36.60056]]]}},{"type":"Feature","properties":{"id":"US-WV"},"geometry":{"type":"Polygon","coordinates":[[[-82.64361,38.1673],[-82.63263,38.13922],[-82.61881,38.12226],[-82.60233,38.11943],[-82.5862,38.10734],[-82.52268,38.0134],[-82.46406,37.98317],[-82.50792,37.94108],[-82.41702,37.84593],[-82.39969,37.83019],[-82.40179,37.81036],[-82.33772,37.77607],[-82.3336,37.74119],[-82.30682,37.70765],[-82.30433,37.67583],[-82.29467,37.67835],[-82.29184,37.66605],[-82.28334,37.67604],[-82.27189,37.6635],[-82.25532,37.65684],[-82.2394,37.66153],[-82.21683,37.63924],[-82.21957,37.63313],[-82.20193,37.61712],[-82.17786,37.64013],[-82.18116,37.61644],[-82.16494,37.62283],[-82.16936,37.61015],[-82.14391,37.56567],[-82.10237,37.55185],[-82.06297,37.53545],[-81.96873,37.53756],[-81.9405,37.50766],[-81.99543,37.4826],[-81.99268,37.4608],[-81.93775,37.43791],[-81.92814,37.36154],[-81.87595,37.32988],[-81.8581,37.28509],[-81.76334,37.27744],[-81.6782,37.20201],[-81.55735,37.20966],[-81.50242,37.2534],[-81.42002,37.27089],[-81.36337,37.33938],[-81.31977,37.29931],[-81.2257,37.23483],[-81.16321,37.26408],[-81.12339,37.27526],[-80.98606,37.30148],[-80.97782,37.29056],[-80.91602,37.30913],[-80.84873,37.34735],[-80.88306,37.38337],[-80.85835,37.43136],[-80.81225,37.40848],[-80.8059,37.39798],[-80.78384,37.39416],[-80.77011,37.37274],[-80.51228,37.48042],[-80.47245,37.42373],[-80.2953,37.51746],[-80.32413,37.56646],[-80.22251,37.62522],[-80.29924,37.68327],[-80.17994,37.85762],[-80.00278,37.99519],[-79.93412,38.10334],[-79.91901,38.18326],[-79.7858,38.26849],[-79.80915,38.30837],[-79.76383,38.35792],[-79.73911,38.35146],[-79.72538,38.36115],[-79.73362,38.37838],[-79.69517,38.42358],[-79.68967,38.45693],[-79.70066,38.49133],[-79.66221,38.51175],[-79.67319,38.53646],[-79.6471,38.59015],[-79.53587,38.55257],[-79.47681,38.458],[-79.30515,38.41282],[-79.21864,38.48918],[-79.12663,38.66418],[-79.10191,38.65345],[-79.08543,38.68133],[-79.09504,38.7092],[-78.99479,38.84998],[-78.86982,38.7633],[-78.78742,38.88794],[-78.75858,38.90183],[-78.7421,38.92748],[-78.71807,38.93602],[-78.71944,38.90557],[-78.69198,38.91519],[-78.62743,38.98408],[-78.6034,38.96539],[-78.55396,39.01716],[-78.5725,39.03156],[-78.40384,39.16773],[-78.43877,39.19807],[-78.4059,39.23079],[-78.40084,39.24566],[-78.42075,39.25736],[-78.35947,39.31969],[-78.33973,39.35352],[-78.3672,39.35883],[-78.34385,39.38909],[-78.3617,39.40872],[-78.34934,39.42676],[-78.34728,39.46705],[-77.82818,39.13337],[-77.7232,39.32234],[-77.75736,39.33861],[-77.73736,39.39017],[-77.75289,39.42632],[-77.80028,39.43519],[-77.79659,39.47834],[-77.7656,39.49562],[-77.84216,39.49842],[-77.88885,39.55774],[-77.83392,39.56621],[-77.83083,39.60806],[-77.91872,39.60535],[-77.93632,39.6234],[-77.95117,39.6035],[-78.0055,39.60284],[-78.0515,39.65235],[-78.10086,39.68182],[-78.1812,39.6986],[-78.24471,39.64839],[-78.26643,39.61957],[-78.35706,39.64165],[-78.38959,39.61977],[-78.43156,39.62208],[-78.39706,39.5826],[-78.45817,39.58584],[-78.41903,39.54925],[-78.4619,39.53714],[-78.46422,39.51596],[-78.521,39.52542],[-78.56803,39.52238],[-78.59404,39.53582],[-78.66382,39.53741],[-78.72695,39.56424],[-78.73343,39.58644],[-78.76364,39.58262],[-78.77738,39.60866],[-78.73892,39.60775],[-78.73592,39.62235],[-78.77948,39.62192],[-78.76532,39.64419],[-78.76424,39.64888],[-78.77523,39.64614],[-78.78141,39.63719],[-78.79643,39.63742],[-78.79574,39.60687],[-78.81832,39.5951],[-78.81925,39.56065],[-78.84218,39.56824],[-78.91591,39.48761],[-78.94114,39.4788],[-78.96492,39.43828],[-78.9774,39.44822],[-79.0083,39.46048],[-79.05126,39.48489],[-79.05697,39.46886],[-79.06692,39.48036],[-79.06993,39.47386],[-79.09418,39.47264],[-79.11143,39.44487],[-79.18172,39.38533],[-79.19657,39.38733],[-79.21579,39.36424],[-79.25321,39.35575],[-79.29235,39.29865],[-79.31158,39.30502],[-79.3454,39.29365],[-79.35999,39.27526],[-79.37853,39.27261],[-79.43226,39.22334],[-79.48702,39.20187],[-79.47663,39.72086],[-80.52292,39.72222],[-80.51902,40.63803],[-80.57837,40.6125],[-80.59948,40.6237],[-80.6345,40.61458],[-80.66608,40.58043],[-80.62935,40.53452],[-80.61287,40.49485],[-80.59639,40.47971],[-80.59681,40.46275],[-80.61424,40.43216],[-80.61209,40.40506],[-80.63261,40.39081],[-80.60591,40.3742],[-80.61046,40.34301],[-80.59879,40.31729],[-80.61484,40.28934],[-80.61492,40.26623],[-80.65346,40.24448],[-80.66762,40.1988],[-80.70625,40.14633],[-80.70402,40.10541],[-80.73929,40.07688],[-80.72822,40.04292],[-80.73929,40.01374],[-80.7368,39.97383],[-80.76135,39.9535],[-80.75329,39.91222],[-80.76281,39.90651],[-80.79543,39.91921],[-80.80839,39.90953],[-80.78899,39.87323],[-80.79062,39.86396],[-80.82436,39.84477],[-80.82015,39.80457],[-80.86881,39.76711],[-80.82521,39.70751],[-80.86263,39.69292],[-80.86564,39.65202],[-80.878,39.6197],[-80.92726,39.61653],[-80.99249,39.57255],[-81.06854,39.51688],[-81.12948,39.44646],[-81.1845,39.43153],[-81.21351,39.38659],[-81.26217,39.38625],[-81.35599,39.3412],[-81.38431,39.34292],[-81.40886,39.38572],[-81.45495,39.41013],[-81.48233,39.38838],[-81.55571,39.34286],[-81.56541,39.26761],[-81.66232,39.27618],[-81.69519,39.25977],[-81.68875,39.22507],[-81.75218,39.1833],[-81.74291,39.09822],[-81.77441,39.0763],[-81.81166,39.08283],[-81.80806,39.05518],[-81.76128,39.01876],[-81.78128,38.96388],[-81.75355,38.9323],[-81.77716,38.91982],[-81.81793,38.94645],[-81.83793,38.93784],[-81.84368,38.9013],[-81.895,38.87152],[-81.93191,38.8951],[-81.89801,38.9289],[-81.92728,38.97982],[-81.94908,38.99577],[-81.98015,38.99217],[-81.99302,39.01918],[-82.01173,39.02971],[-82.03628,39.02391],[-82.04916,38.9953],[-82.08357,38.97582],[-82.14177,38.89737],[-82.14048,38.8375],[-82.21738,38.79583],[-82.18992,38.73801],[-82.16932,38.61029],[-82.20125,38.59017],[-82.26339,38.59701],[-82.28914,38.58105],[-82.31609,38.44874],[-82.37857,38.43288],[-82.40604,38.43718],[-82.54406,38.39937],[-82.58148,38.4084],[-82.59692,38.42382],[-82.59761,38.34576],[-82.57495,38.32261],[-82.57873,38.2497],[-82.61435,38.2404],[-82.59692,38.21156],[-82.60791,38.17378],[-82.64361,38.1673]]]}},{"type":"Feature","properties":{"id":"UA-43"},"geometry":{"type":"Polygon","coordinates":[[[32.14262,45.66011],[33.048,44.55373],[33.28621,44.94345],[33.5643,44.84057],[33.58163,44.81208],[33.67554,44.7907],[33.68172,44.77017],[33.61287,44.74713],[33.61649,44.71237],[33.72772,44.71579],[33.7751,44.68968],[33.71381,44.6216],[33.73528,44.60199],[33.77853,44.6125],[33.92616,44.42082],[33.85784,44.41886],[33.76265,44.39108],[33.72184,44.24476],[36.40186,44.9675],[36.80011,45.62823],[35.23066,45.79231],[34.96015,45.75634],[34.79905,45.81009],[34.80118,45.89282],[34.56575,45.99728],[34.50256,45.9367],[34.44155,45.95995],[34.36177,46.05994],[34.204,46.06017],[34.07311,46.11769],[33.9971,46.10671],[33.82255,46.21028],[33.74047,46.18555],[33.646,46.23028],[33.61517,46.22615],[33.63854,46.14147],[33.61467,46.13561],[33.54017,46.0123],[32.14262,45.66011]]]}},{"type":"Feature","properties":{"id":"UA-40"},"geometry":{"type":"Polygon","coordinates":[[[33.048,44.55373],[33.72184,44.24476],[33.76265,44.39108],[33.85784,44.41886],[33.92616,44.42082],[33.77853,44.6125],[33.73528,44.60199],[33.71381,44.6216],[33.7751,44.68968],[33.72772,44.71579],[33.61649,44.71237],[33.61287,44.74713],[33.68172,44.77017],[33.67554,44.7907],[33.58163,44.81208],[33.5643,44.84057],[33.28621,44.94345],[33.048,44.55373]]]}},{"type":"Feature","properties":{"id":"NL-BQ1"},"geometry":{"type":"Polygon","coordinates":[[[-68.90012,12.62309],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309]]]}},{"type":"Feature","properties":{"id":"NL-BQ2"},"geometry":{"type":"Polygon","coordinates":[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.07669,17.79659],[-63.29212,17.90532],[-63.58819,17.61311]]]}},{"type":"Feature","properties":{"id":"NO-21"},"geometry":{"type":"Polygon","coordinates":[[[5.93257,80.704],[16.30346,73.63706],[30.54194,75.9202],[36.27514,81.36349],[5.93257,80.704]]]}},{"type":"Feature","properties":{"id":"NO-22"},"geometry":{"type":"Polygon","coordinates":[[[-9.46764,70.6182],[-7.10917,70.97072],[-8.64261,71.45615],[-9.46764,70.6182]]]}},{"type":"Feature","properties":{"id":"SO"},"geometry":{"type":"Polygon","coordinates":[[[40.98767,2.82959],[41.00099,-0.83068],[41.56,-1.59812],[41.55967,-1.66272],[41.75542,-1.85308],[48.77884,4.7921],[52.253,11.68582],[51.12877,12.56479],[43.42013,11.71655],[42.95776,10.98533],[42.69452,10.62672],[42.87643,10.18441],[43.0937,9.90579],[43.25523,9.84439],[43.30467,9.60684],[44.00161,8.99156],[44.68816,8.764],[46.99339,7.9989],[47.92099,8.0011],[47.92477,8.00111],[47.98416,8.00007],[45.40992,5.53602],[44.98104,4.91821],[44.02436,4.9451],[43.40263,4.79289],[43.04177,4.57923],[42.97746,4.44032],[42.84526,4.28357],[42.55853,4.20518],[42.07619,4.17667],[41.90586,3.98059],[41.31368,3.14314],[40.98767,2.82959]]]}},{"type":"Feature","properties":{"id":"CN-XZ"},"geometry":{"type":"Polygon","coordinates":[[[78.38897,32.53938],[78.4645,32.45367],[78.49609,32.2762],[78.68754,32.10256],[78.74404,32.00384],[78.78036,31.99478],[78.69933,31.78723],[78.84516,31.60631],[78.71032,31.50197],[78.77898,31.31209],[79.01931,31.42817],[79.14016,31.43403],[79.22805,31.34963],[79.59884,30.93943],[79.93255,30.88288],[80.20721,30.58541],[80.54504,30.44936],[80.83343,30.32023],[81.03953,30.20059],[81.12842,30.01395],[81.24362,30.0126],[81.29032,30.08806],[81.2623,30.14596],[81.33355,30.15303],[81.39928,30.21862],[81.41018,30.42153],[81.62033,30.44703],[81.99082,30.33423],[82.10135,30.35439],[82.10757,30.23745],[82.19475,30.16884],[82.16984,30.0692],[82.38622,30.02608],[82.5341,29.9735],[82.73024,29.81695],[83.07116,29.61957],[83.28131,29.56813],[83.44787,29.30513],[83.63156,29.16249],[83.82303,29.30513],[83.97559,29.33091],[84.18107,29.23451],[84.24801,29.02783],[84.2231,28.89571],[84.47528,28.74023],[84.62317,28.73887],[84.85511,28.58041],[85.06059,28.68562],[85.19135,28.62825],[85.18668,28.54076],[85.10729,28.34092],[85.38127,28.28336],[85.4233,28.32996],[85.59765,28.30529],[85.60854,28.25045],[85.69105,28.38475],[85.71907,28.38064],[85.74864,28.23126],[85.84672,28.18187],[85.90743,28.05144],[85.97813,27.99023],[85.9486,27.94085],[86.06309,27.90021],[86.12069,27.93047],[86.08333,28.02121],[86.088,28.09264],[86.18607,28.17364],[86.22966,27.9786],[86.42736,27.91122],[86.51609,27.96623],[86.56265,28.09569],[86.74181,28.10638],[86.75582,28.04182],[87.03757,27.94835],[87.11696,27.84104],[87.56996,27.84517],[87.72718,27.80938],[87.82681,27.95248],[88.13378,27.88015],[88.1278,27.95417],[88.25332,27.9478],[88.54858,28.06057],[88.63235,28.12356],[88.83559,28.01936],[88.88091,27.85192],[88.77517,27.45415],[88.82981,27.38814],[88.91901,27.32483],[88.93678,27.33777],[88.96947,27.30319],[89.00216,27.32532],[88.96694,27.40027],[88.99131,27.49319],[89.15782,27.55789],[89.12825,27.62502],[89.59525,28.16433],[89.79762,28.23979],[90.13387,28.19178],[90.58842,28.02838],[90.69894,28.07784],[90.91976,27.86153],[91.34685,28.04713],[91.46327,28.0064],[91.48973,27.93903],[91.57842,27.82313],[91.61189,27.83786],[91.66975,27.82435],[91.73738,27.77332],[91.83437,27.78411],[91.87471,27.71605],[92.27432,27.89077],[92.32101,27.79363],[92.42538,27.80092],[92.7275,27.98662],[92.73025,28.05814],[92.65472,28.07632],[92.67486,28.15018],[92.93609,28.23181],[93.14635,28.37035],[93.19221,28.52903],[93.37623,28.53687],[93.62377,28.68426],[93.72797,28.68821],[94.35897,29.01965],[94.2752,29.11687],[94.69318,29.31739],[94.81353,29.17804],[95.0978,29.14446],[95.11291,29.09527],[95.2214,29.10727],[95.26122,29.07727],[95.3038,29.13847],[95.41091,29.13007],[95.50842,29.13487],[95.72086,29.20797],[95.75149,29.32063],[95.84899,29.31464],[96.05361,29.38167],[96.31316,29.18643],[96.18682,29.11087],[96.20467,29.02325],[96.3626,29.10607],[96.61391,28.72742],[96.40929,28.51526],[96.48895,28.42955],[96.6455,28.61657],[96.85561,28.4875],[96.88445,28.39452],[96.98882,28.32564],[97.1289,28.3619],[97.34547,28.21385],[97.41729,28.29783],[97.47085,28.2688],[97.50518,28.49716],[97.56835,28.55628],[97.70705,28.5056],[97.79763,28.3278],[97.90191,28.37327],[98.00474,28.27641],[98.01589,28.2073],[98.08404,28.19913],[98.15337,28.12114],[98.16696,28.21002],[98.26789,28.24421],[98.20129,28.35666],[98.28987,28.39804],[98.36952,28.26084],[98.39355,28.10953],[98.60881,28.1725],[98.69361,28.21789],[98.75884,28.33218],[98.63697,28.49072],[98.5968,28.68622],[98.63113,28.69103],[98.68125,28.73319],[98.6804,28.77239],[98.66254,28.79549],[98.65104,28.86361],[98.64881,28.91937],[98.62495,28.9721],[98.78974,29.01054],[98.83197,28.80406],[98.97548,28.82933],[98.97376,28.87564],[98.91815,28.88796],[98.92501,28.98111],[99.01582,29.04056],[98.96003,29.18663],[99.11487,29.22679],[99.06698,29.30376],[99.06097,29.47502],[99.04638,29.52118],[99.04535,29.56912],[98.98956,29.66359],[99.01153,29.77674],[99.01119,29.8189],[99.05651,29.93708],[99.0438,30.07979],[98.99642,30.15344],[98.90338,30.69254],[98.96947,30.75039],[98.77653,30.90428],[98.80725,30.98496],[98.75181,31.04499],[98.71009,31.11997],[98.60469,31.18725],[98.62478,31.33736],[98.69052,31.33751],[98.77567,31.24949],[98.88733,31.37774],[98.84433,31.42954],[98.71584,31.51116],[98.5883,31.6346],[98.56246,31.67705],[98.41449,31.83439],[98.44402,31.99715],[98.30497,32.12619],[98.23133,32.26579],[98.21863,32.34458],[98.12533,32.40402],[98.10499,32.39677],[98.07752,32.41974],[98.03873,32.43119],[98.02396,32.45321],[98.00148,32.45647],[97.99057,32.46806],[97.72544,32.52886],[97.38624,32.57835],[97.30865,32.07501],[97.22557,32.10962],[97.16583,32.03049],[97.00309,32.06628],[96.9564,31.99351],[96.725,32.02612],[96.8431,31.7083],[96.5657,31.7194],[96.37069,31.85539],[96.33636,31.95682],[96.24435,31.9341],[96.13929,31.82623],[96.21963,31.76145],[96.25053,31.55396],[96.20109,31.53816],[96.14822,31.69078],[95.7891,31.75327],[95.61881,31.77896],[95.51032,31.74685],[95.22571,32.3872],[94.91981,32.413],[94.61254,32.67001],[94.14733,32.43561],[93.72161,32.57343],[93.48953,32.4947],[93.02398,32.73646],[92.22335,32.72144],[92.20275,32.88766],[91.97891,32.86113],[91.51405,33.11339],[90.70175,33.13985],[90.51567,33.2651],[90.38177,33.2605],[90.24993,33.42857],[89.99656,33.55913],[89.93751,33.80083],[89.63882,34.04583],[89.87708,34.2277],[89.73358,34.65862],[89.81941,34.90395],[89.57908,34.90113],[89.45274,35.22991],[89.68482,35.42207],[89.80224,35.859],[89.42802,35.91602],[89.40811,36.01522],[89.691,36.0935],[88.94119,36.35716],[88.76438,36.29077],[88.53332,36.48755],[86.2619,36.19995],[86.09298,35.8679],[85.57662,35.64055],[85.26489,35.80333],[84.19784,35.35881],[83.1253,35.39688],[82.966,35.62716],[82.45238,35.7309],[82.01568,35.34201],[81.66961,35.24337],[80.45287,35.45172],[79.83283,34.48958],[79.05418,34.4154],[79.05364,34.32482],[78.99802,34.3027],[78.91769,34.15452],[78.66225,34.08858],[78.65657,34.03195],[78.73367,34.01121],[78.77349,33.73871],[78.67599,33.66445],[78.73636,33.56521],[79.15252,33.17156],[79.14016,33.02545],[79.46562,32.69668],[79.26768,32.53277],[79.13174,32.47766],[79.0979,32.38051],[78.99322,32.37948],[78.96713,32.33655],[78.7831,32.46873],[78.73916,32.69438],[78.38897,32.53938]]]}},{"type":"Feature","properties":{"id":"CN-XJ"},"geometry":{"type":"Polygon","coordinates":[[[73.5004,39.38402],[73.55396,39.3543],[73.54572,39.27567],[73.60638,39.24534],[73.75823,39.023],[73.81728,39.04007],[73.82964,38.91517],[73.7445,38.93867],[73.7033,38.84782],[73.80656,38.66449],[73.79806,38.61106],[73.97933,38.52945],[74.17022,38.65504],[74.51217,38.47034],[74.69619,38.42947],[74.69894,38.22155],[74.80331,38.19889],[74.82665,38.07359],[74.9063,38.03033],[74.92416,37.83428],[75.00935,37.77486],[74.8912,37.67576],[74.94338,37.55501],[75.06011,37.52779],[75.13907,37.42124],[75.12328,37.31839],[74.88887,37.23275],[74.80605,37.21565],[74.49981,37.24518],[74.56453,37.03023],[75.13839,37.02622],[75.40481,36.95382],[75.45562,36.71971],[75.72737,36.7529],[75.92391,36.56986],[76.0324,36.41198],[76.00906,36.17511],[75.93028,36.13136],[76.15325,35.9264],[76.14913,35.82848],[76.33453,35.84296],[76.50961,35.8908],[76.77323,35.66062],[76.84539,35.67356],[76.96624,35.5932],[77.44277,35.46132],[77.70232,35.46244],[77.80532,35.52058],[78.11664,35.48022],[78.03466,35.3785],[78.00033,35.23954],[78.22692,34.88771],[78.18435,34.7998],[78.27781,34.61484],[78.54964,34.57283],[78.56475,34.50835],[78.74465,34.45174],[79.05364,34.32482],[79.05418,34.4154],[79.83283,34.48958],[80.45287,35.45172],[81.66961,35.24337],[82.01568,35.34201],[82.45238,35.7309],[82.966,35.62716],[83.1253,35.39688],[84.19784,35.35881],[85.26489,35.80333],[85.57662,35.64055],[86.09298,35.8679],[86.2619,36.19995],[88.53332,36.48755],[88.76438,36.29077],[88.94119,36.35716],[89.691,36.0935],[89.95056,36.08018],[90.01922,36.2631],[90.86379,36.02577],[91.10961,36.10792],[91.02035,36.54053],[90.7196,36.59347],[90.84869,36.93342],[91.31149,37.02887],[91.06567,37.48575],[90.5136,37.74465],[90.52322,38.31903],[90.31585,38.22955],[90.14076,38.33734],[90.1799,38.39656],[90.09406,38.49014],[90.44975,38.49928],[92.40874,39.03625],[92.92785,40.58058],[93.76556,40.66605],[94.22424,41.35001],[95.13061,41.77438],[95.30776,41.55535],[96.13174,42.00083],[96.0308,42.49893],[96.37926,42.72055],[96.35658,42.90363],[95.89543,43.2528],[95.52594,43.99353],[95.32891,44.02407],[95.39772,44.2805],[95.01191,44.25274],[94.71959,44.35284],[94.10003,44.71016],[93.51161,44.95964],[91.64048,45.07408],[90.89169,45.19667],[90.65114,45.49314],[90.70907,45.73437],[91.03026,46.04194],[90.99672,46.14207],[90.89639,46.30711],[91.07696,46.57315],[91.0147,46.58171],[91.03649,46.72916],[90.84035,46.99525],[90.76108,46.99399],[90.48542,47.30438],[90.48854,47.41826],[90.33598,47.68303],[90.10871,47.7375],[90.06512,47.88177],[89.76624,47.82745],[89.55453,48.0423],[89.0711,47.98528],[88.93186,48.10263],[88.8011,48.11302],[88.58316,48.21893],[88.58939,48.34531],[87.96361,48.58478],[88.0788,48.71436],[87.73822,48.89582],[87.88171,48.95853],[87.81333,49.17354],[87.48983,49.13794],[87.478,49.07403],[87.28386,49.11626],[86.89258,49.13859],[86.73568,48.99918],[86.75343,48.70331],[86.38069,48.46064],[85.73581,48.3939],[85.5169,48.05493],[85.61067,47.49753],[85.69696,47.2898],[85.54294,47.06171],[85.22443,47.04816],[84.93995,46.87399],[84.73077,47.01394],[83.92184,46.98912],[83.06453,47.23658],[82.21792,45.56619],[82.58474,45.40027],[82.53478,45.16824],[81.73278,45.3504],[80.08655,45.0301],[79.88433,44.9016],[80.38384,44.63073],[80.40229,44.23319],[80.40031,44.10986],[80.75156,43.44948],[80.69718,43.32589],[80.77771,43.30065],[80.78817,43.14235],[80.62913,43.141],[80.3735,43.01557],[80.58999,42.9011],[80.38169,42.83142],[80.26886,42.8366],[80.16892,42.61137],[80.26841,42.23797],[80.17807,42.21166],[80.17842,42.03211],[79.92977,42.04113],[78.3732,41.39603],[78.15757,41.38565],[78.12873,41.23091],[77.81287,41.14307],[77.76206,41.01574],[77.52723,41.00227],[77.3693,41.0375],[77.28004,41.0033],[76.99302,41.0696],[76.75681,40.95354],[76.5261,40.46114],[76.33659,40.3482],[75.96168,40.38064],[75.91361,40.2948],[75.69663,40.28642],[75.5854,40.66874],[75.22834,40.45382],[75.08243,40.43945],[74.82013,40.52197],[74.78168,40.44886],[74.85996,40.32857],[74.69875,40.34668],[74.35063,40.09742],[74.25533,40.13191],[73.97049,40.04378],[73.83006,39.76136],[73.9051,39.75073],[73.92354,39.69565],[73.94683,39.60733],[73.87018,39.47879],[73.59831,39.46425],[73.59241,39.40843],[73.5004,39.38402]]]}},{"type":"Feature","properties":{"id":"CN-NM"},"geometry":{"type":"Polygon","coordinates":[[[97.2134,42.79942],[98.3139,40.56806],[99.95086,40.96953],[100.28045,40.67439],[99.70229,39.98659],[100.84075,39.18224],[101.7279,38.64154],[102.13714,39.16946],[103.11354,39.1817],[103.46374,39.3619],[104.41268,39.398],[103.54476,38.15615],[103.36074,38.08809],[103.39507,37.88352],[103.83865,37.65773],[104.28153,37.42907],[104.43054,37.51299],[105.01899,37.58022],[105.16525,37.66343],[105.76606,37.79513],[105.81756,38.00617],[105.76435,38.18719],[105.86597,38.29586],[105.8258,38.34219],[105.85121,38.62116],[106.13479,39.15615],[106.28654,39.14337],[106.28448,39.27053],[106.59484,39.36934],[106.78161,39.37518],[106.86332,39.0917],[106.96357,39.06451],[106.95396,38.94819],[106.5921,38.38714],[106.47811,38.31364],[106.87911,38.13131],[107.1627,38.16155],[107.40749,37.98642],[107.65674,37.86753],[107.96607,37.79269],[108.02753,37.6512],[108.78936,37.68436],[108.83193,38.0662],[108.93836,37.9182],[109.03209,38.02023],[109.07226,38.02321],[109.04754,38.0989],[108.93356,38.17883],[109.01321,38.38714],[109.50656,38.82419],[109.55223,38.80386],[109.62432,38.86377],[109.72423,39.06451],[109.9007,39.10715],[109.89212,39.14523],[110.20814,39.28076],[110.16094,39.38062],[110.1309,39.39017],[110.11648,39.43486],[110.19853,39.48072],[110.38135,39.30826],[110.43662,39.38261],[110.52005,39.3834],[110.59661,39.27744],[110.68759,39.26522],[110.88363,39.50854],[111.16893,39.58928],[111.04808,39.43022],[111.09289,39.35859],[111.11881,39.36403],[111.12945,39.4025],[111.21288,39.42638],[111.34162,39.42041],[111.36463,39.47966],[111.42488,39.5096],[111.43295,39.64006],[111.49929,39.66088],[111.60924,39.63358],[111.66478,39.64112],[111.7143,39.60383],[111.77799,39.58822],[111.91549,39.61388],[111.96407,39.79284],[112.03222,39.85467],[112.04097,39.89511],[112.06689,39.91197],[112.10758,39.97527],[112.30293,40.25463],[112.45399,40.29995],[112.61947,40.23891],[112.73963,40.1626],[112.84572,40.20169],[112.88761,40.32822],[113.24809,40.41349],[113.31109,40.3184],[113.54249,40.33607],[113.6721,40.4425],[113.7981,40.5151],[113.87157,40.44668],[113.94985,40.52006],[114.07001,40.5365],[114.05731,40.71668],[114.12425,40.74517],[114.04821,40.81289],[114.04443,40.87899],[113.9035,41.03534],[113.82144,41.09487],[113.87071,41.10936],[113.87981,41.1435],[114.01388,41.21934],[113.98864,41.26296],[113.96358,41.23676],[113.9059,41.30128],[113.9392,41.39097],[113.85612,41.41338],[114.0422,41.54764],[114.21798,41.50652],[114.23103,41.65316],[114.20288,41.68624],[114.23309,41.69547],[114.18605,41.76542],[114.33162,41.94314],[114.49813,41.96051],[114.46947,42.06624],[114.56096,42.13184],[114.71271,42.11197],[114.82257,42.15118],[114.93209,41.89895],[114.92866,41.82787],[114.87785,41.80753],[114.89948,41.71111],[114.89038,41.63571],[114.85879,41.60235],[115.12126,41.62442],[115.23937,41.56883],[115.26975,41.61634],[115.38734,41.58668],[115.29687,41.69701],[115.54458,41.77873],[115.81272,41.93101],[115.91194,41.93804],[116.03725,41.77361],[116.10008,41.78334],[116.10969,41.83401],[116.32461,42.00593],[116.63909,41.92859],[116.87976,42.02634],[116.78054,42.19902],[116.90757,42.18477],[116.88697,42.37985],[117.00782,42.45994],[117.39921,42.46449],[117.44453,42.59151],[117.77343,42.6092],[118.01067,42.39202],[118.04981,42.29254],[117.96329,42.23436],[118.10268,42.17154],[118.08689,42.10688],[118.15898,42.08268],[118.11332,42.03271],[118.1916,42.0294],[118.26232,42.08446],[118.28807,42.03832],[118.22971,42.01206],[118.3073,41.98756],[118.26129,41.91837],[118.33236,41.8647],[118.30043,41.77822],[118.12808,41.83196],[118.15383,41.67086],[118.37699,41.33093],[118.73714,41.32655],[118.83258,41.36985],[118.86863,41.30463],[119.19376,41.28116],[119.23562,41.3149],[119.29435,41.32732],[119.36576,41.43191],[119.39872,41.50304],[119.35546,41.56241],[119.41606,41.56138],[119.4128,41.58643],[119.33538,41.61467],[119.30465,41.64187],[119.29641,41.70803],[119.30843,41.76452],[119.28268,41.78155],[119.3074,41.80778],[119.32868,41.8808],[119.3201,41.97199],[119.3692,42.02787],[119.3795,42.08803],[119.29401,42.14189],[119.23736,42.19596],[119.27736,42.26041],[119.46962,42.34116],[119.49932,42.39354],[119.56386,42.34192],[119.53794,42.29153],[119.69432,42.22724],[119.84573,42.21148],[119.83509,42.1214],[120.03662,41.81277],[120.02975,41.71341],[120.09498,41.68932],[120.18424,41.84245],[120.40878,41.98297],[120.53993,42.14864],[120.95226,42.26689],[121.02642,42.24097],[121.26193,42.38111],[121.31275,42.43891],[121.60251,42.50728],[121.6674,42.43181],[121.85794,42.53891],[121.94961,42.68445],[122.05432,42.71851],[122.19234,42.67536],[122.2047,42.71145],[122.38838,42.67284],[122.45601,42.75835],[122.31422,42.83267],[122.49069,42.83796],[122.85049,42.71675],[122.88516,42.76617],[123.05786,42.7702],[123.21338,42.82738],[123.1739,42.92525],[123.2611,42.99385],[123.4671,43.03853],[123.55911,42.96446],[123.70262,43.36662],[123.32153,43.48979],[123.53301,43.64203],[123.3229,44.06045],[123.37715,44.15215],[123.1327,44.34938],[123.12927,44.52784],[122.62115,44.2688],[122.33001,44.22256],[122.11715,44.57237],[122.08213,44.91327],[122.1453,45.2971],[122.16522,45.41484],[122.01965,45.48324],[121.99836,45.6366],[121.95098,45.71097],[121.74568,45.68267],[121.64337,45.73877],[121.82121,45.8728],[121.75735,45.99505],[121.8727,46.04083],[122.2586,45.79434],[122.43953,45.94351],[122.5003,45.78691],[122.72964,45.69682],[122.81272,46.06751],[123.17081,46.24777],[123.00876,46.43052],[122.99331,46.57231],[123.05614,46.62774],[123.07914,46.59897],[123.17115,46.61077],[123.26488,46.57963],[123.27072,46.66381],[123.59447,46.68642],[123.62949,46.8118],[123.49182,46.83483],[123.51447,46.95869],[123.00704,46.72103],[122.88413,46.95682],[122.79281,46.94135],[122.83264,47.06217],[122.60261,47.12481],[122.39868,47.34254],[122.56896,47.53482],[122.84706,47.67047],[123.16635,47.78294],[123.2975,47.95084],[123.54984,48.02644],[123.99169,48.37723],[124.25846,48.53479],[124.4902,48.11384],[124.56607,48.26491],[124.52075,48.50068],[124.61036,48.74894],[124.67834,48.83037],[124.86991,49.17205],[125.01857,49.17429],[125.10955,49.11096],[125.14698,49.18204],[125.21118,49.19314],[125.21598,49.27609],[125.25993,49.33966],[125.18989,49.93442],[125.30044,50.13554],[124.43115,50.44001],[124.31476,50.22744],[123.90929,50.18569],[123.76373,50.33143],[123.78844,50.45575],[123.92509,50.37349],[124.07409,50.54834],[123.57146,51.2516],[122.96447,51.31173],[122.7365,52.20255],[122.19817,52.50786],[121.87133,52.27152],[121.20391,52.57468],[121.82738,53.03956],[121.48681,53.33169],[120.85633,53.28499],[120.0451,52.7359],[120.04049,52.58773],[120.46454,52.63811],[120.71673,52.54099],[120.61346,52.32447],[120.77337,52.20805],[120.65907,51.93544],[120.10963,51.671],[119.13553,50.37412],[119.38598,50.35162],[119.27996,50.13348],[119.11003,50.00276],[118.61623,49.93809],[117.82343,49.52696],[117.48208,49.62324],[117.27597,49.62544],[117.07142,49.68482],[116.71193,49.83813],[116.03781,48.87014],[116.06565,48.81716],[115.78876,48.51781],[115.811,48.25699],[115.52082,48.15367],[115.57128,47.91988],[115.94296,47.67741],[116.08431,47.80693],[116.2527,47.87766],[116.4465,47.83662],[116.67405,47.89039],[116.87527,47.88836],[117.08918,47.82242],[117.37875,47.63627],[117.50181,47.77216],[117.80196,48.01661],[118.03676,48.00982],[118.11009,48.04],[118.22677,48.03853],[118.29654,48.00246],[118.55766,47.99277],[118.7564,47.76947],[119.12343,47.66458],[119.13995,47.53997],[119.35892,47.48104],[119.31964,47.42617],[119.54918,47.29505],[119.56019,47.24874],[119.62403,47.24575],[119.71209,47.19192],[119.85518,46.92196],[119.91242,46.90091],[119.89261,46.66423],[119.80455,46.67631],[119.77373,46.62947],[119.68127,46.59015],[119.65265,46.62342],[119.42827,46.63783],[119.37306,46.61132],[119.30261,46.6083],[119.24978,46.64761],[119.10448,46.65516],[119.00541,46.74273],[118.92616,46.72765],[118.89974,46.77139],[118.8337,46.77742],[118.78747,46.68689],[118.30534,46.73519],[117.69554,46.50991],[117.60748,46.59771],[117.41782,46.57862],[117.36609,46.36335],[117.07252,46.35818],[116.83166,46.38637],[116.75551,46.33083],[116.58612,46.30211],[116.26678,45.96479],[116.24012,45.8778],[116.27366,45.78637],[116.16989,45.68603],[115.91898,45.6227],[115.69688,45.45761],[115.35757,45.39106],[114.94546,45.37377],[114.74612,45.43585],[114.54801,45.38337],[114.5166,45.27189],[114.08071,44.92847],[113.909,44.91444],[113.63821,44.74326],[112.74662,44.86297],[112.4164,45.06858],[111.98695,45.09074],[111.76275,44.98032],[111.40498,44.3461],[111.96289,43.81596],[111.93776,43.68709],[111.79758,43.6637],[111.59087,43.51207],[111.0149,43.3289],[110.4327,42.78293],[110.08401,42.6411],[109.89402,42.63111],[109.452,42.44842],[109.00679,42.45302],[108.84489,42.40246],[108.23156,42.45532],[107.57258,42.40898],[107.49681,42.46221],[107.29755,42.41395],[107.24774,42.36107],[106.76517,42.28741],[105.24708,41.7442],[105.01119,41.58382],[104.91272,41.64619],[104.51667,41.66113],[104.52258,41.8706],[103.92804,41.78246],[103.3685,41.89696],[102.72403,42.14675],[102.42826,42.15137],[102.07645,42.22519],[101.80515,42.50074],[101.28833,42.58524],[100.84979,42.67087],[100.33297,42.68231],[99.50671,42.56535],[97.2134,42.79942]]]}},{"type":"Feature","properties":{"id":"CN-QH"},"geometry":{"type":"Polygon","coordinates":[[[89.40811,36.01522],[89.42802,35.91602],[89.80224,35.859],[89.68482,35.42207],[89.45274,35.22991],[89.57908,34.90113],[89.81941,34.90395],[89.73358,34.65862],[89.87708,34.2277],[89.63882,34.04583],[89.93751,33.80083],[89.99656,33.55913],[90.24993,33.42857],[90.38177,33.2605],[90.51567,33.2651],[90.70175,33.13985],[91.51405,33.11339],[91.97891,32.86113],[92.20275,32.88766],[92.22335,32.72144],[93.02398,32.73646],[93.48953,32.4947],[93.72161,32.57343],[94.14733,32.43561],[94.61254,32.67001],[94.91981,32.413],[95.22571,32.3872],[95.51032,31.74685],[95.61881,31.77896],[95.7891,31.75327],[96.14822,31.69078],[96.20109,31.53816],[96.25053,31.55396],[96.21963,31.76145],[96.13929,31.82623],[96.24435,31.9341],[96.33636,31.95682],[96.37069,31.85539],[96.5657,31.7194],[96.8431,31.7083],[96.725,32.02612],[96.9564,31.99351],[97.00309,32.06628],[97.16583,32.03049],[97.22557,32.10962],[97.30865,32.07501],[97.38624,32.57835],[97.72544,32.52886],[97.66296,32.55781],[97.54417,32.62332],[97.5325,32.64241],[97.48168,32.65556],[97.4271,32.7122],[97.37766,32.80718],[97.37834,32.86978],[97.33989,32.9038],[97.4319,32.9813],[97.53078,32.99167],[97.49061,33.11512],[97.4858,33.166],[97.59841,33.25964],[97.62348,33.33769],[97.7584,33.40536],[97.40409,33.63062],[97.39517,33.89492],[97.65266,33.93538],[97.66158,34.12431],[97.85797,34.21066],[98.42513,34.06233],[98.42651,33.85217],[98.73962,33.43717],[98.85497,33.14675],[99.36035,32.90092],[99.73388,32.72375],[99.8822,33.04781],[100.49554,32.65671],[100.54687,32.5714],[100.66463,32.52481],[100.71819,32.67492],[100.94032,32.60756],[101.06271,32.67868],[101.1185,32.63619],[101.22699,32.73675],[101.13155,33.28548],[101.37702,33.18238],[101.46165,33.22389],[101.64001,33.09844],[101.72996,33.26883],[101.65546,33.36121],[101.77665,33.53681],[101.62628,33.49731],[101.58782,33.67349],[101.17034,33.65463],[101.19232,33.79455],[100.7611,34.17545],[100.93997,34.39529],[101.73442,34.08365],[102.25318,34.36441],[102.15499,34.51221],[101.92153,34.59732],[101.92016,34.84424],[102.40218,35.18503],[102.2834,35.40864],[102.4324,35.43409],[102.50381,35.58808],[102.75169,35.49533],[102.80078,35.57258],[102.6844,35.78077],[102.78499,35.86262],[102.9467,35.83507],[102.94755,35.96578],[102.97399,36.03688],[102.89623,36.07296],[102.9927,36.19524],[103.06823,36.20813],[103.01931,36.23333],[103.02618,36.25257],[102.92026,36.30073],[102.8952,36.33186],[102.84095,36.33421],[102.82859,36.36891],[102.71598,36.60009],[102.59651,36.71081],[102.72319,36.76886],[102.63582,36.85407],[102.5802,36.87714],[102.55823,36.91984],[102.5045,36.9422],[102.49634,36.95668],[102.48098,36.95421],[102.44853,36.96868],[102.63256,37.12254],[102.17628,37.44542],[101.99706,37.69251],[101.33857,37.8331],[100.90736,38.04052],[100.93105,38.16749],[100.0209,38.49766],[100.17677,38.2112],[98.8179,39.05651],[98.31218,39.02638],[98.24867,38.88515],[98.08937,38.78513],[97.3368,39.16733],[96.97769,39.20884],[96.9358,38.9108],[97.05459,38.6284],[96.66595,38.48665],[96.65702,38.22901],[96.29859,38.15669],[95.65658,38.36857],[95.24459,38.30502],[94.99053,38.43638],[94.5346,38.35781],[94.35607,38.76265],[93.42086,38.9092],[93.11599,39.17372],[92.40874,39.03625],[90.44975,38.49928],[90.09406,38.49014],[90.1799,38.39656],[90.14076,38.33734],[90.31585,38.22955],[90.52322,38.31903],[90.5136,37.74465],[91.06567,37.48575],[91.31149,37.02887],[90.84869,36.93342],[90.7196,36.59347],[91.02035,36.54053],[91.10961,36.10792],[90.86379,36.02577],[90.01922,36.2631],[89.95056,36.08018],[89.691,36.0935],[89.40811,36.01522]]]}},{"type":"Feature","properties":{"id":"CN-GX"},"geometry":{"type":"Polygon","coordinates":[[[104.47105,24.65294],[104.56941,24.44714],[104.72734,24.29031],[104.70331,24.42386],[104.75996,24.46215],[104.94123,24.41354],[105.03736,24.44027],[105.10534,24.41808],[105.10225,24.38087],[105.20696,24.34709],[105.16405,24.28139],[105.2473,24.18997],[105.17211,24.14471],[105.2655,24.059],[105.27065,24.12372],[105.49552,24.01949],[105.57329,24.16132],[105.64796,24.03831],[105.88554,24.03564],[105.99678,24.12685],[106.15608,23.90592],[106.13599,23.57169],[106.00089,23.4519],[105.81584,23.49977],[105.57277,23.29985],[105.56402,23.25948],[105.52402,23.23393],[105.54822,23.19259],[105.56505,23.15961],[105.56247,23.07073],[105.72401,23.06425],[105.87558,22.91887],[105.89498,22.93815],[105.99815,22.94116],[106.00622,22.99268],[106.20088,22.98557],[106.26869,22.87459],[106.30868,22.86399],[106.32568,22.87451],[106.34817,22.85695],[106.37632,22.88273],[106.49749,22.91164],[106.51306,22.94891],[106.53631,22.94242],[106.56274,22.92068],[106.60179,22.92884],[106.6516,22.86862],[106.6734,22.89587],[106.71387,22.88296],[106.70702,22.86708],[106.7817,22.81455],[106.81271,22.8226],[106.83766,22.80672],[106.82404,22.7881],[106.76293,22.73491],[106.72582,22.63587],[106.71698,22.58432],[106.65316,22.5757],[106.60875,22.60481],[106.58188,22.51842],[106.58502,22.47552],[106.57592,22.46945],[106.5721,22.47655],[106.55794,22.4637],[106.58523,22.38039],[106.55605,22.34309],[106.6516,22.33977],[106.70239,22.22014],[106.69535,22.20647],[106.67029,22.18565],[106.69389,22.13295],[106.71243,22.09375],[106.70917,22.0255],[106.68274,21.99811],[106.69381,21.95721],[106.70913,21.97369],[106.71887,21.97421],[106.74671,22.00817],[106.77706,22.00672],[106.81217,21.97385],[106.9178,21.97357],[106.9295,21.93197],[106.97387,21.92282],[106.99252,21.95191],[107.05634,21.92303],[107.06101,21.88982],[107.00348,21.84556],[107.02558,21.81969],[107.10631,21.79855],[107.20734,21.71493],[107.24625,21.7077],[107.29728,21.74586],[107.35834,21.6672],[107.35989,21.60063],[107.38636,21.59774],[107.41593,21.64839],[107.47197,21.6672],[107.48637,21.64362],[107.49658,21.61322],[107.48268,21.59862],[107.5001,21.59547],[107.54173,21.5904],[107.56005,21.61306],[107.58254,21.61697],[107.59541,21.60417],[107.67674,21.60808],[107.71674,21.62188],[107.72094,21.62751],[107.80755,21.6591],[107.8257,21.65355],[107.8387,21.64314],[107.8593,21.65427],[107.8766,21.64043],[107.89814,21.59072],[107.92397,21.58936],[107.94264,21.56522],[107.95341,21.53756],[107.96745,21.53604],[107.97074,21.54072],[107.97539,21.53955],[107.97932,21.54503],[108.03212,21.54782],[108.0569,21.53604],[108.10003,21.47338],[108.26073,20.07614],[109.78912,21.47351],[109.74122,21.60556],[109.76534,21.67617],[109.79684,21.63357],[109.90087,21.65798],[109.92216,21.71198],[109.94662,21.85074],[109.99983,21.88141],[110.05373,21.86078],[110.12317,21.90227],[110.19613,21.89996],[110.24728,21.87703],[110.29123,21.91836],[110.32796,21.8916],[110.3847,21.89296],[110.39234,21.91199],[110.3689,21.93667],[110.38513,21.95506],[110.35062,21.97751],[110.35642,22.01046],[110.34603,22.03433],[110.35938,22.12253],[110.31869,22.16022],[110.34479,22.1971],[110.37586,22.1655],[110.42263,22.21382],[110.49173,22.14575],[110.56657,22.19773],[110.62442,22.15274],[110.67317,22.17659],[110.64159,22.2346],[110.72193,22.29719],[110.76261,22.2748],[110.78664,22.28846],[110.70871,22.38071],[110.6809,22.48242],[110.74184,22.46434],[110.75652,22.59016],[110.7972,22.56115],[110.82767,22.58889],[110.87934,22.58691],[110.89977,22.61512],[110.94903,22.61345],[110.95787,22.64229],[111.05066,22.65076],[111.08456,22.69638],[111.05778,22.73819],[111.19297,22.74056],[111.35776,22.89768],[111.36325,22.97277],[111.43621,23.0464],[111.37098,23.08968],[111.38179,23.215],[111.3521,23.2866],[111.39364,23.473],[111.42179,23.46875],[111.47621,23.54038],[111.4817,23.63084],[111.60744,23.64122],[111.66023,23.71338],[111.61422,23.73129],[111.65104,23.83819],[111.80245,23.80827],[111.85214,23.94923],[111.90553,23.94797],[111.93729,23.98617],[111.87171,24.10978],[111.89111,24.21487],[112.06277,24.33677],[112.03617,24.41104],[111.98123,24.46933],[112.01213,24.52494],[111.93111,24.61456],[111.93592,24.69568],[112.01728,24.74714],[111.6961,24.78455],[111.52633,24.63952],[111.42745,24.6832],[111.47981,24.79889],[111.46007,24.92458],[111.43192,24.97345],[111.46522,25.03241],[111.41664,25.04174],[111.39879,25.12803],[111.33218,25.09943],[111.26386,25.1487],[111.19794,25.07658],[111.10542,25.0405],[111.0922,24.95306],[110.99058,24.92084],[110.95521,25.01281],[110.97015,25.10922],[111.31347,25.47613],[111.30678,25.72088],[111.42059,25.76959],[111.48033,25.87513],[111.33441,25.90493],[111.25579,25.85922],[111.19331,25.95665],[111.28927,26.2674],[111.08362,26.31434],[110.96328,26.3851],[110.9262,26.26694],[110.75317,26.25277],[110.61172,26.33465],[110.32779,25.98088],[110.24745,25.96298],[110.22102,26.04722],[110.07562,26.03411],[110.09176,26.16499],[109.96095,26.20843],[109.88336,26.05323],[109.79255,26.02686],[109.81847,25.87142],[109.66827,25.89165],[109.72372,25.99523],[109.62604,26.0503],[109.47669,26.03334],[109.26675,25.71949],[109.2041,25.74176],[109.17989,25.8192],[109.13663,25.76851],[109.03827,25.80112],[108.89476,25.71826],[108.89562,25.68825],[108.93665,25.68361],[109.07089,25.73743],[109.03724,25.6214],[109.07278,25.5206],[108.86301,25.55994],[108.79709,25.53144],[108.77683,25.63518],[108.6989,25.6344],[108.60671,25.49348],[108.61976,25.30306],[108.49822,25.4535],[108.34648,25.535],[108.17859,25.45319],[108.11096,25.21363],[107.77622,25.11358],[107.75133,25.24811],[107.69313,25.19282],[107.6497,25.32106],[107.59752,25.25758],[107.46568,25.21736],[107.48233,25.30414],[107.42603,25.28847],[107.41384,25.39428],[107.3075,25.41079],[107.31874,25.49844],[107.22776,25.57093],[107.21454,25.61119],[107.14313,25.56722],[107.06005,25.56288],[107.06708,25.51889],[107.0125,25.49705],[106.9919,25.4504],[106.96083,25.44203],[106.98142,25.36419],[107.01061,25.3541],[106.99378,25.24096],[106.9095,25.25183],[106.8913,25.18894],[106.78934,25.17371],[106.76067,25.18552],[106.72393,25.16408],[106.68823,25.18133],[106.63621,25.16734],[106.63467,25.13378],[106.5842,25.08995],[106.5327,25.07922],[106.5121,25.05574],[106.45133,25.03879],[106.43726,25.02121],[106.29615,24.97765],[106.21341,24.98325],[106.18595,24.95804],[106.14269,24.95804],[106.20243,24.857],[106.17565,24.76896],[106.13256,24.73404],[106.04776,24.69147],[106.016,24.63344],[105.95849,24.68196],[105.93275,24.73061],[105.80314,24.70192],[105.69808,24.77177],[105.49209,24.8154],[105.4387,24.92551],[105.20507,24.99741],[105.08577,24.92894],[105.03702,24.87927],[105.02655,24.79265],[104.73884,24.62017],[104.52444,24.73357],[104.48839,24.657],[104.47105,24.65294]]]}},{"type":"Feature","properties":{"id":"CN-YN"},"geometry":{"type":"Polygon","coordinates":[[[97.51653,23.9395],[97.63395,23.87955],[97.64511,23.84407],[97.72302,23.89288],[97.76415,23.91189],[97.76827,23.93479],[97.79456,23.94836],[97.79416,23.95663],[97.80608,23.95268],[97.80964,23.96229],[97.8266,23.95409],[97.82368,23.97252],[97.83835,23.96272],[97.84328,23.97603],[97.86545,23.97723],[97.88749,23.97456],[97.89541,23.97778],[97.89715,23.9797],[97.89693,23.98399],[97.8942,23.98357],[97.88814,23.98605],[97.88414,23.99405],[97.88556,24.00291],[97.90792,24.02043],[97.93951,24.01953],[97.98508,24.03556],[97.99583,24.04932],[98.01753,24.05724],[98.04709,24.07616],[98.05274,24.07375],[98.05671,24.07961],[98.06271,24.07825],[98.07007,24.08204],[98.07602,24.07868],[98.09529,24.08901],[98.21537,24.11369],[98.3587,24.09943],[98.36299,24.11354],[98.54667,24.12944],[98.59256,24.08371],[98.71713,24.12873],[98.85319,24.13042],[98.87998,24.15624],[98.89632,24.10612],[98.86776,24.08572],[98.86961,24.07808],[98.67797,23.9644],[98.68209,23.80492],[98.79607,23.77947],[98.82933,23.72921],[98.8179,23.70253],[98.88656,23.59734],[98.80294,23.5345],[98.8276,23.47867],[98.88021,23.49017],[98.91334,23.41883],[98.92104,23.36946],[98.87573,23.33038],[98.93958,23.31414],[98.92515,23.29535],[98.88519,23.18423],[99.05975,23.16382],[99.04601,23.12215],[99.105,23.10152],[99.19881,23.11004],[99.25014,23.08225],[99.30353,23.10404],[99.33803,23.13341],[99.41888,23.08668],[99.52214,23.08218],[99.51939,22.99821],[99.56531,22.93317],[99.54218,22.90014],[99.43305,22.94385],[99.45931,22.84991],[99.39949,22.82856],[99.37957,22.76715],[99.31537,22.73977],[99.3861,22.57755],[99.37972,22.50188],[99.28771,22.4105],[99.22903,22.25541],[99.1783,22.18398],[99.17362,22.17969],[99.1822,22.17576],[99.20285,22.17218],[99.15573,22.16197],[99.27246,22.10281],[99.32121,22.10051],[99.4315,22.10544],[99.47585,22.13345],[99.65423,22.09295],[99.70762,22.04332],[99.72564,22.06686],[99.85267,22.03186],[99.96906,22.05971],[99.99084,21.97053],[99.94331,21.82548],[99.98654,21.71064],[100.04931,21.66763],[100.06965,21.69284],[100.11918,21.70608],[100.17486,21.65306],[100.10757,21.59945],[100.12493,21.51185],[100.16887,21.48645],[100.18447,21.51898],[100.242,21.46752],[100.29624,21.48014],[100.35201,21.53176],[100.43091,21.54243],[100.4811,21.46148],[100.57861,21.45637],[100.71999,21.51272],[100.8847,21.6855],[101.00383,21.70974],[101.08228,21.77081],[101.11744,21.77659],[101.11627,21.69252],[101.16682,21.6437],[101.15156,21.56129],[101.21618,21.55879],[101.19953,21.43709],[101.14013,21.40265],[101.19009,21.32487],[101.2504,21.29478],[101.21661,21.23234],[101.28767,21.1736],[101.54563,21.25668],[101.6068,21.23329],[101.59491,21.18621],[101.60886,21.17947],[101.66904,21.20272],[101.70548,21.14911],[101.76408,21.14204],[101.79025,21.20369],[101.83887,21.20983],[101.84412,21.25291],[101.74014,21.30967],[101.75142,21.45195],[101.7727,21.51794],[101.7475,21.5873],[101.80001,21.57461],[101.83257,21.61562],[101.74555,21.72852],[101.7791,21.83019],[101.62566,21.96574],[101.57525,22.13026],[101.60675,22.13513],[101.54882,22.23586],[101.56581,22.27428],[101.62074,22.27325],[101.68973,22.46843],[101.7685,22.50337],[101.86828,22.38397],[101.90714,22.38688],[101.91344,22.44417],[101.98487,22.42766],[102.03633,22.46164],[102.1245,22.43372],[102.14099,22.40092],[102.16621,22.43336],[102.26428,22.41321],[102.25339,22.4607],[102.41061,22.64184],[102.38415,22.67919],[102.42618,22.69212],[102.46665,22.77108],[102.51802,22.77969],[102.57095,22.7036],[102.60675,22.73376],[102.8636,22.60735],[102.9321,22.48659],[103.0722,22.44775],[103.07843,22.50097],[103.17961,22.55705],[103.15741,22.59463],[103.16604,22.60913],[103.18513,22.64498],[103.28079,22.68063],[103.28933,22.7366],[103.33602,22.80933],[103.43179,22.75816],[103.43646,22.70648],[103.52675,22.59155],[103.57812,22.65764],[103.56056,22.69884],[103.64175,22.79881],[103.87667,22.56598],[103.93286,22.52703],[103.94513,22.52553],[103.95191,22.5134],[103.96352,22.50584],[103.9692,22.51243],[103.97499,22.50609],[103.99247,22.51958],[104.00666,22.5187],[104.01147,22.52385],[104.00705,22.54216],[104.04108,22.72647],[104.12116,22.81261],[104.14155,22.81083],[104.27084,22.8457],[104.25683,22.76534],[104.27201,22.74539],[104.33947,22.72172],[104.3532,22.69369],[104.36797,22.68696],[104.39835,22.70161],[104.41371,22.73249],[104.47225,22.75813],[104.58005,22.8564],[104.60134,22.81637],[104.65507,22.83797],[104.72828,22.81906],[104.77114,22.90017],[104.84942,22.93631],[104.86765,22.95178],[104.8334,23.01484],[104.79892,23.1192],[104.87497,23.12915],[104.88072,23.16585],[104.90638,23.18084],[104.9438,23.15235],[104.96509,23.20182],[104.99547,23.20277],[105.07002,23.26248],[105.11281,23.24686],[105.17267,23.28826],[105.23357,23.26035],[105.24078,23.28826],[105.25846,23.31813],[105.24971,23.33555],[105.27996,23.3419],[105.30807,23.38196],[105.32376,23.39684],[105.37905,23.31128],[105.42411,23.28219],[105.43767,23.303],[105.48986,23.22983],[105.5211,23.18612],[105.54822,23.19259],[105.52402,23.23393],[105.56402,23.25948],[105.57277,23.29985],[105.81584,23.49977],[106.00089,23.4519],[106.13599,23.57169],[106.15608,23.90592],[105.99678,24.12685],[105.88554,24.03564],[105.64796,24.03831],[105.57329,24.16132],[105.49552,24.01949],[105.27065,24.12372],[105.2655,24.059],[105.17211,24.14471],[105.2473,24.18997],[105.16405,24.28139],[105.20696,24.34709],[105.10225,24.38087],[105.10534,24.41808],[105.03736,24.44027],[104.94123,24.41354],[104.75996,24.46215],[104.70331,24.42386],[104.72734,24.29031],[104.56941,24.44714],[104.47105,24.65294],[104.48839,24.657],[104.52444,24.73357],[104.54229,24.77535],[104.53868,24.81852],[104.60203,24.89313],[104.71206,25.00348],[104.67515,25.06849],[104.72339,25.19717],[104.75189,25.21627],[104.82467,25.14808],[104.79532,25.25603],[104.75137,25.26891],[104.7076,25.28428],[104.70708,25.29763],[104.64666,25.2939],[104.64134,25.36],[104.5392,25.40606],[104.55413,25.52857],[104.5428,25.51587],[104.5271,25.53105],[104.43243,25.47427],[104.42161,25.58595],[104.36428,25.58177],[104.3066,25.65452],[104.46865,26.0139],[104.5174,26.17084],[104.59258,26.31926],[104.68666,26.3691],[104.56958,26.59957],[104.47963,26.58422],[104.40925,26.73028],[104.34162,26.62444],[104.15279,26.67323],[104.00104,26.51389],[103.8153,26.53417],[103.7051,26.83173],[103.78063,26.949],[103.68621,27.06248],[103.61377,27.00591],[103.62716,27.11872],[103.83865,27.27202],[103.93512,27.44674],[104.00447,27.42937],[104.01889,27.38152],[104.17579,27.26317],[104.37217,27.47233],[104.50984,27.40712],[104.58057,27.31961],[104.85145,27.34523],[104.86656,27.29338],[105.07186,27.43059],[105.21057,27.37603],[105.24473,27.57235],[105.3012,27.61677],[105.30515,27.70875],[105.23254,27.90584],[105.17074,28.07258],[105.03616,28.09742],[104.87205,27.90766],[104.56409,27.85],[104.29698,28.05259],[104.44805,28.11801],[104.35432,28.34366],[104.30076,28.30679],[104.24446,28.53717],[104.35518,28.55994],[104.41509,28.6014],[104.42504,28.63048],[104.40153,28.64555],[104.36977,28.65293],[104.30574,28.62069],[104.24566,28.66769],[104.2345,28.64148],[104.15948,28.64314],[104.08241,28.61014],[104.05752,28.63063],[103.94525,28.60562],[103.92242,28.63124],[103.87762,28.62656],[103.86474,28.6698],[103.84517,28.66995],[103.83264,28.58813],[103.80826,28.56869],[103.78749,28.51757],[103.82766,28.46401],[103.83075,28.43563],[103.85959,28.38807],[103.85032,28.35847],[103.87556,28.31813],[103.865,28.30098],[103.82646,28.28677],[103.80998,28.26863],[103.78363,28.25721],[103.77376,28.23861],[103.73428,28.23816],[103.71007,28.19308],[103.6899,28.23596],[103.64433,28.2628],[103.59386,28.23816],[103.50185,28.1495],[103.44932,28.12376],[103.45447,28.09954],[103.42649,28.05122],[103.49155,28.03456],[103.50288,27.96984],[103.57412,27.98242],[103.50116,27.91949],[103.50528,27.83452],[103.48726,27.79899],[103.39284,27.71681],[103.37121,27.7124],[103.29637,27.64247],[103.27629,27.63517],[103.29568,27.6046],[103.2895,27.56778],[103.22221,27.57143],[103.20608,27.53536],[103.18943,27.52654],[103.15492,27.474],[103.14102,27.42739],[103.09295,27.39676],[103.07046,27.41291],[102.98652,27.37146],[102.93846,27.41764],[102.88181,27.29063],[102.88352,27.25585],[102.91254,27.13247],[102.86945,27.03114],[102.89073,27.00759],[102.89571,26.91441],[102.96644,26.83678],[103.02343,26.66249],[103.02206,26.59144],[103.05725,26.53079],[102.99579,26.45996],[102.99648,26.36264],[102.64766,26.22629],[102.61196,26.37956],[102.39257,26.29926],[102.10864,26.08546],[101.91467,26.10889],[101.83536,26.0466],[101.79485,26.16529],[101.63726,26.26632],[101.63177,26.3974],[101.40209,26.55075],[101.39453,26.6094],[101.46629,26.60387],[101.49307,26.78638],[101.38492,26.73242],[101.399,26.85899],[101.14768,27.03099],[101.16451,27.19693],[101.05911,27.21799],[100.83835,27.67835],[100.65261,27.90857],[100.54618,27.81812],[100.43701,27.8673],[100.32096,27.71635],[100.02468,28.16766],[100.18638,28.25237],[99.95635,28.56462],[99.66384,28.82362],[99.41013,28.53175],[99.40034,28.16539],[99.30644,28.23286],[99.2843,28.29757],[99.23898,28.32281],[99.16345,28.42914],[99.18834,28.44333],[99.15744,28.64088],[99.1286,28.69977],[99.11487,29.22679],[98.96003,29.18663],[99.01582,29.04056],[98.92501,28.98111],[98.91815,28.88796],[98.97376,28.87564],[98.97548,28.82933],[98.83197,28.80406],[98.78974,29.01054],[98.62495,28.9721],[98.64881,28.91937],[98.65104,28.86361],[98.66254,28.79549],[98.6804,28.77239],[98.68125,28.73319],[98.63113,28.69103],[98.5968,28.68622],[98.63697,28.49072],[98.75884,28.33218],[98.69361,28.21789],[98.60881,28.1725],[98.39355,28.10953],[98.36952,28.26084],[98.28987,28.39804],[98.20129,28.35666],[98.26789,28.24421],[98.16696,28.21002],[98.15337,28.12114],[98.13829,27.96302],[98.32641,27.51385],[98.42529,27.55404],[98.43353,27.67086],[98.69582,27.56499],[98.7333,26.85615],[98.77547,26.61994],[98.72741,26.36183],[98.67797,26.24487],[98.7329,26.17218],[98.66884,26.09165],[98.63128,26.15492],[98.57085,26.11547],[98.60763,26.01512],[98.70818,25.86241],[98.63128,25.79937],[98.54064,25.85129],[98.40606,25.61129],[98.31268,25.55307],[98.25774,25.6051],[98.16848,25.62739],[98.18084,25.56298],[98.12591,25.50722],[98.14925,25.41547],[97.92541,25.20815],[97.83857,25.27186],[97.75926,25.09679],[97.72216,25.08508],[97.71729,24.98193],[97.72903,24.91332],[97.76046,24.88223],[97.79949,24.85655],[97.76481,24.8289],[97.73127,24.83015],[97.70236,24.84356],[97.64354,24.79171],[97.56648,24.76475],[97.56383,24.75535],[97.5542,24.74943],[97.54675,24.74202],[97.57026,24.72297],[97.56286,24.54535],[97.52757,24.43748],[97.60029,24.4401],[97.67146,24.45472],[97.72047,24.35945],[97.65624,24.33781],[97.66723,24.30027],[97.73368,24.29672],[97.76799,24.26365],[97.72998,24.2302],[97.726,24.20293],[97.72799,24.18883],[97.7439,24.1843],[97.75305,24.16902],[97.73454,24.15192],[97.72903,24.12606],[97.63309,24.04983],[97.62639,24.00907],[97.51653,23.9395]]]}},{"type":"Feature","properties":{"id":"CN-SC"},"geometry":{"type":"Polygon","coordinates":[[[97.33989,32.9038],[97.37834,32.86978],[97.37766,32.80718],[97.4271,32.7122],[97.48168,32.65556],[97.5325,32.64241],[97.54417,32.62332],[97.66296,32.55781],[97.72544,32.52886],[97.99057,32.46806],[98.00148,32.45647],[98.02396,32.45321],[98.03873,32.43119],[98.07752,32.41974],[98.10499,32.39677],[98.12533,32.40402],[98.21863,32.34458],[98.23133,32.26579],[98.30497,32.12619],[98.44402,31.99715],[98.41449,31.83439],[98.56246,31.67705],[98.5883,31.6346],[98.71584,31.51116],[98.84433,31.42954],[98.88733,31.37774],[98.77567,31.24949],[98.69052,31.33751],[98.62478,31.33736],[98.60469,31.18725],[98.71009,31.11997],[98.75181,31.04499],[98.80725,30.98496],[98.77653,30.90428],[98.96947,30.75039],[98.90338,30.69254],[98.99642,30.15344],[99.0438,30.07979],[99.05651,29.93708],[99.01119,29.8189],[99.01153,29.77674],[98.98956,29.66359],[99.04535,29.56912],[99.04638,29.52118],[99.06097,29.47502],[99.06698,29.30376],[99.11487,29.22679],[99.1286,28.69977],[99.15744,28.64088],[99.18834,28.44333],[99.16345,28.42914],[99.23898,28.32281],[99.2843,28.29757],[99.30644,28.23286],[99.40034,28.16539],[99.41013,28.53175],[99.66384,28.82362],[99.95635,28.56462],[100.18638,28.25237],[100.02468,28.16766],[100.32096,27.71635],[100.43701,27.8673],[100.54618,27.81812],[100.65261,27.90857],[100.83835,27.67835],[101.05911,27.21799],[101.16451,27.19693],[101.14768,27.03099],[101.399,26.85899],[101.38492,26.73242],[101.49307,26.78638],[101.46629,26.60387],[101.39453,26.6094],[101.40209,26.55075],[101.63177,26.3974],[101.63726,26.26632],[101.79485,26.16529],[101.83536,26.0466],[101.91467,26.10889],[102.10864,26.08546],[102.39257,26.29926],[102.61196,26.37956],[102.64766,26.22629],[102.99648,26.36264],[102.99579,26.45996],[103.05725,26.53079],[103.02206,26.59144],[103.02343,26.66249],[102.96644,26.83678],[102.89571,26.91441],[102.89073,27.00759],[102.86945,27.03114],[102.91254,27.13247],[102.88352,27.25585],[102.88181,27.29063],[102.93846,27.41764],[102.98652,27.37146],[103.07046,27.41291],[103.09295,27.39676],[103.14102,27.42739],[103.15492,27.474],[103.18943,27.52654],[103.20608,27.53536],[103.22221,27.57143],[103.2895,27.56778],[103.29568,27.6046],[103.27629,27.63517],[103.29637,27.64247],[103.37121,27.7124],[103.39284,27.71681],[103.48726,27.79899],[103.50528,27.83452],[103.50116,27.91949],[103.57412,27.98242],[103.50288,27.96984],[103.49155,28.03456],[103.42649,28.05122],[103.45447,28.09954],[103.44932,28.12376],[103.50185,28.1495],[103.59386,28.23816],[103.64433,28.2628],[103.6899,28.23596],[103.71007,28.19308],[103.73428,28.23816],[103.77376,28.23861],[103.78363,28.25721],[103.80998,28.26863],[103.82646,28.28677],[103.865,28.30098],[103.87556,28.31813],[103.85032,28.35847],[103.85959,28.38807],[103.83075,28.43563],[103.82766,28.46401],[103.78749,28.51757],[103.80826,28.56869],[103.83264,28.58813],[103.84517,28.66995],[103.86474,28.6698],[103.87762,28.62656],[103.92242,28.63124],[103.94525,28.60562],[104.05752,28.63063],[104.08241,28.61014],[104.15948,28.64314],[104.2345,28.64148],[104.24566,28.66769],[104.30574,28.62069],[104.36977,28.65293],[104.40153,28.64555],[104.42504,28.63048],[104.41509,28.6014],[104.35518,28.55994],[104.24446,28.53717],[104.30076,28.30679],[104.35432,28.34366],[104.44805,28.11801],[104.29698,28.05259],[104.56409,27.85],[104.87205,27.90766],[105.03616,28.09742],[105.17074,28.07258],[105.30515,27.70875],[105.39562,27.76831],[105.50617,27.77105],[105.64212,27.66999],[105.77825,27.72228],[105.9288,27.732],[106.0802,27.78107],[106.20861,27.76436],[106.33872,27.8245],[106.31521,27.97954],[106.23865,28.01986],[106.26457,28.06372],[106.20397,28.13754],[106.12561,28.17485],[105.97103,28.11468],[105.86082,28.14526],[105.88382,28.25358],[105.65895,28.308],[105.61071,28.46144],[105.68839,28.5702],[105.68778,28.58934],[105.69482,28.59635],[105.71001,28.58987],[105.7386,28.61903],[105.88142,28.602],[105.96691,28.76284],[106.10269,28.63937],[106.3655,28.47593],[106.36756,28.52662],[106.21238,28.92012],[106.03763,28.91381],[106.03969,28.95528],[105.97995,28.98081],[105.90983,28.92478],[105.90425,28.90382],[105.87181,28.938],[105.80022,28.94521],[105.78426,28.98231],[105.76435,28.99102],[105.74048,29.04131],[105.69705,29.30017],[105.65586,29.25435],[105.47183,29.2816],[105.45844,29.32831],[105.41381,29.31723],[105.443,29.41612],[105.37261,29.42285],[105.27717,29.57375],[105.38497,29.64405],[105.47475,29.68119],[105.56076,29.73784],[105.61466,29.84898],[105.72006,29.85583],[105.73448,30.02867],[105.58359,30.12092],[105.53947,30.17154],[105.6459,30.20448],[105.61809,30.273],[105.71817,30.2598],[105.7125,30.31865],[105.81275,30.44186],[106.16432,30.30591],[106.16775,30.2518],[106.22388,30.18045],[106.47623,30.30413],[106.6084,30.29627],[106.6702,30.14468],[106.72531,30.03462],[106.76582,30.01738],[106.83448,30.04665],[106.92066,30.02897],[106.97542,30.08677],[107.03121,30.03477],[107.19291,30.18727],[107.50431,30.63732],[107.42637,30.7318],[107.49435,30.8567],[107.63683,30.81233],[107.71373,30.89633],[107.76832,30.82058],[107.84866,30.79405],[107.99114,30.91076],[107.91543,30.93359],[108.05551,31.05205],[108.00899,31.08557],[108.08933,31.21045],[107.998,31.22718],[108.17481,31.3334],[108.22151,31.50684],[108.3403,31.5165],[108.41548,31.6145],[108.54011,31.67559],[108.273,31.9475],[108.35643,32.07384],[108.46492,32.07195],[108.36553,32.182],[108.50646,32.20263],[108.47934,32.2581],[108.18872,32.23385],[108.02616,32.22209],[107.9647,32.13782],[107.64919,32.41213],[107.48989,32.38315],[107.402,32.56562],[107.25437,32.40083],[107.1215,32.48543],[107.05799,32.70208],[106.84204,32.7327],[106.41941,32.62434],[106.11007,32.72144],[106.02527,32.85853],[105.60504,32.70064],[105.49432,32.91403],[105.38909,32.9257],[105.38909,32.83517],[105.41982,32.78597],[105.46068,32.74974],[105.11032,32.60004],[104.40376,32.76475],[104.27604,32.91446],[104.43019,33.01643],[104.35037,33.03989],[104.37492,33.11426],[104.2963,33.31202],[104.40908,33.28447],[104.43414,33.32809],[104.36496,33.35017],[104.29252,33.33569],[104.10026,33.68435],[103.77891,33.66606],[103.54511,33.69606],[103.50666,33.80767],[103.33671,33.74147],[103.16162,33.8188],[103.12866,34.07512],[103.1714,34.06972],[103.18016,34.08479],[102.89039,34.33266],[102.62054,34.16579],[102.65865,34.07768],[102.59342,34.09929],[102.43205,34.0791],[102.3912,33.97753],[102.14332,33.98379],[102.46879,33.47211],[101.81579,33.10937],[101.93527,33.58545],[101.77665,33.53681],[101.65546,33.36121],[101.72996,33.26883],[101.64001,33.09844],[101.46165,33.22389],[101.37702,33.18238],[101.13155,33.28548],[101.22699,32.73675],[101.1185,32.63619],[101.06271,32.67868],[100.94032,32.60756],[100.71819,32.67492],[100.66463,32.52481],[100.54687,32.5714],[100.49554,32.65671],[99.8822,33.04781],[99.73388,32.72375],[99.36035,32.90092],[98.85497,33.14675],[98.73962,33.43717],[98.42651,33.85217],[98.42513,34.06233],[97.85797,34.21066],[97.66158,34.12431],[97.65266,33.93538],[97.39517,33.89492],[97.40409,33.63062],[97.7584,33.40536],[97.62348,33.33769],[97.59841,33.25964],[97.4858,33.166],[97.49061,33.11512],[97.53078,32.99167],[97.4319,32.9813],[97.33989,32.9038]]]}},{"type":"Feature","properties":{"id":"CN-GS"},"geometry":{"type":"Polygon","coordinates":[[[92.40874,39.03625],[93.11599,39.17372],[93.42086,38.9092],[94.35607,38.76265],[94.5346,38.35781],[94.99053,38.43638],[95.24459,38.30502],[95.65658,38.36857],[96.29859,38.15669],[96.65702,38.22901],[96.66595,38.48665],[97.05459,38.6284],[96.9358,38.9108],[96.97769,39.20884],[97.3368,39.16733],[98.08937,38.78513],[98.24867,38.88515],[98.31218,39.02638],[98.8179,39.05651],[100.17677,38.2112],[100.0209,38.49766],[100.93105,38.16749],[100.90736,38.04052],[101.33857,37.8331],[101.99706,37.69251],[102.17628,37.44542],[102.63256,37.12254],[102.44853,36.96868],[102.48098,36.95421],[102.49634,36.95668],[102.5045,36.9422],[102.55823,36.91984],[102.5802,36.87714],[102.63582,36.85407],[102.72319,36.76886],[102.59651,36.71081],[102.71598,36.60009],[102.82859,36.36891],[102.84095,36.33421],[102.8952,36.33186],[102.92026,36.30073],[103.02618,36.25257],[103.01931,36.23333],[103.06823,36.20813],[102.9927,36.19524],[102.89623,36.07296],[102.97399,36.03688],[102.94755,35.96578],[102.9467,35.83507],[102.78499,35.86262],[102.6844,35.78077],[102.80078,35.57258],[102.75169,35.49533],[102.50381,35.58808],[102.4324,35.43409],[102.2834,35.40864],[102.40218,35.18503],[101.92016,34.84424],[101.92153,34.59732],[102.15499,34.51221],[102.25318,34.36441],[101.73442,34.08365],[100.93997,34.39529],[100.7611,34.17545],[101.19232,33.79455],[101.17034,33.65463],[101.58782,33.67349],[101.62628,33.49731],[101.77665,33.53681],[101.93527,33.58545],[101.81579,33.10937],[102.46879,33.47211],[102.14332,33.98379],[102.3912,33.97753],[102.43205,34.0791],[102.59342,34.09929],[102.65865,34.07768],[102.62054,34.16579],[102.89039,34.33266],[103.18016,34.08479],[103.1714,34.06972],[103.12866,34.07512],[103.16162,33.8188],[103.33671,33.74147],[103.50666,33.80767],[103.54511,33.69606],[103.77891,33.66606],[104.10026,33.68435],[104.29252,33.33569],[104.36496,33.35017],[104.43414,33.32809],[104.40908,33.28447],[104.2963,33.31202],[104.37492,33.11426],[104.35037,33.03989],[104.43019,33.01643],[104.27604,32.91446],[104.40376,32.76475],[105.11032,32.60004],[105.46068,32.74974],[105.41982,32.78597],[105.38909,32.83517],[105.38909,32.9257],[105.49432,32.91403],[105.62187,32.88304],[105.85945,32.93896],[105.93687,33.02147],[105.91884,33.13553],[105.97017,33.1489],[105.95352,33.22834],[105.82134,33.25648],[105.69276,33.39088],[105.83593,33.38243],[105.8337,33.47856],[105.95764,33.61061],[106.0905,33.61347],[106.18869,33.55312],[106.40327,33.61276],[106.51519,33.50246],[106.58454,33.5986],[106.45511,33.81595],[106.48807,33.85872],[106.40567,33.90846],[106.44498,33.94193],[106.56738,34.1317],[106.50798,34.27282],[106.67621,34.25863],[106.71295,34.37092],[106.69518,34.36972],[106.67716,34.3846],[106.6611,34.38325],[106.63338,34.39444],[106.61828,34.42078],[106.60995,34.44896],[106.59493,34.45236],[106.5945,34.46729],[106.58025,34.46693],[106.55459,34.49028],[106.53802,34.48844],[106.47399,34.52183],[106.46318,34.53024],[106.45245,34.53152],[106.40593,34.5243],[106.39031,34.51129],[106.37778,34.52147],[106.36147,34.51419],[106.33048,34.51928],[106.34267,34.56382],[106.57373,34.77503],[106.48395,35.0165],[106.5533,35.09308],[106.61184,35.07257],[106.69406,35.08142],[106.71964,35.10404],[106.8338,35.0803],[107.03155,35.05473],[107.20733,34.87832],[107.29728,34.93773],[107.52319,34.91155],[107.56542,34.97262],[107.63854,34.93266],[107.86514,34.98809],[107.76077,35.07187],[107.68215,35.21561],[107.64095,35.24477],[107.70721,35.25893],[107.73673,35.314],[107.81398,35.27547],[107.96333,35.24309],[108.20468,35.307],[108.23902,35.25963],[108.47248,35.27],[108.61083,35.3133],[108.62182,35.54284],[108.50303,35.89044],[108.64654,35.95021],[108.69804,36.10903],[108.6383,36.43039],[108.45737,36.42984],[108.30562,36.55625],[108.0574,36.59678],[107.66944,36.83017],[107.30071,36.92162],[107.27462,37.09462],[106.8877,37.09955],[106.77646,37.1584],[106.64085,37.1157],[106.63124,36.73668],[106.48773,36.64197],[106.49047,36.30848],[106.83002,36.21824],[106.9313,36.12456],[106.9392,35.93798],[106.85474,35.89238],[106.92958,35.77632],[106.73698,35.69132],[106.6151,35.73341],[106.43417,35.71083],[106.50146,35.35265],[106.35177,35.23356],[106.17736,35.43577],[106.10458,35.36385],[106.06681,35.45703],[105.89584,35.41535],[105.88588,35.44892],[106.00398,35.48024],[106.01154,35.52077],[105.9003,35.55206],[105.83919,35.49254],[105.80211,35.59646],[105.67714,35.68909],[105.7434,35.73675],[105.55801,35.72421],[105.48591,35.72226],[105.42137,35.81558],[105.37536,35.79832],[105.36609,35.84119],[105.39665,35.85844],[105.33382,35.88209],[105.33554,36.02189],[105.53466,36.13842],[105.45227,36.31457],[105.19958,36.70751],[105.3376,36.75924],[105.2758,36.87357],[105.17829,36.91037],[105.16525,36.98884],[104.96612,37.04038],[104.84184,37.21884],[104.61044,37.22048],[104.66606,37.40889],[104.28153,37.42907],[103.83865,37.65773],[103.39507,37.88352],[103.36074,38.08809],[103.54476,38.15615],[104.41268,39.398],[103.46374,39.3619],[103.11354,39.1817],[102.13714,39.16946],[101.7279,38.64154],[100.84075,39.18224],[99.70229,39.98659],[100.28045,40.67439],[99.95086,40.96953],[98.3139,40.56806],[97.2134,42.79942],[96.37926,42.72055],[96.0308,42.49893],[96.13174,42.00083],[95.30776,41.55535],[95.13061,41.77438],[94.22424,41.35001],[93.76556,40.66605],[92.92785,40.58058],[92.40874,39.03625]]]}},{"type":"Feature","properties":{"id":"CN-NX"},"geometry":{"type":"Polygon","coordinates":[[[104.28153,37.42907],[104.66606,37.40889],[104.61044,37.22048],[104.84184,37.21884],[104.96612,37.04038],[105.16525,36.98884],[105.17829,36.91037],[105.2758,36.87357],[105.3376,36.75924],[105.19958,36.70751],[105.45227,36.31457],[105.53466,36.13842],[105.33554,36.02189],[105.33382,35.88209],[105.39665,35.85844],[105.36609,35.84119],[105.37536,35.79832],[105.42137,35.81558],[105.48591,35.72226],[105.55801,35.72421],[105.7434,35.73675],[105.67714,35.68909],[105.80211,35.59646],[105.83919,35.49254],[105.9003,35.55206],[106.01154,35.52077],[106.00398,35.48024],[105.88588,35.44892],[105.89584,35.41535],[106.06681,35.45703],[106.10458,35.36385],[106.17736,35.43577],[106.35177,35.23356],[106.50146,35.35265],[106.43417,35.71083],[106.6151,35.73341],[106.73698,35.69132],[106.92958,35.77632],[106.85474,35.89238],[106.9392,35.93798],[106.9313,36.12456],[106.83002,36.21824],[106.49047,36.30848],[106.48773,36.64197],[106.63124,36.73668],[106.64085,37.1157],[106.77646,37.1584],[106.8877,37.09955],[107.27462,37.09462],[107.33264,37.15621],[107.25299,37.31447],[107.28183,37.48412],[107.65674,37.86753],[107.40749,37.98642],[107.1627,38.16155],[106.87911,38.13131],[106.47811,38.31364],[106.5921,38.38714],[106.95396,38.94819],[106.96357,39.06451],[106.86332,39.0917],[106.78161,39.37518],[106.59484,39.36934],[106.28448,39.27053],[106.28654,39.14337],[106.13479,39.15615],[105.85121,38.62116],[105.8258,38.34219],[105.86597,38.29586],[105.76435,38.18719],[105.81756,38.00617],[105.76606,37.79513],[105.16525,37.66343],[105.01899,37.58022],[104.43054,37.51299],[104.28153,37.42907]]]}},{"type":"Feature","properties":{"id":"CN-HL"},"geometry":{"type":"Polygon","coordinates":[[[121.20391,52.57468],[121.87133,52.27152],[122.19817,52.50786],[122.7365,52.20255],[122.96447,51.31173],[123.57146,51.2516],[124.07409,50.54834],[123.92509,50.37349],[123.78844,50.45575],[123.76373,50.33143],[123.90929,50.18569],[124.31476,50.22744],[124.43115,50.44001],[125.30044,50.13554],[125.18989,49.93442],[125.25993,49.33966],[125.21598,49.27609],[125.21118,49.19314],[125.14698,49.18204],[125.10955,49.11096],[125.01857,49.17429],[124.86991,49.17205],[124.67834,48.83037],[124.61036,48.74894],[124.52075,48.50068],[124.56607,48.26491],[124.4902,48.11384],[124.25846,48.53479],[123.99169,48.37723],[123.54984,48.02644],[123.2975,47.95084],[123.16635,47.78294],[122.84706,47.67047],[122.56896,47.53482],[122.39868,47.34254],[122.60261,47.12481],[122.83264,47.06217],[122.79281,46.94135],[122.88413,46.95682],[123.00704,46.72103],[123.51447,46.95869],[123.49182,46.83483],[123.62949,46.8118],[123.59447,46.68642],[123.27072,46.66381],[123.26488,46.57963],[123.17115,46.61077],[123.07914,46.59897],[123.05614,46.62774],[122.99331,46.57231],[123.00876,46.43052],[123.17081,46.24777],[123.75274,46.2613],[123.90586,46.30947],[124.01847,46.01794],[124.0068,45.75602],[124.28283,45.54915],[124.52178,45.42014],[124.95746,45.49575],[125.07728,45.38856],[125.68256,45.51067],[125.69526,45.34538],[125.91499,45.19268],[126.15531,45.13604],[126.49898,45.24685],[126.62017,45.23283],[126.79115,45.13967],[126.94496,45.13749],[127.08778,44.93029],[127.01156,44.89406],[126.9786,44.82787],[127.04177,44.5665],[127.19524,44.64911],[127.39471,44.62884],[127.55538,44.5731],[127.48638,44.43083],[127.62611,44.18318],[128.02436,44.03676],[128.33541,44.5709],[128.88198,43.50374],[129.20883,43.58138],[129.23354,43.80381],[129.93942,44.03528],[130.05271,43.82362],[130.35003,44.05576],[130.41526,43.64899],[130.61988,43.61768],[130.87394,43.43048],[131.00852,43.50747],[131.13796,43.42624],[131.30739,43.47335],[131.19492,43.53047],[131.22962,43.6552],[131.20525,43.82164],[131.26176,43.94011],[131.23583,43.96085],[131.25484,44.03131],[131.30365,44.04262],[131.10603,44.70673],[130.95639,44.85154],[131.48415,44.99513],[131.68466,45.12374],[131.66852,45.2196],[131.76532,45.22609],[131.86903,45.33636],[131.99417,45.2567],[132.83978,45.05916],[132.96373,45.0212],[133.12293,45.1332],[133.09279,45.25693],[133.19652,45.51284],[133.41083,45.57723],[133.47976,45.67212],[133.43616,45.71049],[133.48457,45.86203],[133.52251,45.89849],[133.57915,45.8655],[133.61228,45.90171],[133.60645,45.9379],[133.67013,45.94136],[133.67569,45.9759],[133.73897,46.0637],[133.67065,46.14416],[133.91441,46.26273],[133.86154,46.34526],[133.94977,46.40117],[133.84104,46.46681],[133.91647,46.59638],[134.01466,46.66663],[134.02255,46.77937],[134.11388,47.06591],[134.24777,47.12224],[134.14375,47.26222],[134.20074,47.34301],[134.31644,47.43737],[134.50252,47.44666],[134.7671,47.72051],[134.57221,48.006],[134.67098,48.1564],[134.76619,48.36286],[134.49516,48.42884],[132.66989,47.96491],[132.57309,47.71741],[131.90448,47.68011],[131.2635,47.73325],[131.09871,47.6852],[130.95985,47.6957],[130.90915,47.90623],[130.65103,48.10052],[130.84462,48.30942],[130.52147,48.61745],[130.66946,48.88251],[130.61358,48.88862],[130.43232,48.90844],[130.2355,48.86741],[129.85416,49.11067],[129.67598,49.29596],[129.50685,49.42398],[129.40398,49.44194],[129.35317,49.3481],[129.23232,49.40353],[129.11153,49.36813],[128.72896,49.58676],[127.83476,49.5748],[127.53516,49.84306],[127.49299,50.01251],[127.60515,50.23503],[127.37384,50.28393],[127.36009,50.43787],[127.28765,50.46585],[127.36335,50.58306],[127.28165,50.72075],[127.14586,50.91152],[126.93135,51.0841],[126.90369,51.3238],[126.68349,51.70607],[126.44606,51.98254],[126.56524,52.12042],[126.03378,52.58052],[126.08562,52.79923],[125.96099,52.76995],[125.6131,53.07229],[125.17522,53.20225],[124.46078,53.21881],[123.86158,53.49391],[123.26989,53.54843],[122.85966,53.47395],[122.35063,53.49565],[121.48681,53.33169],[121.82738,53.03956],[121.20391,52.57468]]]}},{"type":"Feature","properties":{"id":"CN-JL"},"geometry":{"type":"Polygon","coordinates":[[[121.64337,45.73877],[121.74568,45.68267],[121.95098,45.71097],[121.99836,45.6366],[122.01965,45.48324],[122.16522,45.41484],[122.1453,45.2971],[122.08213,44.91327],[122.11715,44.57237],[122.33001,44.22256],[122.62115,44.2688],[123.12927,44.52784],[123.1327,44.34938],[123.37715,44.15215],[123.3229,44.06045],[123.53301,43.64203],[123.32153,43.48979],[123.70262,43.36662],[123.7445,43.47758],[123.81866,43.49203],[124.02019,43.28045],[124.094,43.2902],[124.11151,43.2482],[124.27734,43.23144],[124.27562,43.16912],[124.41707,43.07415],[124.35836,42.88854],[124.45793,42.81907],[124.78958,43.11802],[124.91146,43.13732],[124.8373,43.02498],[124.86305,42.97225],[124.84863,42.79086],[124.96227,42.80774],[124.99626,42.64305],[125.09307,42.61728],[125.06835,42.52221],[125.18817,42.40951],[125.16843,42.30664],[125.21581,42.29889],[125.26396,42.31114],[125.30319,42.2172],[125.31383,42.12611],[125.35383,42.18197],[125.4848,42.13871],[125.28671,41.9554],[125.32241,41.67034],[125.44738,41.67393],[125.49167,41.53222],[125.54283,41.39767],[125.57544,41.39174],[125.63449,41.33325],[125.63724,41.26877],[125.75225,41.23005],[125.78487,41.16185],[125.63552,40.95086],[125.56892,40.89327],[125.66574,40.91403],[125.70608,40.86562],[125.77423,40.89262],[125.80839,40.86614],[125.90984,40.91195],[125.9131,40.88574],[126.00889,40.91286],[126.12768,41.0532],[126.11824,41.08219],[126.15016,41.08698],[126.22364,41.12746],[126.23445,41.14608],[126.2717,41.15371],[126.2923,41.16948],[126.28389,41.18795],[126.30552,41.18989],[126.31736,41.2285],[126.35307,41.24322],[126.36594,41.28438],[126.43203,41.33042],[126.43341,41.35026],[126.51615,41.37333],[126.47186,41.34356],[126.53189,41.35206],[126.61485,41.67304],[126.74497,41.67342],[126.70394,41.75274],[126.79304,41.69156],[126.80308,41.76401],[126.83166,41.71437],[126.86797,41.78193],[126.91337,41.80171],[126.93792,41.80548],[126.93122,41.77406],[127.0119,41.73904],[127.04092,41.74685],[127.12537,41.5976],[127.18305,41.58848],[127.10057,41.54462],[127.16872,41.52245],[127.20605,41.53209],[127.20623,41.5177],[127.23318,41.52098],[127.23635,41.49527],[127.26931,41.51307],[127.28588,41.50407],[127.24888,41.48022],[127.28776,41.48395],[127.3536,41.45546],[127.35643,41.47675],[127.39445,41.47855],[127.40604,41.4581],[127.44775,41.4588],[127.47367,41.46897],[127.52998,41.46787],[127.58156,41.42573],[127.61049,41.43037],[127.64044,41.40745],[127.65452,41.42052],[127.69992,41.41859],[127.74653,41.42515],[127.83914,41.42065],[127.85614,41.40771],[127.93055,41.44491],[128.03827,41.41698],[128.0326,41.39149],[128.05904,41.39438],[128.10882,41.36205],[128.1235,41.38022],[128.14925,41.38138],[128.16478,41.40224],[128.18546,41.41279],[128.20272,41.40874],[128.20658,41.42702],[128.22126,41.44472],[128.30716,41.60322],[128.15119,41.74568],[128.04487,42.01769],[128.73229,42.03756],[128.94601,42.02098],[128.95683,42.08013],[129.01914,42.09185],[129.04815,42.13425],[129.12823,42.14825],[129.12729,42.15984],[129.16368,42.16403],[129.15887,42.1807],[129.19492,42.20296],[129.2023,42.23843],[129.17707,42.25749],[129.21792,42.26524],[129.19801,42.31387],[129.25449,42.32162],[129.21243,42.37236],[129.2665,42.38016],[129.29912,42.41255],[129.35646,42.4574],[129.37963,42.4422],[129.42821,42.44626],[129.54794,42.37052],[129.59901,42.45449],[129.71694,42.43137],[129.74226,42.47526],[129.73669,42.56231],[129.75294,42.59409],[129.77183,42.69435],[129.76037,42.72179],[129.7835,42.76521],[129.80719,42.79218],[129.81342,42.8474],[129.83711,42.87269],[129.84372,42.92054],[129.87204,42.91633],[129.84603,42.95277],[129.8865,43.00395],[129.95082,43.01051],[129.96409,42.97306],[130.14369,42.98468],[130.09735,42.91243],[130.26849,42.9025],[130.2309,42.79174],[130.24051,42.72658],[130.35467,42.63408],[130.38007,42.60149],[130.43226,42.60831],[130.4302,42.55156],[130.47397,42.55295],[130.47328,42.61665],[130.51105,42.61981],[130.51345,42.57836],[130.55143,42.52158],[130.62107,42.58413],[130.56576,42.68925],[130.40213,42.70788],[130.44361,42.76205],[130.66524,42.84753],[131.02438,42.86518],[131.02668,42.91246],[131.135,42.94114],[131.10274,43.04734],[131.20414,43.13654],[131.19031,43.21385],[131.30324,43.39498],[131.30739,43.47335],[131.13796,43.42624],[131.00852,43.50747],[130.87394,43.43048],[130.61988,43.61768],[130.41526,43.64899],[130.35003,44.05576],[130.05271,43.82362],[129.93942,44.03528],[129.23354,43.80381],[129.20883,43.58138],[128.88198,43.50374],[128.33541,44.5709],[128.02436,44.03676],[127.62611,44.18318],[127.48638,44.43083],[127.55538,44.5731],[127.39471,44.62884],[127.19524,44.64911],[127.04177,44.5665],[126.9786,44.82787],[127.01156,44.89406],[127.08778,44.93029],[126.94496,45.13749],[126.79115,45.13967],[126.62017,45.23283],[126.49898,45.24685],[126.15531,45.13604],[125.91499,45.19268],[125.69526,45.34538],[125.68256,45.51067],[125.07728,45.38856],[124.95746,45.49575],[124.52178,45.42014],[124.28283,45.54915],[124.0068,45.75602],[124.01847,46.01794],[123.90586,46.30947],[123.75274,46.2613],[123.17081,46.24777],[122.81272,46.06751],[122.72964,45.69682],[122.5003,45.78691],[122.43953,45.94351],[122.2586,45.79434],[121.8727,46.04083],[121.75735,45.99505],[121.82121,45.8728],[121.64337,45.73877]]]}},{"type":"Feature","properties":{"id":"CN-LN"},"geometry":{"type":"Polygon","coordinates":[[[118.88992,40.9576],[118.90056,40.74023],[119.14638,40.6611],[119.26277,40.53154],[119.5642,40.54876],[119.58137,40.36799],[119.63458,40.25568],[119.75921,40.14397],[119.729,40.10459],[119.75423,40.06532],[119.78118,40.03812],[119.81449,40.04995],[119.84607,40.03313],[119.83955,40.00079],[119.98931,39.77661],[120.97044,38.45359],[123.90497,38.79949],[124.16679,39.77477],[124.23201,39.9248],[124.34703,39.94804],[124.37346,40.0278],[124.3322,40.05573],[124.40719,40.13655],[124.46994,40.17546],[124.59423,40.27271],[124.71919,40.32115],[124.741,40.37048],[124.88502,40.4668],[125.0354,40.46079],[125.0045,40.52371],[125.46592,40.70562],[125.70608,40.86562],[125.66574,40.91403],[125.56892,40.89327],[125.63552,40.95086],[125.78487,41.16185],[125.75225,41.23005],[125.63724,41.26877],[125.63449,41.33325],[125.57544,41.39174],[125.54283,41.39767],[125.49167,41.53222],[125.44738,41.67393],[125.32241,41.67034],[125.28671,41.9554],[125.4848,42.13871],[125.35383,42.18197],[125.31383,42.12611],[125.30319,42.2172],[125.26396,42.31114],[125.21581,42.29889],[125.16843,42.30664],[125.18817,42.40951],[125.06835,42.52221],[125.09307,42.61728],[124.99626,42.64305],[124.96227,42.80774],[124.84863,42.79086],[124.86305,42.97225],[124.8373,43.02498],[124.91146,43.13732],[124.78958,43.11802],[124.45793,42.81907],[124.35836,42.88854],[124.41707,43.07415],[124.27562,43.16912],[124.27734,43.23144],[124.11151,43.2482],[124.094,43.2902],[124.02019,43.28045],[123.81866,43.49203],[123.7445,43.47758],[123.70262,43.36662],[123.55911,42.96446],[123.4671,43.03853],[123.2611,42.99385],[123.1739,42.92525],[123.21338,42.82738],[123.05786,42.7702],[122.88516,42.76617],[122.85049,42.71675],[122.49069,42.83796],[122.31422,42.83267],[122.45601,42.75835],[122.38838,42.67284],[122.2047,42.71145],[122.19234,42.67536],[122.05432,42.71851],[121.94961,42.68445],[121.85794,42.53891],[121.6674,42.43181],[121.60251,42.50728],[121.31275,42.43891],[121.26193,42.38111],[121.02642,42.24097],[120.95226,42.26689],[120.53993,42.14864],[120.40878,41.98297],[120.18424,41.84245],[120.09498,41.68932],[120.02975,41.71341],[120.03662,41.81277],[119.83509,42.1214],[119.84573,42.21148],[119.69432,42.22724],[119.53794,42.29153],[119.56386,42.34192],[119.49932,42.39354],[119.46962,42.34116],[119.27736,42.26041],[119.23736,42.19596],[119.29401,42.14189],[119.3795,42.08803],[119.3692,42.02787],[119.3201,41.97199],[119.32868,41.8808],[119.3074,41.80778],[119.28268,41.78155],[119.30843,41.76452],[119.29641,41.70803],[119.30465,41.64187],[119.33538,41.61467],[119.4128,41.58643],[119.41606,41.56138],[119.35546,41.56241],[119.39872,41.50304],[119.36576,41.43191],[119.29435,41.32732],[119.23562,41.3149],[119.24972,41.27264],[119.07325,41.08608],[118.92631,41.06382],[119.01695,40.97523],[118.88992,40.9576]]]}},{"type":"Feature","properties":{"id":"IN-KL"},"geometry":{"type":"Polygon","coordinates":[[[74.66307,12.69394],[75.25839,11.52644],[75.53074,11.70527],[75.52654,11.7288],[75.5401,11.74132],[75.53271,11.7593],[75.54396,11.75998],[75.5419,11.73569],[75.55494,11.72283],[75.53778,11.71401],[75.54568,11.70409],[75.53649,11.69283],[75.26869,11.50222],[76.77589,8.02795],[77.13114,8.32641],[77.17217,8.33346],[77.16093,8.37524],[77.22933,8.45072],[77.20024,8.50734],[77.28675,8.54401],[77.17655,8.7385],[77.26135,8.843],[77.15045,9.01496],[77.42065,9.51543],[77.16865,9.61632],[77.24693,9.80447],[77.25997,10.02971],[77.20298,10.11759],[77.27645,10.13382],[77.21465,10.36423],[76.96403,10.221],[76.8758,10.27505],[76.81417,10.6326],[76.88438,10.63901],[76.86876,10.68658],[76.91614,10.80312],[76.8497,10.8188],[76.84181,10.86668],[76.65779,10.92703],[76.74156,11.04887],[76.74817,11.13511],[76.73229,11.22504],[76.43531,11.19456],[76.54775,11.35071],[76.24966,11.51072],[76.25507,11.59271],[76.31026,11.57766],[76.38072,11.60204],[76.42948,11.66568],[76.18949,11.87608],[76.14692,11.86718],[76.11482,11.84719],[76.11259,11.97887],[76.00307,11.93185],[75.86059,11.95502],[75.78987,12.08296],[75.72172,12.08632],[75.53941,12.20883],[75.49392,12.29103],[75.43264,12.30646],[75.36621,12.45702],[75.42955,12.46574],[75.4038,12.5063],[75.34252,12.46356],[75.26956,12.55138],[75.28123,12.57282],[75.33376,12.58304],[75.27248,12.6204],[75.2275,12.55774],[75.18905,12.62827],[75.13807,12.63698],[75.16107,12.67417],[75.12519,12.67785],[75.0991,12.70599],[75.06872,12.66228],[75.04108,12.67484],[75.0531,12.71804],[74.98211,12.73906],[75.01224,12.77204],[75.0004,12.79506],[74.86444,12.76057],[74.66307,12.69394]]]}},{"type":"Feature","properties":{"id":"IN-KA"},"geometry":{"type":"Polygon","coordinates":[[[73.87481,14.75496],[74.66307,12.69394],[74.86444,12.76057],[75.0004,12.79506],[75.01224,12.77204],[74.98211,12.73906],[75.0531,12.71804],[75.04108,12.67484],[75.06872,12.66228],[75.0991,12.70599],[75.12519,12.67785],[75.16107,12.67417],[75.13807,12.63698],[75.18905,12.62827],[75.2275,12.55774],[75.27248,12.6204],[75.33376,12.58304],[75.28123,12.57282],[75.26956,12.55138],[75.34252,12.46356],[75.4038,12.5063],[75.42955,12.46574],[75.36621,12.45702],[75.43264,12.30646],[75.49392,12.29103],[75.53941,12.20883],[75.72172,12.08632],[75.78987,12.08296],[75.86059,11.95502],[76.00307,11.93185],[76.11259,11.97887],[76.11482,11.84719],[76.14692,11.86718],[76.18949,11.87608],[76.42948,11.66568],[76.539,11.69123],[76.61281,11.60717],[76.85691,11.60398],[76.84249,11.66938],[76.91013,11.79308],[76.97193,11.77628],[76.98772,11.81291],[77.12059,11.71863],[77.25465,11.81241],[77.42906,11.76199],[77.46665,11.84887],[77.49704,11.9426],[77.67917,11.94898],[77.72724,12.05409],[77.77925,12.112],[77.725,12.17963],[77.45738,12.20681],[77.48931,12.2766],[77.61806,12.36649],[77.63523,12.49088],[77.58853,12.51803],[77.60089,12.66579],[77.67514,12.68363],[77.67943,12.65625],[77.71265,12.66395],[77.71196,12.6817],[77.74114,12.67065],[77.73994,12.69962],[77.76329,12.6956],[77.77522,12.72415],[77.7608,12.72759],[77.7662,12.73906],[77.79298,12.74521],[77.79543,12.75668],[77.78011,12.7658],[77.80569,12.80041],[77.81032,12.82987],[77.78998,12.84092],[77.8365,12.86628],[77.88783,12.8713],[77.92868,12.89029],[77.93598,12.87289],[77.91666,12.82753],[77.94044,12.8287],[77.94834,12.85866],[78.00215,12.80359],[78.03322,12.85406],[78.08627,12.83356],[78.13236,12.78183],[78.23209,12.76208],[78.2575,12.86201],[78.315,12.85699],[78.35878,12.93739],[78.38891,12.9046],[78.40238,12.9133],[78.42118,12.89849],[78.44444,12.91171],[78.46598,12.86167],[78.46693,12.90267],[78.4374,12.92685],[78.40787,12.93898],[78.41929,12.95688],[78.43328,12.95763],[78.43663,12.9798],[78.47122,12.97252],[78.46461,13.04118],[78.51551,13.06559],[78.50958,13.08733],[78.53482,13.1079],[78.51671,13.12754],[78.57988,13.17343],[78.57293,13.18905],[78.5464,13.19332],[78.57464,13.21228],[78.58752,13.27252],[78.56821,13.31103],[78.55653,13.28464],[78.54228,13.28647],[78.53001,13.26734],[78.47911,13.3208],[78.44598,13.3071],[78.43826,13.33116],[78.39792,13.31412],[78.37594,13.34385],[78.40873,13.336],[78.40616,13.34903],[78.36547,13.3634],[78.38058,13.40581],[78.37818,13.50748],[78.40461,13.59343],[78.22042,13.58659],[78.19072,13.57074],[78.20325,13.62363],[78.14609,13.65849],[78.0867,13.64682],[78.06876,13.69736],[78.12154,13.69185],[78.09845,13.74938],[78.12807,13.77656],[78.11742,13.8094],[78.12189,13.84874],[78.05253,13.88457],[78.02919,13.86074],[78.00378,13.87691],[77.95709,13.82974],[77.99365,13.94156],[77.96911,13.95439],[77.92568,13.9049],[77.88568,13.93556],[77.81375,13.93156],[77.83332,13.86841],[77.79092,13.84174],[77.78509,13.81907],[77.72209,13.7889],[77.71934,13.74021],[77.69908,13.76873],[77.67574,13.76356],[77.67471,13.7964],[77.55969,13.72337],[77.52433,13.76206],[77.53429,13.70686],[77.5233,13.68918],[77.49464,13.71603],[77.48022,13.67751],[77.45035,13.69619],[77.46837,13.75489],[77.45206,13.81057],[77.42065,13.80307],[77.43009,13.84091],[77.26221,13.85341],[77.24367,13.90274],[77.20453,13.85474],[77.17414,13.91257],[77.16127,13.87607],[77.13123,13.84508],[77.15526,13.84441],[77.17432,13.76339],[77.07046,13.75172],[77.03269,13.7829],[77.00265,13.72837],[76.97862,13.82941],[77.04574,13.9239],[76.99665,13.96255],[76.99819,13.99154],[76.93107,14.03334],[76.97467,14.06165],[76.94635,14.11976],[76.88112,14.14156],[76.95339,14.19233],[77.02651,14.16736],[77.0284,14.05432],[77.13415,14.03684],[77.14822,13.99953],[77.31954,14.03084],[77.35645,13.9917],[77.3549,13.91057],[77.4064,13.88907],[77.43284,13.91074],[77.41447,13.94423],[77.44417,13.94706],[77.43215,13.98237],[77.33791,14.03967],[77.41275,14.12459],[77.35937,14.11826],[77.40383,14.17236],[77.51249,14.16121],[77.48502,14.29332],[77.39576,14.34089],[77.37224,14.31877],[77.4282,14.19998],[77.3767,14.21113],[77.35885,14.27619],[77.28967,14.29016],[77.28658,14.33607],[77.13466,14.33923],[77.09569,14.20198],[77.04952,14.24774],[76.93862,14.24624],[76.88404,14.38846],[77.00077,14.48205],[76.86704,14.47523],[76.84764,14.51612],[76.80335,14.53257],[76.76902,14.64338],[76.79443,14.78484],[76.84661,14.80094],[76.86996,14.97082],[76.7637,14.96485],[76.80044,15.09681],[76.95425,15.02686],[77.0272,15.03432],[77.06703,15.00348],[77.11921,15.03913],[77.17037,15.17619],[77.13998,15.23996],[77.16333,15.26911],[77.04471,15.35554],[77.01295,15.4507],[76.96935,15.50231],[77.02583,15.50463],[77.03561,15.63626],[77.12934,15.63675],[77.08024,15.71675],[77.02257,15.84675],[77.08351,15.92419],[77.23766,15.96496],[77.44571,15.94366],[77.51197,15.92864],[77.4864,16.16999],[77.49618,16.26115],[77.59798,16.29328],[77.58991,16.34402],[77.48451,16.38223],[77.28847,16.40595],[77.2344,16.47658],[77.32366,16.48942],[77.37636,16.48481],[77.47198,16.587],[77.47095,16.71216],[77.427,16.72252],[77.44279,16.78712],[77.47489,16.77956],[77.45532,16.92068],[77.50099,17.01657],[77.46181,17.11208],[77.36306,17.15563],[77.39301,17.19548],[77.38812,17.2259],[77.4597,17.28066],[77.44339,17.2941],[77.45713,17.373],[77.5227,17.35244],[77.53,17.44007],[77.61935,17.44581],[77.64385,17.47798],[77.69273,17.4892],[77.66175,17.52202],[77.59832,17.54355],[77.53309,17.5762],[77.50794,17.55648],[77.44417,17.58348],[77.45687,17.69897],[77.58244,17.74639],[77.50296,17.79241],[77.59634,17.87355],[77.59111,17.90548],[77.62038,17.90581],[77.63025,17.95644],[77.66741,17.96493],[77.61651,18.01154],[77.55231,18.03701],[77.56072,18.07438],[77.59935,18.08532],[77.57154,18.19217],[77.60021,18.27923],[77.5506,18.29211],[77.5142,18.31004],[77.4761,18.25103],[77.42949,18.30979],[77.35593,18.30824],[77.41052,18.39541],[77.37138,18.40095],[77.36074,18.44997],[77.32469,18.45567],[77.31345,18.44264],[77.24083,18.40681],[77.25148,18.37448],[77.22736,18.36438],[77.20822,18.2785],[77.17706,18.28453],[77.13303,18.20701],[77.09664,18.19641],[77.11921,18.15678],[77.05587,18.15474],[77.04771,18.18018],[77.00523,18.15792],[76.98858,18.19046],[76.95656,18.18858],[76.91888,18.12007],[76.97047,18.10041],[76.9454,18.08303],[76.95356,18.03921],[76.91596,18.03489],[76.91305,17.96779],[76.92043,17.91716],[76.88129,17.89413],[76.78842,17.87518],[76.76868,17.90001],[76.73486,17.88318],[76.79391,17.82779],[76.70207,17.72808],[76.68937,17.67739],[76.61573,17.77304],[76.57522,17.69341],[76.56612,17.76389],[76.52166,17.75833],[76.52801,17.72252],[76.49076,17.71418],[76.41111,17.60622],[76.33386,17.59035],[76.34485,17.49526],[76.37557,17.49378],[76.32579,17.45055],[76.37042,17.435],[76.36562,17.38078],[76.41351,17.36259],[76.3742,17.30836],[76.32631,17.34801],[76.27962,17.33441],[76.24357,17.38242],[76.16992,17.34916],[76.19207,17.29705],[76.11928,17.37242],[76.07276,17.33081],[76.05525,17.36178],[75.92428,17.32835],[75.892,17.36816],[75.82609,17.42976],[75.80995,17.36194],[75.76343,17.40633],[75.69425,17.40994],[75.67159,17.45514],[75.63262,17.47643],[75.57563,17.35162],[75.6546,17.2523],[75.63503,17.17392],[75.68138,17.07516],[75.64584,17.04496],[75.6788,16.9596],[75.58525,17.01558],[75.51023,16.95303],[75.46251,16.98735],[75.28432,16.9555],[75.27454,16.86286],[75.18064,16.83789],[75.07987,16.94893],[75.03061,16.93398],[74.96932,16.95172],[74.91044,16.93908],[74.97293,16.89523],[74.90427,16.86303],[74.91731,16.78777],[74.70136,16.7189],[74.65209,16.63619],[74.68763,16.61365],[74.6284,16.57762],[74.56472,16.55969],[74.54326,16.63339],[74.45056,16.65115],[74.4667,16.60757],[74.39117,16.57664],[74.38224,16.52991],[74.31255,16.56249],[74.31941,16.52793],[74.2674,16.54867],[74.24938,16.48465],[74.30414,16.46999],[74.36147,16.36988],[74.32353,16.39623],[74.33332,16.32145],[74.3316,16.26362],[74.36233,16.29344],[74.50687,16.22983],[74.48249,16.09496],[74.42344,16.10931],[74.35761,16.04779],[74.4328,16.05678],[74.46327,16.04416],[74.43151,15.95258],[74.38774,15.90339],[74.40696,15.84906],[74.36353,15.87367],[74.34104,15.85715],[74.39014,15.84411],[74.36765,15.82561],[74.3534,15.76681],[74.13848,15.72583],[74.11806,15.6518],[74.25659,15.64551],[74.24903,15.49206],[74.33486,15.28352],[74.25075,15.25371],[74.31838,15.1805],[74.2741,15.09996],[74.29195,15.02869],[74.20543,14.92819],[74.16217,14.94976],[73.87481,14.75496]]]}},{"type":"Feature","properties":{"id":"IN-TG"},"geometry":{"type":"Polygon","coordinates":[[[77.2344,16.47658],[77.28847,16.40595],[77.48451,16.38223],[77.58991,16.34402],[77.59798,16.29328],[77.49618,16.26115],[77.4864,16.16999],[77.51197,15.92864],[77.61119,15.91379],[77.65823,15.88671],[77.83882,15.87829],[77.89049,15.90306],[78.00807,15.86359],[78.03159,15.90652],[78.08412,15.83602],[78.17459,15.86326],[78.25561,15.9841],[78.41766,16.08012],[78.68408,16.04581],[79.22035,16.2239],[79.21897,16.56989],[79.42514,16.58289],[79.44471,16.61793],[79.63611,16.66151],[79.6737,16.69374],[79.71353,16.69079],[79.77842,16.73271],[79.81533,16.6921],[79.86305,16.7069],[79.913,16.62961],[79.9978,16.6528],[80.0735,16.81242],[79.99402,16.89408],[80.0414,16.92134],[80.0596,16.97093],[80.19556,17.0251],[80.28379,16.99654],[80.31537,16.94876],[80.3322,16.87732],[80.42524,16.84923],[80.46369,16.81965],[80.46661,16.79583],[80.60153,16.76953],[80.56514,16.85449],[80.60256,16.91838],[80.53476,16.9619],[80.49201,16.93251],[80.36584,16.9793],[80.39503,17.08353],[80.46747,17.02478],[80.50643,17.07335],[80.50695,17.11913],[80.58523,17.14587],[80.66865,17.10207],[80.64943,17.06958],[80.72462,17.07844],[80.86864,17.04217],[80.8798,17.16457],[80.92331,17.15645],[80.91293,17.21819],[80.99172,17.19606],[81.16939,17.23164],[81.18055,17.30639],[81.19308,17.32442],[81.2947,17.32016],[81.33178,17.40568],[80.83774,17.60933],[80.89267,17.63502],[80.91344,17.68],[80.88632,17.68442],[80.89387,17.73822],[80.96855,17.77811],[81.06948,17.69668],[81.08459,17.79315],[80.87345,18.2238],[80.51055,18.62802],[80.34782,18.59158],[80.27503,18.72104],[80.11196,18.6869],[79.9094,18.82474],[79.96261,18.86145],[79.93446,19.04524],[79.87232,19.03842],[79.85858,19.10883],[79.94407,19.16722],[79.92553,19.20937],[79.98012,19.39471],[79.92931,19.49442],[79.86236,19.51416],[79.79782,19.60151],[79.63216,19.57661],[79.59508,19.50802],[79.52762,19.54911],[79.49363,19.4962],[79.2418,19.62124],[79.24163,19.53876],[79.20833,19.4538],[79.06534,19.54781],[78.97985,19.56124],[78.93814,19.54814],[78.97178,19.58065],[78.9462,19.66974],[78.85866,19.65665],[78.84355,19.75474],[78.69918,19.78883],[78.54589,19.82436],[78.49096,19.80627],[78.3974,19.8376],[78.37938,19.87926],[78.32736,19.88555],[78.32891,19.91477],[78.31003,19.9167],[78.28754,19.84729],[78.36067,19.81887],[78.35861,19.75604],[78.29303,19.65616],[78.30985,19.46011],[78.19141,19.42903],[78.17441,19.23255],[78.04,19.24616],[78.04447,19.27371],[78.01631,19.27728],[77.95846,19.3424],[77.86422,19.30498],[77.90559,19.27112],[77.87487,19.25961],[77.8395,19.13868],[77.85066,19.08986],[77.80963,19.10413],[77.79762,19.05368],[77.7602,19.05676],[77.77427,18.97853],[77.80431,18.97464],[77.84534,18.90612],[77.88139,18.90807],[77.95349,18.82831],[77.8486,18.81288],[77.79436,18.70527],[77.73307,18.67844],[77.73994,18.55204],[77.59643,18.54846],[77.60227,18.47749],[77.5518,18.46804],[77.59214,18.44769],[77.53051,18.4389],[77.55008,18.40616],[77.52914,18.3454],[77.56982,18.31574],[77.5506,18.29211],[77.60021,18.27923],[77.57154,18.19217],[77.59935,18.08532],[77.56072,18.07438],[77.55231,18.03701],[77.61651,18.01154],[77.66741,17.96493],[77.63025,17.95644],[77.62038,17.90581],[77.59111,17.90548],[77.59634,17.87355],[77.50296,17.79241],[77.58244,17.74639],[77.45687,17.69897],[77.44417,17.58348],[77.50794,17.55648],[77.53309,17.5762],[77.59832,17.54355],[77.66175,17.52202],[77.69273,17.4892],[77.64385,17.47798],[77.61935,17.44581],[77.53,17.44007],[77.5227,17.35244],[77.45713,17.373],[77.44339,17.2941],[77.4597,17.28066],[77.38812,17.2259],[77.39301,17.19548],[77.36306,17.15563],[77.46181,17.11208],[77.50099,17.01657],[77.45532,16.92068],[77.47489,16.77956],[77.44279,16.78712],[77.427,16.72252],[77.47095,16.71216],[77.47198,16.587],[77.37636,16.48481],[77.32366,16.48942],[77.2344,16.47658]]]}},{"type":"Feature","properties":{"id":"IN-GA"},"geometry":{"type":"Polygon","coordinates":[[[73.38042,15.62832],[73.87481,14.75496],[74.16217,14.94976],[74.20543,14.92819],[74.29195,15.02869],[74.2741,15.09996],[74.31838,15.1805],[74.25075,15.25371],[74.33486,15.28352],[74.24903,15.49206],[74.25659,15.64551],[74.11806,15.6518],[74.00545,15.61096],[73.97163,15.62865],[73.9397,15.74037],[73.85593,15.80001],[73.81679,15.74682],[73.68598,15.72484],[73.38042,15.62832]]]}},{"type":"Feature","properties":{"id":"IN-AP"},"geometry":{"type":"Polygon","coordinates":[[[76.7637,14.96485],[76.86996,14.97082],[76.84661,14.80094],[76.79443,14.78484],[76.76902,14.64338],[76.80335,14.53257],[76.84764,14.51612],[76.86704,14.47523],[77.00077,14.48205],[76.88404,14.38846],[76.93862,14.24624],[77.04952,14.24774],[77.09569,14.20198],[77.13466,14.33923],[77.28658,14.33607],[77.28967,14.29016],[77.35885,14.27619],[77.3767,14.21113],[77.4282,14.19998],[77.37224,14.31877],[77.39576,14.34089],[77.48502,14.29332],[77.51249,14.16121],[77.40383,14.17236],[77.35937,14.11826],[77.41275,14.12459],[77.33791,14.03967],[77.43215,13.98237],[77.44417,13.94706],[77.41447,13.94423],[77.43284,13.91074],[77.4064,13.88907],[77.3549,13.91057],[77.35645,13.9917],[77.31954,14.03084],[77.14822,13.99953],[77.13415,14.03684],[77.0284,14.05432],[77.02651,14.16736],[76.95339,14.19233],[76.88112,14.14156],[76.94635,14.11976],[76.97467,14.06165],[76.93107,14.03334],[76.99819,13.99154],[76.99665,13.96255],[77.04574,13.9239],[76.97862,13.82941],[77.00265,13.72837],[77.03269,13.7829],[77.07046,13.75172],[77.17432,13.76339],[77.15526,13.84441],[77.13123,13.84508],[77.16127,13.87607],[77.17414,13.91257],[77.20453,13.85474],[77.24367,13.90274],[77.26221,13.85341],[77.43009,13.84091],[77.42065,13.80307],[77.45206,13.81057],[77.46837,13.75489],[77.45035,13.69619],[77.48022,13.67751],[77.49464,13.71603],[77.5233,13.68918],[77.53429,13.70686],[77.52433,13.76206],[77.55969,13.72337],[77.67471,13.7964],[77.67574,13.76356],[77.69908,13.76873],[77.71934,13.74021],[77.72209,13.7889],[77.78509,13.81907],[77.79092,13.84174],[77.83332,13.86841],[77.81375,13.93156],[77.88568,13.93556],[77.92568,13.9049],[77.96911,13.95439],[77.99365,13.94156],[77.95709,13.82974],[78.00378,13.87691],[78.02919,13.86074],[78.05253,13.88457],[78.12189,13.84874],[78.11742,13.8094],[78.12807,13.77656],[78.09845,13.74938],[78.12154,13.69185],[78.06876,13.69736],[78.0867,13.64682],[78.14609,13.65849],[78.20325,13.62363],[78.19072,13.57074],[78.22042,13.58659],[78.40461,13.59343],[78.37818,13.50748],[78.38058,13.40581],[78.36547,13.3634],[78.40616,13.34903],[78.40873,13.336],[78.37594,13.34385],[78.39792,13.31412],[78.43826,13.33116],[78.44598,13.3071],[78.47911,13.3208],[78.53001,13.26734],[78.54228,13.28647],[78.55653,13.28464],[78.56821,13.31103],[78.58752,13.27252],[78.57464,13.21228],[78.5464,13.19332],[78.57293,13.18905],[78.57988,13.17343],[78.51671,13.12754],[78.53482,13.1079],[78.50958,13.08733],[78.51551,13.06559],[78.46461,13.04118],[78.47122,12.97252],[78.43663,12.9798],[78.43328,12.95763],[78.41929,12.95688],[78.40787,12.93898],[78.4374,12.92685],[78.46693,12.90267],[78.46598,12.86167],[78.44444,12.91171],[78.42118,12.89849],[78.40238,12.9133],[78.38891,12.9046],[78.35878,12.93739],[78.315,12.85699],[78.2575,12.86201],[78.23209,12.76208],[78.20291,12.68991],[78.46401,12.61454],[78.50795,12.73345],[78.57181,12.76626],[78.64562,12.99602],[78.83892,13.07964],[79.1831,13.01894],[79.22189,13.16072],[79.32111,13.11559],[79.36351,13.13097],[79.45226,13.22256],[79.3848,13.30727],[79.45896,13.3492],[79.53895,13.3355],[79.53741,13.25882],[79.66495,13.27937],[79.70924,13.21989],[79.7664,13.20618],[79.79953,13.23409],[79.75215,13.27804],[79.92982,13.335],[79.96519,13.39412],[79.93961,13.42051],[79.97154,13.4377],[80.016,13.4973],[80.05222,13.50231],[79.99832,13.53436],[80.06767,13.54554],[80.07574,13.4958],[80.22714,13.48862],[80.26782,13.56156],[80.32791,13.44405],[80.56272,13.46443],[82.60962,16.71347],[82.26802,16.70509],[82.18674,16.72975],[82.18305,16.73674],[82.20562,16.73526],[82.20897,16.74931],[82.22853,16.76123],[82.21713,16.73353],[82.24262,16.72219],[82.29154,16.72531],[82.30562,16.748],[82.31403,16.72811],[82.62817,16.73846],[84.98748,18.92967],[84.76467,19.07785],[84.71042,19.16008],[84.66922,19.16787],[84.66579,19.13008],[84.59318,19.13122],[84.62888,19.08418],[84.65789,19.09699],[84.66407,19.06909],[84.62099,19.0626],[84.58837,19.02073],[84.57618,19.08239],[84.45842,18.99866],[84.42203,19.02057],[84.42804,18.9157],[84.37774,18.9019],[84.32212,18.7911],[84.2241,18.79776],[84.1436,18.77842],[84.08369,18.75144],[84.04695,18.80573],[83.90825,18.82685],[83.89657,18.8145],[83.79152,19.03453],[83.74156,18.96279],[83.70946,19.00418],[83.72886,19.0222],[83.65041,19.13235],[83.47875,19.08726],[83.46193,18.95922],[83.36013,19.02041],[83.31104,18.99671],[83.33395,18.92788],[83.36116,18.94436],[83.41094,18.84358],[83.21336,18.74266],[83.13285,18.78151],[82.99467,18.61273],[83.09406,18.54862],[83.02659,18.45583],[83.075,18.40339],[82.93682,18.3568],[82.89751,18.42277],[82.81202,18.45306],[82.78352,18.43067],[82.78266,18.34246],[82.65151,18.29635],[82.63366,18.23522],[82.59143,18.27744],[82.60173,18.37977],[82.49753,18.55757],[82.36879,18.42261],[82.34596,18.32617],[82.38578,18.30564],[82.30811,18.19103],[82.34613,18.1686],[82.34939,18.06378],[82.29343,18.06835],[82.25927,17.98575],[82.08057,18.07504],[81.81055,17.95146],[81.66154,17.8443],[81.454,17.83613],[81.39135,17.79772],[81.08459,17.79315],[81.06948,17.69668],[80.96855,17.77811],[80.89387,17.73822],[80.88632,17.68442],[80.91344,17.68],[80.89267,17.63502],[80.83774,17.60933],[81.33178,17.40568],[81.2947,17.32016],[81.19308,17.32442],[81.18055,17.30639],[81.16939,17.23164],[80.99172,17.19606],[80.91293,17.21819],[80.92331,17.15645],[80.8798,17.16457],[80.86864,17.04217],[80.72462,17.07844],[80.64943,17.06958],[80.66865,17.10207],[80.58523,17.14587],[80.50695,17.11913],[80.50643,17.07335],[80.46747,17.02478],[80.39503,17.08353],[80.36584,16.9793],[80.49201,16.93251],[80.53476,16.9619],[80.60256,16.91838],[80.56514,16.85449],[80.60153,16.76953],[80.46661,16.79583],[80.46369,16.81965],[80.42524,16.84923],[80.3322,16.87732],[80.31537,16.94876],[80.28379,16.99654],[80.19556,17.0251],[80.0596,16.97093],[80.0414,16.92134],[79.99402,16.89408],[80.0735,16.81242],[79.9978,16.6528],[79.913,16.62961],[79.86305,16.7069],[79.81533,16.6921],[79.77842,16.73271],[79.71353,16.69079],[79.6737,16.69374],[79.63611,16.66151],[79.44471,16.61793],[79.42514,16.58289],[79.21897,16.56989],[79.22035,16.2239],[78.68408,16.04581],[78.41766,16.08012],[78.25561,15.9841],[78.17459,15.86326],[78.08412,15.83602],[78.03159,15.90652],[78.00807,15.86359],[77.89049,15.90306],[77.83882,15.87829],[77.65823,15.88671],[77.61119,15.91379],[77.51197,15.92864],[77.44571,15.94366],[77.23766,15.96496],[77.08351,15.92419],[77.02257,15.84675],[77.08024,15.71675],[77.12934,15.63675],[77.03561,15.63626],[77.02583,15.50463],[76.96935,15.50231],[77.01295,15.4507],[77.04471,15.35554],[77.16333,15.26911],[77.13998,15.23996],[77.17037,15.17619],[77.11921,15.03913],[77.06703,15.00348],[77.0272,15.03432],[76.95425,15.02686],[76.80044,15.09681],[76.7637,14.96485]]]}},{"type":"Feature","properties":{"id":"IN-TN"},"geometry":{"type":"Polygon","coordinates":[[[76.24966,11.51072],[76.54775,11.35071],[76.43531,11.19456],[76.73229,11.22504],[76.74817,11.13511],[76.74156,11.04887],[76.65779,10.92703],[76.84181,10.86668],[76.8497,10.8188],[76.91614,10.80312],[76.86876,10.68658],[76.88438,10.63901],[76.81417,10.6326],[76.8758,10.27505],[76.96403,10.221],[77.21465,10.36423],[77.27645,10.13382],[77.20298,10.11759],[77.25997,10.02971],[77.24693,9.80447],[77.16865,9.61632],[77.42065,9.51543],[77.15045,9.01496],[77.26135,8.843],[77.17655,8.7385],[77.28675,8.54401],[77.20024,8.50734],[77.22933,8.45072],[77.16093,8.37524],[77.17217,8.33346],[77.13114,8.32641],[76.77589,8.02795],[77.66782,7.85769],[79.37385,8.98767],[79.45362,9.159],[79.42124,9.80115],[80.42924,10.17542],[80.05943,10.8306],[79.8167,10.82925],[79.8167,10.88556],[79.76417,10.89197],[79.71902,10.94152],[79.74803,11.0027],[79.7985,10.98955],[80.05393,10.99781],[79.99831,11.6993],[79.68109,11.80048],[79.59525,11.86735],[79.64229,12.06752],[80.10406,11.97753],[80.56272,13.46443],[80.32791,13.44405],[80.26782,13.56156],[80.22714,13.48862],[80.07574,13.4958],[80.06767,13.54554],[79.99832,13.53436],[80.05222,13.50231],[80.016,13.4973],[79.97154,13.4377],[79.93961,13.42051],[79.96519,13.39412],[79.92982,13.335],[79.75215,13.27804],[79.79953,13.23409],[79.7664,13.20618],[79.70924,13.21989],[79.66495,13.27937],[79.53741,13.25882],[79.53895,13.3355],[79.45896,13.3492],[79.3848,13.30727],[79.45226,13.22256],[79.36351,13.13097],[79.32111,13.11559],[79.22189,13.16072],[79.1831,13.01894],[78.83892,13.07964],[78.64562,12.99602],[78.57181,12.76626],[78.50795,12.73345],[78.46401,12.61454],[78.20291,12.68991],[78.23209,12.76208],[78.13236,12.78183],[78.08627,12.83356],[78.03322,12.85406],[78.00215,12.80359],[77.94834,12.85866],[77.94044,12.8287],[77.91666,12.82753],[77.93598,12.87289],[77.92868,12.89029],[77.88783,12.8713],[77.8365,12.86628],[77.78998,12.84092],[77.81032,12.82987],[77.80569,12.80041],[77.78011,12.7658],[77.79543,12.75668],[77.79298,12.74521],[77.7662,12.73906],[77.7608,12.72759],[77.77522,12.72415],[77.76329,12.6956],[77.73994,12.69962],[77.74114,12.67065],[77.71196,12.6817],[77.71265,12.66395],[77.67943,12.65625],[77.67514,12.68363],[77.60089,12.66579],[77.58853,12.51803],[77.63523,12.49088],[77.61806,12.36649],[77.48931,12.2766],[77.45738,12.20681],[77.725,12.17963],[77.77925,12.112],[77.72724,12.05409],[77.67917,11.94898],[77.49704,11.9426],[77.46665,11.84887],[77.42906,11.76199],[77.25465,11.81241],[77.12059,11.71863],[76.98772,11.81291],[76.97193,11.77628],[76.91013,11.79308],[76.84249,11.66938],[76.85691,11.60398],[76.61281,11.60717],[76.539,11.69123],[76.42948,11.66568],[76.38072,11.60204],[76.31026,11.57766],[76.25507,11.59271],[76.24966,11.51072]]]}},{"type":"Feature","properties":{"id":"IN-JK"},"geometry":{"type":"Polygon","coordinates":[[[73.74862,34.34183],[73.8475,34.32935],[73.90517,34.35317],[74.01077,34.21677],[73.90678,34.10686],[73.89636,34.05635],[73.94373,34.01617],[74.07875,34.03523],[74.21625,34.02605],[74.26637,33.98635],[74.2783,33.89535],[74.14001,33.83002],[74.05763,33.82443],[74.00891,33.75437],[73.96232,33.72298],[73.98968,33.66155],[73.98296,33.64338],[74.03295,33.57662],[74.1372,33.56092],[74.18354,33.47626],[74.17983,33.3679],[74.1275,33.30571],[74.00794,33.25462],[74.01309,33.21556],[74.15374,33.13477],[74.17548,33.075],[74.31854,33.02891],[74.33976,32.95682],[74.30783,32.92858],[74.41143,32.89904],[74.44687,32.79506],[74.46335,32.77695],[74.54137,32.74815],[74.6369,32.75407],[74.64008,32.82089],[74.70952,32.84202],[74.65227,32.69724],[74.69758,32.66351],[74.64068,32.61118],[74.65251,32.56416],[74.67175,32.56844],[74.69098,32.52995],[74.68351,32.49209],[74.81243,32.48116],[74.82101,32.49796],[74.99181,32.45024],[75.02683,32.49745],[75.10906,32.47428],[75.13532,32.41329],[75.19334,32.42402],[75.1954,32.40445],[75.28784,32.37322],[75.33265,32.32703],[75.34475,32.35103],[75.43032,32.31593],[75.46285,32.32753],[75.49289,32.34733],[75.48654,32.31927],[75.50954,32.28256],[75.53512,32.27711],[75.54027,32.33762],[75.57855,32.37474],[75.6867,32.3917],[75.74129,32.43126],[75.72875,32.4527],[75.78506,32.47095],[75.83845,32.50831],[75.93132,32.64428],[75.77888,32.9355],[75.95329,32.88362],[76.31584,33.1341],[76.76902,33.26337],[77.32795,32.82305],[77.66784,32.97007],[77.88345,32.7942],[77.97477,32.58905],[78.32565,32.75263],[78.38897,32.53938],[78.73916,32.69438],[78.7831,32.46873],[78.96713,32.33655],[78.99322,32.37948],[79.0979,32.38051],[79.13174,32.47766],[79.26768,32.53277],[79.46562,32.69668],[79.14016,33.02545],[79.15252,33.17156],[78.73636,33.56521],[78.67599,33.66445],[78.77349,33.73871],[78.73367,34.01121],[78.65657,34.03195],[78.66225,34.08858],[78.91769,34.15452],[78.99802,34.3027],[79.05364,34.32482],[78.74465,34.45174],[78.56475,34.50835],[78.54964,34.57283],[78.27781,34.61484],[78.18435,34.7998],[78.22692,34.88771],[78.00033,35.23954],[78.03466,35.3785],[78.11664,35.48022],[77.80532,35.52058],[77.70232,35.46244],[77.44277,35.46132],[76.96624,35.5932],[76.84539,35.67356],[76.77323,35.66062],[76.75475,35.52617],[76.85088,35.39754],[76.93465,35.39866],[77.11796,35.05419],[76.93725,34.99569],[76.89966,34.92999],[76.77675,34.94702],[76.74377,34.84039],[76.67409,34.74598],[76.47186,34.78965],[76.14074,34.63462],[76.04614,34.67566],[75.75438,34.51827],[75.38009,34.55021],[75.01479,34.64629],[74.89194,34.66628],[74.6663,34.703],[74.58137,34.76389],[74.31667,34.78673],[74.12149,34.67528],[73.9706,34.68516],[73.93401,34.63386],[73.95584,34.56269],[73.90125,34.51843],[73.88732,34.48911],[73.75731,34.38226],[73.74862,34.34183]]]}},{"type":"Feature","properties":{"id":"IN-UT"},"geometry":{"type":"Polygon","coordinates":[[[77.57549,30.39804],[77.95022,30.23192],[77.82646,30.08513],[77.80431,30.09642],[77.76886,30.04576],[77.78646,30.02986],[77.76148,30.01084],[77.73324,29.95805],[77.74457,29.91804],[77.71556,29.87354],[77.8177,29.66344],[77.87693,29.69178],[77.92619,29.71444],[77.96396,29.66702],[77.97632,29.61614],[77.92997,29.59465],[78.01923,29.5836],[78.20342,29.73337],[78.36101,29.78225],[78.50503,29.72353],[78.55155,29.58958],[78.79446,29.4659],[78.94552,29.43436],[78.84836,29.34387],[78.82759,29.36931],[78.7984,29.32082],[78.76613,29.33534],[78.7251,29.30556],[78.81617,29.2333],[78.86389,29.2396],[78.91282,29.14578],[78.91376,29.11909],[78.95633,29.12082],[78.98345,29.14016],[79.03289,29.11002],[79.03547,29.16415],[79.11581,29.12712],[79.14739,29.12757],[79.14396,29.05917],[79.16936,29.04731],[79.16645,29.01399],[79.21245,29.02435],[79.30789,28.96849],[79.37038,28.98682],[79.38703,28.94852],[79.41415,28.94972],[79.42789,28.86151],[79.56298,28.89398],[79.58787,28.85023],[79.65362,28.86963],[79.70495,28.84738],[79.8076,28.88691],[79.84176,28.87444],[79.82597,28.79353],[79.86854,28.83926],[79.91901,28.81188],[79.86803,28.80888],[79.96484,28.72958],[80.02304,28.74237],[80.06466,28.83813],[80.05743,28.91479],[80.10011,28.98546],[80.11745,28.98156],[80.13547,29.06007],[80.12895,29.06217],[80.13187,29.09232],[80.1844,29.13746],[80.23049,29.11666],[80.26843,29.14061],[80.24285,29.219],[80.29336,29.19604],[80.30611,29.28946],[80.31718,29.31237],[80.28113,29.34417],[80.24272,29.44389],[80.3019,29.45193],[80.28662,29.47644],[80.34464,29.51245],[80.35726,29.53201],[80.34147,29.553],[80.37932,29.56091],[80.37876,29.57129],[80.38932,29.57241],[80.40803,29.59741],[80.40824,29.61805],[80.42447,29.63106],[80.40584,29.65952],[80.38975,29.66504],[80.38155,29.68693],[80.37825,29.70154],[80.36636,29.72406],[80.36477,29.74086],[80.36769,29.75014],[80.3858,29.75062],[80.38576,29.75893],[80.39713,29.75878],[80.41009,29.79417],[80.43026,29.7989],[80.43485,29.80557],[80.44494,29.79905],[80.46159,29.80218],[80.49283,29.79514],[80.49875,29.81227],[80.50699,29.82269],[80.52291,29.8282],[80.53047,29.83781],[80.53506,29.83848],[80.53862,29.84734],[80.54536,29.84712],[80.54802,29.85021],[80.55287,29.85054],[80.55446,29.86107],[80.56247,29.86661],[80.55948,29.86811],[80.57218,29.89453],[80.57441,29.92161],[80.60128,29.9582],[80.62917,29.96512],[80.65355,29.95471],[80.67389,29.95657],[80.72161,30.00013],[80.74384,29.99924],[80.78281,30.06731],[80.87705,30.12798],[80.8925,30.22273],[80.92906,30.17644],[81.03953,30.20059],[80.83343,30.32023],[80.54504,30.44936],[80.20721,30.58541],[79.93255,30.88288],[79.59884,30.93943],[79.22805,31.34963],[79.14016,31.43403],[79.01931,31.42817],[78.77898,31.31209],[78.99581,31.10821],[78.32239,31.27855],[78.03915,31.15934],[77.95675,31.15949],[77.81324,31.05778],[77.8298,31.03072],[77.79573,31.02234],[77.82491,31.00865],[77.8147,30.96333],[77.83787,30.95236],[77.75333,30.96966],[77.76088,30.91916],[77.81204,30.92564],[77.80517,30.85036],[77.75453,30.87902],[77.7444,30.85021],[77.76131,30.82567],[77.74045,30.81071],[77.7305,30.7911],[77.7038,30.74412],[77.72097,30.74227],[77.75238,30.71188],[77.74543,30.68191],[77.78646,30.66065],[77.79333,30.61501],[77.7408,30.60127],[77.78174,30.55494],[77.82371,30.55161],[77.81633,30.50637],[77.76723,30.49468],[77.65583,30.4355],[77.58132,30.42366],[77.57549,30.39804]]]}},{"type":"Feature","properties":{"id":"IN-MH"},"geometry":{"type":"Polygon","coordinates":[[[72.4768,20.16425],[73.38042,15.62832],[73.68598,15.72484],[73.81679,15.74682],[73.85593,15.80001],[73.9397,15.74037],[73.97163,15.62865],[74.00545,15.61096],[74.11806,15.6518],[74.13848,15.72583],[74.3534,15.76681],[74.36765,15.82561],[74.39014,15.84411],[74.34104,15.85715],[74.36353,15.87367],[74.40696,15.84906],[74.38774,15.90339],[74.43151,15.95258],[74.46327,16.04416],[74.4328,16.05678],[74.35761,16.04779],[74.42344,16.10931],[74.48249,16.09496],[74.50687,16.22983],[74.36233,16.29344],[74.3316,16.26362],[74.33332,16.32145],[74.32353,16.39623],[74.36147,16.36988],[74.30414,16.46999],[74.24938,16.48465],[74.2674,16.54867],[74.31941,16.52793],[74.31255,16.56249],[74.38224,16.52991],[74.39117,16.57664],[74.4667,16.60757],[74.45056,16.65115],[74.54326,16.63339],[74.56472,16.55969],[74.6284,16.57762],[74.68763,16.61365],[74.65209,16.63619],[74.70136,16.7189],[74.91731,16.78777],[74.90427,16.86303],[74.97293,16.89523],[74.91044,16.93908],[74.96932,16.95172],[75.03061,16.93398],[75.07987,16.94893],[75.18064,16.83789],[75.27454,16.86286],[75.28432,16.9555],[75.46251,16.98735],[75.51023,16.95303],[75.58525,17.01558],[75.6788,16.9596],[75.64584,17.04496],[75.68138,17.07516],[75.63503,17.17392],[75.6546,17.2523],[75.57563,17.35162],[75.63262,17.47643],[75.67159,17.45514],[75.69425,17.40994],[75.76343,17.40633],[75.80995,17.36194],[75.82609,17.42976],[75.892,17.36816],[75.92428,17.32835],[76.05525,17.36178],[76.07276,17.33081],[76.11928,17.37242],[76.19207,17.29705],[76.16992,17.34916],[76.24357,17.38242],[76.27962,17.33441],[76.32631,17.34801],[76.3742,17.30836],[76.41351,17.36259],[76.36562,17.38078],[76.37042,17.435],[76.32579,17.45055],[76.37557,17.49378],[76.34485,17.49526],[76.33386,17.59035],[76.41111,17.60622],[76.49076,17.71418],[76.52801,17.72252],[76.52166,17.75833],[76.56612,17.76389],[76.57522,17.69341],[76.61573,17.77304],[76.68937,17.67739],[76.70207,17.72808],[76.79391,17.82779],[76.73486,17.88318],[76.76868,17.90001],[76.78842,17.87518],[76.88129,17.89413],[76.92043,17.91716],[76.91305,17.96779],[76.91596,18.03489],[76.95356,18.03921],[76.9454,18.08303],[76.97047,18.10041],[76.91888,18.12007],[76.95656,18.18858],[76.98858,18.19046],[77.00523,18.15792],[77.04771,18.18018],[77.05587,18.15474],[77.11921,18.15678],[77.09664,18.19641],[77.13303,18.20701],[77.17706,18.28453],[77.20822,18.2785],[77.22736,18.36438],[77.25148,18.37448],[77.24083,18.40681],[77.31345,18.44264],[77.32469,18.45567],[77.36074,18.44997],[77.37138,18.40095],[77.41052,18.39541],[77.35593,18.30824],[77.42949,18.30979],[77.4761,18.25103],[77.5142,18.31004],[77.5506,18.29211],[77.56982,18.31574],[77.52914,18.3454],[77.55008,18.40616],[77.53051,18.4389],[77.59214,18.44769],[77.5518,18.46804],[77.60227,18.47749],[77.59643,18.54846],[77.73994,18.55204],[77.73307,18.67844],[77.79436,18.70527],[77.8486,18.81288],[77.95349,18.82831],[77.88139,18.90807],[77.84534,18.90612],[77.80431,18.97464],[77.77427,18.97853],[77.7602,19.05676],[77.79762,19.05368],[77.80963,19.10413],[77.85066,19.08986],[77.8395,19.13868],[77.87487,19.25961],[77.90559,19.27112],[77.86422,19.30498],[77.95846,19.3424],[78.01631,19.27728],[78.04447,19.27371],[78.04,19.24616],[78.17441,19.23255],[78.19141,19.42903],[78.30985,19.46011],[78.29303,19.65616],[78.35861,19.75604],[78.36067,19.81887],[78.28754,19.84729],[78.31003,19.9167],[78.32891,19.91477],[78.32736,19.88555],[78.37938,19.87926],[78.3974,19.8376],[78.49096,19.80627],[78.54589,19.82436],[78.69918,19.78883],[78.84355,19.75474],[78.85866,19.65665],[78.9462,19.66974],[78.97178,19.58065],[78.93814,19.54814],[78.97985,19.56124],[79.06534,19.54781],[79.20833,19.4538],[79.24163,19.53876],[79.2418,19.62124],[79.49363,19.4962],[79.52762,19.54911],[79.59508,19.50802],[79.63216,19.57661],[79.79782,19.60151],[79.86236,19.51416],[79.92931,19.49442],[79.98012,19.39471],[79.92553,19.20937],[79.94407,19.16722],[79.85858,19.10883],[79.87232,19.03842],[79.93446,19.04524],[79.96261,18.86145],[79.9094,18.82474],[80.11196,18.6869],[80.27503,18.72104],[80.24826,18.75616],[80.37134,18.82376],[80.26473,18.95499],[80.38764,19.2071],[80.56514,19.40961],[80.6084,19.31308],[80.72341,19.27355],[80.8937,19.47306],[80.49854,19.89637],[80.40927,19.94043],[80.5363,19.95495],[80.56325,20.11106],[80.38284,20.23256],[80.62351,20.32724],[80.62711,20.60129],[80.46764,20.61221],[80.5799,20.6694],[80.54969,20.92953],[80.46747,20.93017],[80.44412,21.09923],[80.47416,21.17368],[80.66986,21.25242],[80.65784,21.32967],[80.60789,21.32263],[80.52566,21.39298],[80.45768,21.40129],[80.44052,21.37444],[80.40747,21.37619],[80.39597,21.4084],[80.41803,21.44428],[80.37546,21.52342],[80.32344,21.57587],[80.20946,21.6354],[80.13427,21.61115],[80.06732,21.55528],[79.93154,21.5556],[79.91489,21.52207],[79.85549,21.53468],[79.79764,21.58258],[79.74031,21.60269],[79.53638,21.5366],[79.49981,21.66588],[79.42274,21.69539],[79.40076,21.67561],[79.28935,21.69108],[79.14585,21.62583],[78.91136,21.59295],[78.9347,21.49268],[78.69832,21.4823],[78.52787,21.52414],[78.43654,21.50099],[78.42178,21.60077],[78.27793,21.58386],[78.0079,21.41455],[77.49343,21.37763],[77.41104,21.54315],[77.6081,21.53165],[77.54596,21.70017],[77.49893,21.76332],[77.28435,21.75965],[77.2586,21.71437],[77.07733,21.72474],[76.90429,21.60349],[76.78997,21.59407],[76.77246,21.50642],[76.79151,21.47958],[76.74473,21.4398],[76.73812,21.4112],[76.61933,21.33351],[76.66774,21.27897],[76.40338,21.07713],[76.17301,21.08386],[76.1107,21.16216],[76.17937,21.19833],[76.14151,21.23154],[76.15653,21.2625],[76.08821,21.3666],[75.90539,21.3901],[75.40912,21.38754],[75.22304,21.40928],[75.11455,21.45306],[75.06134,21.56486],[74.89654,21.62934],[74.75475,21.61689],[74.66686,21.65152],[74.58377,21.66476],[74.5479,21.71692],[74.50859,21.72091],[74.52609,21.90769],[74.43717,22.03282],[74.2353,21.92807],[74.14947,21.95323],[73.79602,21.82803],[73.89284,21.65966],[73.78864,21.63365],[73.84923,21.49907],[73.97747,21.52103],[73.97489,21.54147],[74.03205,21.54562],[74.11874,21.55648],[74.12904,21.55129],[74.16689,21.56917],[74.2317,21.55352],[74.25916,21.54075],[74.32628,21.55751],[74.32302,21.50274],[74.24886,21.4676],[74.22346,21.48038],[74.15857,21.46824],[74.10037,21.44875],[74.06158,21.46161],[74.04622,21.44779],[74.04922,21.42023],[74.01712,21.42047],[73.93541,21.29065],[73.82915,21.26746],[73.81164,21.21658],[73.82228,21.17624],[73.7368,21.1676],[73.73319,21.14295],[73.57578,21.16024],[73.66916,21.10788],[73.75602,21.09218],[73.89678,20.96127],[73.9179,20.90981],[73.93901,20.73588],[73.88545,20.72753],[73.7622,20.57493],[73.69354,20.57783],[73.65663,20.56095],[73.58814,20.64563],[73.55432,20.64756],[73.48548,20.66153],[73.46437,20.73652],[73.39399,20.64868],[73.48274,20.54022],[73.40137,20.39258],[73.42128,20.20034],[73.28919,20.20405],[73.27889,20.15087],[73.21615,20.11961],[73.18971,20.04948],[73.06783,20.09946],[73.04088,20.0727],[72.96775,20.1354],[72.97067,20.21516],[72.8754,20.22869],[72.78665,20.1238],[72.4768,20.16425]]]}},{"type":"Feature","properties":{"id":"IN-LD"},"geometry":{"type":"Polygon","coordinates":[[[71.37481,12.9453],[73.04319,7.79398],[73.97643,10.90343],[71.37481,12.9453]]]}},{"type":"Feature","properties":{"id":"IN-AN"},"geometry":{"type":"Polygon","coordinates":[[[91.77977,11.01669],[93.62843,6.54546],[94.20774,6.67551],[94.64499,13.56452],[92.61282,13.95915],[91.77977,11.01669]]]}},{"type":"Feature","properties":{"id":"IN-CT"},"geometry":{"type":"Polygon","coordinates":[[[80.24826,18.75616],[80.27503,18.72104],[80.34782,18.59158],[80.51055,18.62802],[80.87345,18.2238],[81.08459,17.79315],[81.39135,17.79772],[81.40165,17.88727],[81.44988,17.89707],[81.52679,18.16248],[81.50722,18.19217],[81.54275,18.26864],[81.61468,18.31052],[81.65811,18.34703],[81.75115,18.35224],[81.76832,18.4187],[81.85861,18.49181],[81.8605,18.51241],[81.9635,18.59093],[81.89466,18.64299],[82.16468,18.79094],[82.17945,18.90401],[82.25429,18.92041],[82.16297,19.20078],[82.18717,19.42661],[82.03937,19.51772],[82.06375,19.78221],[81.98667,19.8032],[81.96264,19.86231],[81.85295,19.92042],[81.84608,19.96124],[81.87458,20.05044],[81.96229,20.09204],[82.02787,20.01835],[82.07387,20.05689],[82.26974,19.96495],[82.34184,19.83066],[82.43591,19.91719],[82.60311,19.8665],[82.56362,19.76702],[82.72258,19.85036],[82.71503,19.9969],[82.5959,19.99238],[82.39299,20.05915],[82.42973,20.30663],[82.34424,20.56658],[82.34596,20.8864],[82.39351,20.87132],[82.45788,20.82672],[82.48861,20.91045],[82.54388,20.93755],[82.62577,21.0374],[82.61117,21.07472],[82.64293,21.10019],[82.6407,21.15847],[82.75863,21.16664],[82.79039,21.14343],[82.82936,21.16824],[82.94548,21.1636],[82.95948,21.19545],[83.04359,21.12389],[83.19671,21.1415],[83.22366,21.2681],[83.27121,21.27977],[83.2719,21.38243],[83.39755,21.34038],[83.39584,21.40385],[83.32803,21.4815],[83.36889,21.55672],[83.36614,21.5991],[83.44347,21.65487],[83.44571,21.62495],[83.45764,21.60883],[83.49154,21.63955],[83.41695,21.68247],[83.43137,21.70009],[83.46073,21.69443],[83.48819,21.74738],[83.47163,21.78803],[83.49403,21.81736],[83.54021,21.79807],[83.53763,21.84062],[83.57995,21.8325],[83.5917,21.84907],[83.53506,22.04395],[83.5996,22.06209],[83.56183,22.10456],[83.65436,22.22967],[84.00764,22.37674],[84.04746,22.46973],[83.97743,22.51049],[83.99425,22.53602],[84.39834,22.92045],[84.3695,22.97704],[84.14909,22.97546],[84.02961,23.16592],[84.06978,23.33059],[83.97039,23.37424],[84.02652,23.63068],[83.78929,23.58853],[83.72165,23.68508],[83.70758,23.81958],[83.56853,23.8772],[83.4185,24.08596],[83.32134,24.10131],[83.15963,23.90467],[82.95226,23.87642],[82.80876,23.96492],[82.65666,23.90216],[82.66593,23.86511],[82.49599,23.7844],[81.90685,23.85569],[81.79218,23.80764],[81.72214,23.83999],[81.66274,23.92821],[81.59614,23.88834],[81.69467,23.71605],[81.57091,23.58113],[81.62567,23.48875],[81.75338,23.56619],[81.94942,23.49347],[82.06066,23.37991],[82.20382,23.3164],[82.13687,23.16608],[82.16245,23.15077],[82.1149,23.10247],[81.93886,23.07823],[81.93929,22.95776],[81.75991,22.85086],[81.77192,22.66043],[81.39358,22.44498],[81.35684,22.52175],[81.11171,22.44339],[80.98571,22.03441],[80.90606,22.13112],[80.81851,21.75009],[80.73921,21.74212],[80.7344,21.46648],[80.65784,21.32967],[80.66986,21.25242],[80.47416,21.17368],[80.44412,21.09923],[80.46747,20.93017],[80.54969,20.92953],[80.5799,20.6694],[80.46764,20.61221],[80.62711,20.60129],[80.62351,20.32724],[80.38284,20.23256],[80.56325,20.11106],[80.5363,19.95495],[80.40927,19.94043],[80.49854,19.89637],[80.8937,19.47306],[80.72341,19.27355],[80.6084,19.31308],[80.56514,19.40961],[80.38764,19.2071],[80.26473,18.95499],[80.37134,18.82376],[80.24826,18.75616]]]}},{"type":"Feature","properties":{"id":"IN-OR"},"geometry":{"type":"Polygon","coordinates":[[[81.39135,17.79772],[81.454,17.83613],[81.66154,17.8443],[81.81055,17.95146],[82.08057,18.07504],[82.25927,17.98575],[82.29343,18.06835],[82.34939,18.06378],[82.34613,18.1686],[82.30811,18.19103],[82.38578,18.30564],[82.34596,18.32617],[82.36879,18.42261],[82.49753,18.55757],[82.60173,18.37977],[82.59143,18.27744],[82.63366,18.23522],[82.65151,18.29635],[82.78266,18.34246],[82.78352,18.43067],[82.81202,18.45306],[82.89751,18.42277],[82.93682,18.3568],[83.075,18.40339],[83.02659,18.45583],[83.09406,18.54862],[82.99467,18.61273],[83.13285,18.78151],[83.21336,18.74266],[83.41094,18.84358],[83.36116,18.94436],[83.33395,18.92788],[83.31104,18.99671],[83.36013,19.02041],[83.46193,18.95922],[83.47875,19.08726],[83.65041,19.13235],[83.72886,19.0222],[83.70946,19.00418],[83.74156,18.96279],[83.79152,19.03453],[83.89657,18.8145],[83.90825,18.82685],[84.04695,18.80573],[84.08369,18.75144],[84.1436,18.77842],[84.2241,18.79776],[84.32212,18.7911],[84.37774,18.9019],[84.42804,18.9157],[84.42203,19.02057],[84.45842,18.99866],[84.57618,19.08239],[84.58837,19.02073],[84.62099,19.0626],[84.66407,19.06909],[84.65789,19.09699],[84.62888,19.08418],[84.59318,19.13122],[84.66579,19.13008],[84.66922,19.16787],[84.71042,19.16008],[84.76467,19.07785],[84.98748,18.92967],[87.69835,20.81775],[87.44447,21.76539],[87.27436,21.80668],[87.21771,21.97934],[87.04364,21.84429],[86.99523,22.06559],[86.71852,22.14845],[86.7247,22.21569],[86.49295,22.3469],[86.41382,22.30831],[86.2825,22.44799],[86.11083,22.49622],[86.04663,22.5682],[85.96458,22.46973],[86.01848,22.41237],[86.03839,22.30752],[85.91686,21.97616],[85.78639,21.98698],[85.82107,22.09104],[85.66966,22.07259],[85.5828,22.08373],[85.40651,22.16149],[85.28549,22.09566],[85.23965,22.01117],[85.02696,22.12683],[85.07228,22.26987],[85.11408,22.29608],[85.11846,22.31855],[85.094,22.3207],[85.06782,22.48623],[84.90509,22.42562],[84.8117,22.44958],[84.7063,22.41896],[84.57893,22.43292],[84.31835,22.33705],[84.17346,22.39769],[84.13295,22.4748],[83.99425,22.53602],[83.97743,22.51049],[84.04746,22.46973],[84.00764,22.37674],[83.65436,22.22967],[83.56183,22.10456],[83.5996,22.06209],[83.53506,22.04395],[83.5917,21.84907],[83.57995,21.8325],[83.53763,21.84062],[83.54021,21.79807],[83.49403,21.81736],[83.47163,21.78803],[83.48819,21.74738],[83.46073,21.69443],[83.43137,21.70009],[83.41695,21.68247],[83.49154,21.63955],[83.45764,21.60883],[83.44571,21.62495],[83.44347,21.65487],[83.36614,21.5991],[83.36889,21.55672],[83.32803,21.4815],[83.39584,21.40385],[83.39755,21.34038],[83.2719,21.38243],[83.27121,21.27977],[83.22366,21.2681],[83.19671,21.1415],[83.04359,21.12389],[82.95948,21.19545],[82.94548,21.1636],[82.82936,21.16824],[82.79039,21.14343],[82.75863,21.16664],[82.6407,21.15847],[82.64293,21.10019],[82.61117,21.07472],[82.62577,21.0374],[82.54388,20.93755],[82.48861,20.91045],[82.45788,20.82672],[82.39351,20.87132],[82.34596,20.8864],[82.34424,20.56658],[82.42973,20.30663],[82.39299,20.05915],[82.5959,19.99238],[82.71503,19.9969],[82.72258,19.85036],[82.56362,19.76702],[82.60311,19.8665],[82.43591,19.91719],[82.34184,19.83066],[82.26974,19.96495],[82.07387,20.05689],[82.02787,20.01835],[81.96229,20.09204],[81.87458,20.05044],[81.84608,19.96124],[81.85295,19.92042],[81.96264,19.86231],[81.98667,19.8032],[82.06375,19.78221],[82.03937,19.51772],[82.18717,19.42661],[82.16297,19.20078],[82.25429,18.92041],[82.17945,18.90401],[82.16468,18.79094],[81.89466,18.64299],[81.9635,18.59093],[81.8605,18.51241],[81.85861,18.49181],[81.76832,18.4187],[81.75115,18.35224],[81.65811,18.34703],[81.61468,18.31052],[81.54275,18.26864],[81.50722,18.19217],[81.52679,18.16248],[81.44988,17.89707],[81.40165,17.88727],[81.39135,17.79772]]]}},{"type":"Feature","properties":{"id":"IN-WB"},"geometry":{"type":"Polygon","coordinates":[[[85.81901,23.26941],[85.89403,23.15519],[86.04423,23.14572],[86.21074,22.99569],[86.2976,23.01228],[86.39871,22.9821],[86.4212,22.99663],[86.55286,22.98605],[86.54874,22.96819],[86.43648,22.92535],[86.45828,22.89293],[86.41914,22.78979],[86.63509,22.66471],[86.65311,22.58453],[86.76538,22.57645],[86.80847,22.47861],[86.74804,22.47972],[86.76349,22.43213],[86.84967,22.40674],[86.83456,22.33102],[86.89189,22.27225],[86.7247,22.21569],[86.71852,22.14845],[86.99523,22.06559],[87.04364,21.84429],[87.21771,21.97934],[87.27436,21.80668],[87.44447,21.76539],[87.69835,20.81775],[89.13606,21.42955],[89.13927,21.60785],[89.03553,21.77397],[89.07268,22.19455],[89.04058,22.22713],[89.03234,22.25812],[89.01638,22.25462],[89.01775,22.27114],[88.99423,22.28846],[89.02908,22.29918],[88.98556,22.33086],[88.99011,22.39277],[88.96985,22.41198],[89.00058,22.42991],[88.98977,22.44514],[88.99406,22.47243],[88.97981,22.48385],[88.96059,22.55235],[88.93775,22.55837],[88.93947,22.5934],[88.96436,22.61939],[88.93449,22.61892],[88.92814,22.65045],[88.9472,22.66486],[88.96041,22.70113],[88.92299,22.72251],[88.91492,22.75861],[88.96007,22.80814],[88.96779,22.84738],[88.87063,22.95235],[88.85836,22.94574],[88.8557,22.96708],[88.87115,22.97854],[88.86248,23.00153],[88.84334,23.00849],[88.87776,23.00422],[88.87368,23.0186],[88.88411,23.04044],[88.8748,23.04462],[88.86875,23.08636],[88.93003,23.14446],[88.93672,23.1735],[89.0035,23.21815],[88.94205,23.20821],[88.90737,23.23487],[88.84729,23.23298],[88.81141,23.25506],[88.80437,23.21579],[88.71683,23.2538],[88.7642,23.44781],[88.78892,23.4445],[88.80043,23.5089],[88.7412,23.48576],[88.56525,23.64075],[88.58413,23.87076],[88.67048,23.86857],[88.7,23.90482],[88.73828,23.9191],[88.70429,24.16241],[88.74841,24.1959],[88.69434,24.31737],[88.50934,24.32474],[88.12296,24.51301],[88.08786,24.63232],[88.00683,24.66477],[88.10314,24.78127],[88.1052,24.80387],[88.16167,24.85996],[88.14004,24.93529],[88.22278,24.96271],[88.27325,24.88796],[88.33917,24.86803],[88.46277,25.07468],[88.46054,25.14652],[88.44423,25.19173],[88.48491,25.21177],[88.55632,25.19251],[88.56422,25.17061],[88.61537,25.20121],[88.72163,25.20664],[88.73605,25.18474],[88.80317,25.17076],[88.84265,25.21239],[88.87853,25.179],[88.93758,25.15864],[88.9611,25.20835],[88.95544,25.25354],[89.00762,25.26518],[89.0108,25.30213],[88.99449,25.29607],[88.97981,25.30577],[88.95329,25.30244],[88.92814,25.30352],[88.90565,25.33813],[88.87905,25.3306],[88.86334,25.35488],[88.84034,25.36535],[88.8375,25.40141],[88.81896,25.40932],[88.83888,25.47024],[88.82823,25.48945],[88.81381,25.48992],[88.80918,25.52338],[88.77948,25.51626],[88.75974,25.52648],[88.76661,25.4955],[88.7182,25.50464],[88.70944,25.47985],[88.66756,25.47163],[88.64662,25.48535],[88.6461,25.49798],[88.62396,25.49829],[88.60662,25.5161],[88.59701,25.50836],[88.54019,25.50913],[88.53315,25.53903],[88.49435,25.55916],[88.4983,25.58363],[88.44783,25.5971],[88.45436,25.66349],[88.41161,25.67154],[88.35514,25.72738],[88.26948,25.78165],[88.26004,25.81627],[88.20768,25.78799],[88.18691,25.80128],[88.14811,25.77639],[88.11841,25.8002],[88.08804,25.91334],[88.17661,26.03426],[88.1797,26.14927],[88.35067,26.22244],[88.35865,26.24292],[88.3493,26.25223],[88.35102,26.2841],[88.37634,26.30803],[88.41479,26.3158],[88.41144,26.33642],[88.43487,26.33542],[88.4559,26.37403],[88.48131,26.35042],[88.49727,26.35965],[88.5256,26.35072],[88.48414,26.4602],[88.36938,26.48683],[88.35393,26.4469],[88.33093,26.48929],[88.3705,26.55951],[88.36887,26.57486],[88.40955,26.63802],[88.41642,26.63549],[88.42277,26.56542],[88.44826,26.53593],[88.47504,26.54492],[88.49135,26.51266],[88.51959,26.51136],[88.5274,26.48186],[88.55598,26.48524],[88.56147,26.45743],[88.59323,26.45059],[88.59538,26.47064],[88.62353,26.47088],[88.63005,26.43499],[88.68833,26.40593],[88.67374,26.39686],[88.68876,26.39571],[88.6855,26.38018],[88.70352,26.3358],[88.74197,26.35011],[88.74481,26.33673],[88.73459,26.3298],[88.75502,26.32549],[88.70275,26.31218],[88.67837,26.32265],[88.6679,26.26078],[88.78961,26.31093],[88.83579,26.22983],[88.87493,26.2363],[88.889,26.29772],[88.97071,26.23922],[89.05328,26.2469],[89.05998,26.29403],[88.98599,26.3028],[88.99784,26.33496],[88.91132,26.37018],[88.92728,26.40878],[88.96162,26.45781],[89.00659,26.41401],[89.09105,26.39279],[89.08744,26.32465],[89.13722,26.32049],[89.12195,26.28802],[89.09791,26.31265],[89.141,26.22306],[89.13757,26.18055],[89.15869,26.13708],[89.22992,26.1203],[89.26975,26.05986],[89.35953,26.0077],[89.39025,26.01158],[89.4275,26.04614],[89.43008,26.0122],[89.46544,25.99832],[89.49205,26.0058],[89.54612,26.0058],[89.53908,25.97],[89.58346,25.96761],[89.57951,26.02493],[89.61393,26.05169],[89.65187,26.06156],[89.60517,26.1468],[89.6135,26.17716],[89.65719,26.17161],[89.63058,26.2286],[89.68294,26.22675],[89.68826,26.16067],[89.70201,26.15138],[89.72117,26.15674],[89.71993,26.16637],[89.73401,26.17677],[89.7068,26.18008],[89.72705,26.26771],[89.84653,26.40078],[89.86885,26.46258],[89.86124,26.73307],[89.63369,26.74402],[89.42349,26.83727],[89.3901,26.84225],[89.38319,26.85963],[89.37913,26.86224],[89.31816,26.84815],[89.26219,26.82322],[89.1949,26.81135],[89.12924,26.81189],[89.09362,26.87223],[89.09525,26.89153],[89.07937,26.89742],[89.01904,26.94173],[88.98136,26.91694],[88.949,26.93247],[88.95217,26.96927],[88.92153,26.99467],[88.87836,26.94594],[88.87072,26.95627],[88.8799,27.0397],[88.87184,27.10917],[88.74219,27.144],[88.65674,27.16211],[88.59271,27.19021],[88.5219,27.17379],[88.53298,27.15294],[88.47358,27.11781],[88.45161,27.07655],[88.32235,27.10818],[88.30965,27.13171],[88.22879,27.11811],[88.20219,27.14408],[88.1524,27.11215],[88.08563,27.14072],[88.08331,27.16501],[88.04932,27.21631],[88.01646,27.21612],[88.00889,27.15142],[87.98975,27.1175],[88.02108,27.08747],[88.11743,26.9882],[88.13519,26.98625],[88.12219,26.96845],[88.12084,26.95051],[88.13781,26.93329],[88.14549,26.92055],[88.13747,26.89872],[88.16914,26.872],[88.19107,26.75516],[88.16502,26.67798],[88.16452,26.64111],[88.13163,26.6031],[88.12665,26.57993],[88.09963,26.54195],[88.14064,26.51297],[88.23394,26.55413],[88.19807,26.48916],[88.25729,26.41723],[88.23034,26.37679],[88.2954,26.3438],[88.18442,26.27186],[88.06331,26.1673],[88.01739,26.15628],[87.98091,26.1391],[87.9804,26.07482],[87.94126,26.05971],[87.9186,26.08561],[87.8829,26.04012],[87.85955,26.03411],[87.83775,25.93149],[87.8096,25.93519],[87.82831,25.87065],[87.91191,25.85397],[87.90092,25.77485],[88.05061,25.69366],[88.02675,25.56319],[88.05919,25.54337],[88.08091,25.47721],[87.93482,25.53949],[87.84238,25.46791],[87.81835,25.43141],[87.79123,25.45048],[87.77672,25.42529],[87.76591,25.3804],[87.78985,25.37536],[87.78865,25.34495],[87.85903,25.29002],[87.82539,25.23646],[87.77338,25.21441],[87.79998,25.08684],[87.96134,24.96147],[87.89405,24.82576],[87.86848,24.82553],[87.86745,24.77192],[87.81749,24.7621],[87.84358,24.74012],[87.90195,24.72313],[87.89852,24.56351],[87.78436,24.57975],[87.81784,24.48746],[87.7811,24.34928],[87.64085,24.23131],[87.70437,24.15897],[87.53374,24.13359],[87.44979,23.98342],[87.23299,24.04787],[87.28929,23.89478],[87.1511,23.79728],[87.08312,23.80639],[86.97069,23.87218],[86.94914,23.84682],[86.91172,23.88756],[86.88082,23.86331],[86.88898,23.85844],[86.8882,23.84682],[86.89619,23.82751],[86.88992,23.81887],[86.88099,23.82971],[86.88108,23.84674],[86.86606,23.84667],[86.85361,23.82492],[86.80272,23.83253],[86.82872,23.75895],[86.80967,23.74622],[86.79267,23.68414],[86.74289,23.67927],[86.59286,23.67156],[86.53329,23.63162],[86.48437,23.63964],[86.39167,23.57248],[86.3146,23.42088],[86.1498,23.47159],[86.15427,23.56209],[86.02243,23.57515],[86.04783,23.48859],[85.87068,23.47521],[85.86244,23.43836],[85.90244,23.40166],[85.8748,23.39016],[85.87806,23.35123],[85.81901,23.26941]]]}},{"type":"Feature","properties":{"id":"IN-SK"},"geometry":{"type":"Polygon","coordinates":[[[88.01646,27.21612],[88.04932,27.21631],[88.08331,27.16501],[88.08563,27.14072],[88.1524,27.11215],[88.20219,27.14408],[88.22879,27.11811],[88.30965,27.13171],[88.32235,27.10818],[88.45161,27.07655],[88.47358,27.11781],[88.53298,27.15294],[88.5219,27.17379],[88.59271,27.19021],[88.65674,27.16211],[88.74219,27.144],[88.82257,27.25478],[88.90531,27.27522],[88.91901,27.32483],[88.82981,27.38814],[88.77517,27.45415],[88.88091,27.85192],[88.83559,28.01936],[88.63235,28.12356],[88.54858,28.06057],[88.25332,27.9478],[88.1278,27.95417],[88.13378,27.88015],[88.1973,27.85067],[88.19107,27.79285],[88.04008,27.49223],[88.07277,27.43007],[88.01646,27.21612]]]}},{"type":"Feature","properties":{"id":"IN-UP"},"geometry":{"type":"Polygon","coordinates":[[[77.09896,29.51431],[77.13363,29.47696],[77.14187,29.09577],[77.22753,28.89368],[77.21157,28.8573],[77.2229,28.82091],[77.20985,28.81429],[77.20418,28.80527],[77.20659,28.78451],[77.23839,28.75908],[77.24886,28.75524],[77.25512,28.7558],[77.26006,28.75039],[77.25658,28.7444],[77.25547,28.73861],[77.26057,28.73564],[77.27667,28.73564],[77.28688,28.7248],[77.29083,28.72273],[77.2865,28.7143],[77.29036,28.70602],[77.29628,28.70526],[77.29971,28.71008],[77.3134,28.71366],[77.33207,28.71317],[77.32409,28.69864],[77.33345,28.68147],[77.32997,28.67861],[77.32551,28.67827],[77.32027,28.66234],[77.31988,28.65188],[77.31594,28.64152],[77.34087,28.62299],[77.3419,28.60524],[77.33645,28.60174],[77.33066,28.60098],[77.3246,28.59789],[77.31332,28.59661],[77.31049,28.59085],[77.30422,28.58587],[77.29916,28.58772],[77.29293,28.57634],[77.29886,28.55723],[77.34632,28.51782],[77.37902,28.46416],[77.41447,28.47465],[77.42477,28.46122],[77.41584,28.44039],[77.43086,28.4259],[77.45704,28.43586],[77.47361,28.41918],[77.46811,28.3997],[77.4979,28.40891],[77.48828,28.35515],[77.47781,28.27928],[77.54613,28.24012],[77.49961,28.20216],[77.54699,28.17613],[77.47283,28.08409],[77.53635,27.99167],[77.54116,27.93147],[77.48416,27.93284],[77.46253,27.88081],[77.41567,27.88339],[77.41928,27.8541],[77.34649,27.85395],[77.28092,27.81114],[77.31388,27.76922],[77.29963,27.70632],[77.33448,27.70632],[77.35971,27.65525],[77.32031,27.59517],[77.35851,27.58817],[77.33645,27.57151],[77.34829,27.54381],[77.34023,27.52448],[77.39911,27.50225],[77.40005,27.47804],[77.43679,27.45679],[77.42202,27.42442],[77.44056,27.39089],[77.49464,27.38198],[77.58253,27.32205],[77.62201,27.33761],[77.61351,27.29086],[77.61712,27.25836],[77.65557,27.24112],[77.64759,27.21212],[77.68123,27.19395],[77.6651,27.16974],[77.61754,27.17028],[77.60604,27.11972],[77.52511,27.10131],[77.52107,27.06493],[77.57137,27.07257],[77.56467,27.03068],[77.66183,27.01814],[77.68672,27.04521],[77.719,26.99689],[77.74595,27.03007],[77.7705,27.00147],[77.68072,26.96598],[77.6548,26.96996],[77.60536,26.93523],[77.48142,26.89574],[77.40211,26.82744],[77.48348,26.74331],[77.50562,26.83173],[77.56261,26.8184],[77.72397,26.87874],[77.74646,26.94089],[77.90662,26.88594],[77.90594,26.91824],[77.98181,26.89321],[78.0182,26.90201],[78.04275,26.87001],[78.0503,26.90706],[78.08275,26.90431],[78.13459,26.95099],[78.26333,26.93507],[78.26831,26.87813],[78.19793,26.87981],[78.21029,26.82407],[78.36238,26.87001],[78.57971,26.74913],[78.72064,26.79388],[78.81402,26.76201],[78.87548,26.69761],[78.91882,26.71004],[78.96105,26.67047],[79.01813,26.67231],[79.01641,26.60126],[78.98895,26.59497],[79.07478,26.4824],[79.05384,26.45459],[79.14825,26.43937],[79.09692,26.4014],[79.08473,26.35711],[79.13898,26.33773],[79.09812,26.29941],[79.06019,26.22706],[78.9953,26.24246],[79.04954,26.20319],[78.98586,26.18655],[79.00989,26.15512],[78.95067,26.13278],[79.01435,26.0813],[78.88458,25.90864],[78.89677,25.88594],[78.74914,25.73589],[78.80905,25.69629],[78.8178,25.61923],[78.65198,25.56001],[78.61498,25.58154],[78.58975,25.55521],[78.49027,25.57891],[78.46263,25.5453],[78.43766,25.56296],[78.41156,25.52238],[78.43225,25.47396],[78.41697,25.47566],[78.38281,25.43986],[78.3514,25.45404],[78.33869,25.40087],[78.30136,25.36473],[78.40272,25.18102],[78.45851,25.16361],[78.42658,25.14948],[78.44804,25.12337],[78.34384,25.08248],[78.33938,24.99726],[78.16841,24.87584],[78.23621,24.76584],[78.27501,24.59349],[78.22402,24.52651],[78.27424,24.47488],[78.26548,24.45347],[78.36891,24.37993],[78.32839,24.32582],[78.39328,24.26292],[78.51276,24.39181],[78.70021,24.22943],[78.8166,24.17713],[78.9759,24.35397],[78.98483,24.44527],[78.91239,24.46558],[78.97024,24.49105],[78.88269,24.64233],[78.77437,24.58709],[78.7512,24.65122],[78.77592,24.72017],[78.77489,24.85404],[78.67807,24.89842],[78.63447,24.96271],[78.65335,25.05154],[78.60546,25.09523],[78.57748,25.26534],[78.42109,25.28164],[78.55327,25.31051],[78.52872,25.35907],[78.58709,25.3448],[78.56134,25.38807],[78.60116,25.398],[78.60975,25.41505],[78.6379,25.40249],[78.64425,25.43691],[78.72013,25.43722],[78.66022,25.38668],[78.70425,25.37349],[78.71686,25.34619],[78.77471,25.35209],[78.77188,25.37698],[78.74579,25.41009],[78.82321,25.42242],[78.79978,25.43815],[78.76665,25.42583],[78.73017,25.46784],[78.74133,25.49705],[78.83849,25.46202],[78.81995,25.44001],[78.83188,25.42847],[78.87187,25.458],[78.83583,25.5082],[78.92681,25.56753],[78.95565,25.54507],[78.92354,25.43567],[78.97212,25.43738],[78.99238,25.35736],[78.84492,25.35643],[78.83531,25.23584],[78.87702,25.34728],[78.96234,25.32587],[78.97899,25.27264],[78.87531,25.236],[78.8832,25.16113],[79.00302,25.27822],[79.06379,25.20696],[79.031,25.1344],[79.08302,25.17962],[79.31802,25.13891],[79.27562,25.24485],[79.36283,25.23786],[79.26034,25.27869],[79.29244,25.34899],[79.35029,25.33145],[79.34961,25.28738],[79.39733,25.28521],[79.42377,25.25463],[79.47458,25.2863],[79.4914,25.26596],[79.39081,25.11171],[79.46805,25.12492],[79.49449,25.08264],[79.56813,25.16983],[79.6368,25.13067],[79.75404,25.13829],[79.85893,25.09104],[79.86459,25.23615],[80.09256,25.35348],[80.12998,25.33875],[80.20053,25.40901],[80.25255,25.40265],[80.28104,25.42544],[80.41648,25.1633],[80.34542,25.12259],[80.26937,25.0265],[80.33374,24.99383],[80.39434,25.01157],[80.38679,25.05154],[80.44884,25.07864],[80.47566,25.04314],[80.50086,25.03879],[80.49605,25.02938],[80.43554,25.03949],[80.45991,24.97578],[80.52927,25.00006],[80.54609,25.0286],[80.506,25.04268],[80.5054,25.08116],[80.47425,25.09741],[80.51107,25.09881],[80.54695,25.06227],[80.59484,25.08979],[80.60377,25.15118],[80.63398,25.12865],[80.62042,25.0971],[80.7011,25.05543],[80.77972,25.05823],[80.70333,25.13021],[80.80676,25.13728],[80.83937,25.1037],[80.84289,25.18389],[80.86323,25.18366],[80.86838,25.17542],[80.88272,25.18552],[80.90692,25.15592],[80.8719,25.11373],[80.85362,24.99134],[80.79371,24.96287],[80.82435,24.91991],[81.22741,24.95431],[81.27822,25.16703],[81.35942,25.16051],[81.49709,25.04936],[81.48181,25.13285],[81.5316,25.14497],[81.51786,25.18987],[81.59322,25.17931],[81.62841,25.15336],[81.60026,25.1229],[81.63459,25.10767],[81.60078,25.06569],[81.68283,25.0646],[81.69605,25.03863],[81.73004,25.04874],[81.8011,25.00255],[81.90376,24.99943],[81.90359,24.88923],[81.96281,24.83441],[82.01637,24.84594],[82.12177,24.78782],[82.20794,24.79265],[82.29343,24.66729],[82.30339,24.60675],[82.42286,24.59286],[82.40518,24.70488],[82.54491,24.65044],[82.67692,24.69942],[82.77236,24.63344],[82.8,24.58069],[82.72481,24.54368],[82.71606,24.37305],[82.76618,24.3754],[82.7703,24.29797],[82.72808,24.13124],[82.67744,24.15724],[82.6637,24.12779],[82.70851,24.0945],[82.79708,24.00319],[82.80876,23.96492],[82.95226,23.87642],[83.15963,23.90467],[83.32134,24.10131],[83.41197,24.26887],[83.38073,24.31456],[83.45781,24.36508],[83.39893,24.42823],[83.39275,24.50027],[83.52355,24.53431],[83.54089,24.62657],[83.48098,24.73342],[83.39824,24.78735],[83.34125,25.0125],[83.3543,25.19096],[83.40331,25.21425],[83.41215,25.24888],[83.46974,25.25634],[83.49077,25.28614],[83.64852,25.34464],[83.65917,25.36745],[83.74242,25.40792],[83.76422,25.3859],[83.84593,25.43645],[83.81452,25.45459],[83.871,25.49333],[83.88061,25.51765],[84.05845,25.64833],[84.0921,25.72908],[84.17278,25.72011],[84.24419,25.65978],[84.32899,25.70588],[84.38804,25.76959],[84.54133,25.65762],[84.56245,25.73743],[84.63541,25.73125],[84.60433,25.76124],[84.62047,25.7968],[84.54082,25.85737],[84.27955,25.94538],[84.15527,26.03642],[84.0279,26.14203],[84.00833,26.18224],[84.0279,26.19865],[84.01082,26.23676],[84.0842,26.22105],[84.12729,26.25909],[84.16282,26.24769],[84.17398,26.26878],[84.18359,26.31418],[84.17638,26.37126],[83.90799,26.44744],[83.91262,26.5172],[83.93932,26.52518],[83.97013,26.50698],[83.97691,26.53194],[84.00798,26.52357],[84.05287,26.53993],[84.09141,26.63855],[84.27286,26.6031],[84.42066,26.62459],[84.30599,26.75082],[84.24144,26.73365],[84.26307,26.83984],[84.22616,26.87369],[84.16265,26.82836],[84.13141,26.89313],[84.07579,26.88027],[84.05107,26.90982],[84.05845,26.92023],[84.03871,26.94211],[84.05433,26.96246],[84.04661,27.04337],[84.00403,27.06508],[84.03717,27.10589],[83.9479,27.08663],[83.99116,27.20395],[83.9134,27.25371],[83.92507,27.32953],[83.84164,27.30863],[83.85595,27.35797],[83.61288,27.47013],[83.38932,27.4804],[83.40579,27.40758],[83.35136,27.33885],[83.29999,27.32778],[83.2673,27.36235],[83.27197,27.38309],[83.16993,27.45694],[83.03552,27.44781],[82.94969,27.47004],[82.93261,27.50328],[82.80395,27.497],[82.73597,27.50202],[82.75073,27.5865],[82.70378,27.72122],[82.46405,27.6716],[82.06554,27.92222],[81.97214,27.93322],[81.91223,27.84995],[81.47867,28.08303],[81.48179,28.12148],[81.38683,28.17638],[81.32923,28.13521],[81.19847,28.36284],[81.08507,28.38346],[80.89648,28.47237],[80.72427,28.57472],[80.55142,28.69182],[80.50575,28.6706],[80.52443,28.54897],[80.43906,28.63576],[80.37589,28.63071],[80.26576,28.71994],[80.25701,28.75396],[80.21495,28.75562],[80.11762,28.8288],[80.07471,28.82452],[80.06466,28.83813],[80.02304,28.74237],[79.96484,28.72958],[79.86803,28.80888],[79.91901,28.81188],[79.86854,28.83926],[79.82597,28.79353],[79.84176,28.87444],[79.8076,28.88691],[79.70495,28.84738],[79.65362,28.86963],[79.58787,28.85023],[79.56298,28.89398],[79.42789,28.86151],[79.41415,28.94972],[79.38703,28.94852],[79.37038,28.98682],[79.30789,28.96849],[79.21245,29.02435],[79.16645,29.01399],[79.16936,29.04731],[79.14396,29.05917],[79.14739,29.12757],[79.11581,29.12712],[79.03547,29.16415],[79.03289,29.11002],[78.98345,29.14016],[78.95633,29.12082],[78.91376,29.11909],[78.91282,29.14578],[78.86389,29.2396],[78.81617,29.2333],[78.7251,29.30556],[78.76613,29.33534],[78.7984,29.32082],[78.82759,29.36931],[78.84836,29.34387],[78.94552,29.43436],[78.79446,29.4659],[78.55155,29.58958],[78.50503,29.72353],[78.36101,29.78225],[78.20342,29.73337],[78.01923,29.5836],[77.92997,29.59465],[77.97632,29.61614],[77.96396,29.66702],[77.92619,29.71444],[77.87693,29.69178],[77.8177,29.66344],[77.71556,29.87354],[77.74457,29.91804],[77.73324,29.95805],[77.76148,30.01084],[77.78646,30.02986],[77.76886,30.04576],[77.80431,30.09642],[77.82646,30.08513],[77.95022,30.23192],[77.57549,30.39804],[77.58613,30.31006],[77.46425,30.17095],[77.42271,30.15848],[77.42048,30.10147],[77.28898,30.04606],[77.27577,29.99463],[77.19509,29.89944],[77.13174,29.74723],[77.14839,29.6876],[77.10754,29.62077],[77.09896,29.51431]]]}},{"type":"Feature","properties":{"id":"IN-JH"},"geometry":{"type":"Polygon","coordinates":[[[83.32134,24.10131],[83.4185,24.08596],[83.56853,23.8772],[83.70758,23.81958],[83.72165,23.68508],[83.78929,23.58853],[84.02652,23.63068],[83.97039,23.37424],[84.06978,23.33059],[84.02961,23.16592],[84.14909,22.97546],[84.3695,22.97704],[84.39834,22.92045],[83.99425,22.53602],[84.13295,22.4748],[84.17346,22.39769],[84.31835,22.33705],[84.57893,22.43292],[84.7063,22.41896],[84.8117,22.44958],[84.90509,22.42562],[85.06782,22.48623],[85.094,22.3207],[85.11846,22.31855],[85.11408,22.29608],[85.07228,22.26987],[85.02696,22.12683],[85.23965,22.01117],[85.28549,22.09566],[85.40651,22.16149],[85.5828,22.08373],[85.66966,22.07259],[85.82107,22.09104],[85.78639,21.98698],[85.91686,21.97616],[86.03839,22.30752],[86.01848,22.41237],[85.96458,22.46973],[86.04663,22.5682],[86.11083,22.49622],[86.2825,22.44799],[86.41382,22.30831],[86.49295,22.3469],[86.7247,22.21569],[86.89189,22.27225],[86.83456,22.33102],[86.84967,22.40674],[86.76349,22.43213],[86.74804,22.47972],[86.80847,22.47861],[86.76538,22.57645],[86.65311,22.58453],[86.63509,22.66471],[86.41914,22.78979],[86.45828,22.89293],[86.43648,22.92535],[86.54874,22.96819],[86.55286,22.98605],[86.4212,22.99663],[86.39871,22.9821],[86.2976,23.01228],[86.21074,22.99569],[86.04423,23.14572],[85.89403,23.15519],[85.81901,23.26941],[85.87806,23.35123],[85.8748,23.39016],[85.90244,23.40166],[85.86244,23.43836],[85.87068,23.47521],[86.04783,23.48859],[86.02243,23.57515],[86.15427,23.56209],[86.1498,23.47159],[86.3146,23.42088],[86.39167,23.57248],[86.48437,23.63964],[86.53329,23.63162],[86.59286,23.67156],[86.74289,23.67927],[86.79267,23.68414],[86.80967,23.74622],[86.82872,23.75895],[86.80272,23.83253],[86.85361,23.82492],[86.86606,23.84667],[86.88108,23.84674],[86.88099,23.82971],[86.88992,23.81887],[86.89619,23.82751],[86.8882,23.84682],[86.88898,23.85844],[86.88082,23.86331],[86.91172,23.88756],[86.94914,23.84682],[86.97069,23.87218],[87.08312,23.80639],[87.1511,23.79728],[87.28929,23.89478],[87.23299,24.04787],[87.44979,23.98342],[87.53374,24.13359],[87.70437,24.15897],[87.64085,24.23131],[87.7811,24.34928],[87.81784,24.48746],[87.78436,24.57975],[87.89852,24.56351],[87.90195,24.72313],[87.84358,24.74012],[87.81749,24.7621],[87.86745,24.77192],[87.86848,24.82553],[87.89405,24.82576],[87.96134,24.96147],[87.79998,25.08684],[87.77338,25.21441],[87.82539,25.23646],[87.68033,25.31237],[87.60515,25.32106],[87.58369,25.35271],[87.5709,25.33301],[87.55202,25.33254],[87.53906,25.27947],[87.48344,25.30383],[87.50069,25.26014],[87.47898,25.25929],[87.47752,25.19368],[87.41967,25.21123],[87.39812,25.23219],[87.36971,25.20835],[87.32508,25.22342],[87.31667,25.16198],[87.29066,25.08831],[87.23959,25.11661],[87.23968,25.09072],[87.21393,25.09026],[87.17848,25.06227],[87.14741,25.01927],[87.15866,24.89391],[87.10235,24.84812],[87.05532,24.61237],[86.95129,24.63484],[86.8507,24.55992],[86.59526,24.58958],[86.45004,24.36586],[86.28833,24.46027],[86.32163,24.5824],[86.12491,24.61143],[86.12869,24.71876],[85.7373,24.8154],[85.66108,24.61518],[84.91092,24.36773],[84.88672,24.46402],[84.83522,24.45215],[84.83436,24.51432],[84.80621,24.53228],[84.6869,24.45746],[84.66287,24.39291],[84.56932,24.40963],[84.57378,24.38259],[84.53412,24.37774],[84.50408,24.28624],[84.48572,24.31487],[84.3338,24.40776],[84.3374,24.42714],[84.2962,24.46668],[84.32847,24.49277],[84.29397,24.56304],[84.26908,24.55711],[84.27989,24.5454],[84.25329,24.52744],[84.23192,24.55211],[84.21329,24.5347],[84.19733,24.55508],[84.14179,24.53338],[84.11647,24.47183],[84.01176,24.66605],[83.94584,24.54899],[83.75015,24.50245],[83.52355,24.53431],[83.39275,24.50027],[83.39893,24.42823],[83.45781,24.36508],[83.38073,24.31456],[83.41197,24.26887],[83.32134,24.10131]]]}},{"type":"Feature","properties":{"id":"IN-BR"},"geometry":{"type":"Polygon","coordinates":[[[83.34125,25.0125],[83.39824,24.78735],[83.48098,24.73342],[83.54089,24.62657],[83.52355,24.53431],[83.75015,24.50245],[83.94584,24.54899],[84.01176,24.66605],[84.11647,24.47183],[84.14179,24.53338],[84.19733,24.55508],[84.21329,24.5347],[84.23192,24.55211],[84.25329,24.52744],[84.27989,24.5454],[84.26908,24.55711],[84.29397,24.56304],[84.32847,24.49277],[84.2962,24.46668],[84.3374,24.42714],[84.3338,24.40776],[84.48572,24.31487],[84.50408,24.28624],[84.53412,24.37774],[84.57378,24.38259],[84.56932,24.40963],[84.66287,24.39291],[84.6869,24.45746],[84.80621,24.53228],[84.83436,24.51432],[84.83522,24.45215],[84.88672,24.46402],[84.91092,24.36773],[85.66108,24.61518],[85.7373,24.8154],[86.12869,24.71876],[86.12491,24.61143],[86.32163,24.5824],[86.28833,24.46027],[86.45004,24.36586],[86.59526,24.58958],[86.8507,24.55992],[86.95129,24.63484],[87.05532,24.61237],[87.10235,24.84812],[87.15866,24.89391],[87.14741,25.01927],[87.17848,25.06227],[87.21393,25.09026],[87.23968,25.09072],[87.23959,25.11661],[87.29066,25.08831],[87.31667,25.16198],[87.32508,25.22342],[87.36971,25.20835],[87.39812,25.23219],[87.41967,25.21123],[87.47752,25.19368],[87.47898,25.25929],[87.50069,25.26014],[87.48344,25.30383],[87.53906,25.27947],[87.55202,25.33254],[87.5709,25.33301],[87.58369,25.35271],[87.60515,25.32106],[87.68033,25.31237],[87.82539,25.23646],[87.85903,25.29002],[87.78865,25.34495],[87.78985,25.37536],[87.76591,25.3804],[87.77672,25.42529],[87.79123,25.45048],[87.81835,25.43141],[87.84238,25.46791],[87.93482,25.53949],[88.08091,25.47721],[88.05919,25.54337],[88.02675,25.56319],[88.05061,25.69366],[87.90092,25.77485],[87.91191,25.85397],[87.82831,25.87065],[87.8096,25.93519],[87.83775,25.93149],[87.85955,26.03411],[87.8829,26.04012],[87.9186,26.08561],[87.94126,26.05971],[87.9804,26.07482],[87.98091,26.1391],[88.01739,26.15628],[88.06331,26.1673],[88.18442,26.27186],[88.2954,26.3438],[88.23034,26.37679],[88.25729,26.41723],[88.19807,26.48916],[88.23394,26.55413],[88.14064,26.51297],[88.09963,26.54195],[88.10382,26.51927],[88.08923,26.50168],[88.104,26.46957],[88.09284,26.43706],[88.02906,26.38494],[88.02992,26.36457],[88.00572,26.36145],[87.99233,26.3711],[87.99164,26.39202],[87.96607,26.39755],[87.93332,26.41758],[87.92083,26.42984],[87.92452,26.44583],[87.8989,26.44886],[87.9089,26.45996],[87.8895,26.48724],[87.85869,26.46327],[87.86144,26.44921],[87.83792,26.43484],[87.82865,26.44982],[87.78951,26.47303],[87.7605,26.40805],[87.73308,26.40828],[87.68033,26.43764],[87.67785,26.41608],[87.64978,26.40597],[87.65132,26.39379],[87.62763,26.39371],[87.61261,26.39033],[87.60498,26.38025],[87.58678,26.38187],[87.58815,26.3914],[87.55274,26.40596],[87.5473,26.41819],[87.51,26.43188],[87.46566,26.44058],[87.36491,26.40463],[87.34568,26.34787],[87.26569,26.37518],[87.26587,26.40592],[87.24682,26.4143],[87.18863,26.40558],[87.15325,26.40509],[87.09147,26.45039],[87.0707,26.58571],[87.04691,26.58685],[87.04004,26.56595],[87.01559,26.53228],[86.95912,26.52076],[86.94543,26.52076],[86.83301,26.43906],[86.76797,26.45892],[86.74025,26.42386],[86.69277,26.45044],[86.62686,26.46891],[86.61313,26.48658],[86.57217,26.49661],[86.54258,26.53819],[86.49726,26.54218],[86.4563,26.5668],[86.39545,26.58484],[86.31975,26.61922],[86.25546,26.61492],[86.23151,26.58975],[86.19812,26.59489],[86.18662,26.61515],[86.13596,26.60651],[86.13942,26.61738],[86.06934,26.65731],[86.03453,26.66502],[85.96312,26.65137],[85.94664,26.61492],[85.84562,26.56254],[85.85583,26.59017],[85.85126,26.60866],[85.83126,26.61134],[85.82317,26.59865],[85.79386,26.62528],[85.76907,26.63076],[85.73756,26.64784],[85.72315,26.67471],[85.73483,26.79613],[85.71996,26.8207],[85.66239,26.84822],[85.61621,26.86721],[85.59388,26.85095],[85.5757,26.85955],[85.56471,26.84133],[85.47752,26.79292],[85.34302,26.74954],[85.21159,26.75933],[85.18046,26.80519],[85.19291,26.86909],[85.15883,26.86966],[85.0237,26.85003],[85.05592,26.88991],[85.00536,26.89523],[84.97186,26.9149],[84.96687,26.95599],[84.89152,26.97241],[84.86608,26.98354],[84.85754,26.98984],[84.85333,27.00836],[84.85187,27.00889],[84.84904,27.0095],[84.82531,27.02063],[84.793,26.9968],[84.77651,27.01623],[84.75686,27.00308],[84.64399,27.04613],[84.64725,27.07632],[84.67257,27.09726],[84.68708,27.22295],[84.62161,27.33885],[84.29315,27.39],[84.25735,27.44941],[84.21376,27.45218],[84.10791,27.52399],[84.02229,27.43836],[83.93306,27.44939],[83.86182,27.4241],[83.85595,27.35797],[83.84164,27.30863],[83.92507,27.32953],[83.9134,27.25371],[83.99116,27.20395],[83.9479,27.08663],[84.03717,27.10589],[84.00403,27.06508],[84.04661,27.04337],[84.05433,26.96246],[84.03871,26.94211],[84.05845,26.92023],[84.05107,26.90982],[84.07579,26.88027],[84.13141,26.89313],[84.16265,26.82836],[84.22616,26.87369],[84.26307,26.83984],[84.24144,26.73365],[84.30599,26.75082],[84.42066,26.62459],[84.27286,26.6031],[84.09141,26.63855],[84.05287,26.53993],[84.00798,26.52357],[83.97691,26.53194],[83.97013,26.50698],[83.93932,26.52518],[83.91262,26.5172],[83.90799,26.44744],[84.17638,26.37126],[84.18359,26.31418],[84.17398,26.26878],[84.16282,26.24769],[84.12729,26.25909],[84.0842,26.22105],[84.01082,26.23676],[84.0279,26.19865],[84.00833,26.18224],[84.0279,26.14203],[84.15527,26.03642],[84.27955,25.94538],[84.54082,25.85737],[84.62047,25.7968],[84.60433,25.76124],[84.63541,25.73125],[84.56245,25.73743],[84.54133,25.65762],[84.38804,25.76959],[84.32899,25.70588],[84.24419,25.65978],[84.17278,25.72011],[84.0921,25.72908],[84.05845,25.64833],[83.88061,25.51765],[83.871,25.49333],[83.81452,25.45459],[83.84593,25.43645],[83.76422,25.3859],[83.74242,25.40792],[83.65917,25.36745],[83.64852,25.34464],[83.49077,25.28614],[83.46974,25.25634],[83.41215,25.24888],[83.40331,25.21425],[83.3543,25.19096],[83.34125,25.0125]]]}},{"type":"Feature","properties":{"id":"IN-DD"},"geometry":{"type":"Polygon","coordinates":[[[70.8467,20.44438],[72.47526,20.38318],[72.89291,20.36748],[72.90801,20.43087],[72.83901,20.48555],[71.00154,20.74648],[70.87331,20.73203],[70.8467,20.44438]]]}},{"type":"Feature","properties":{"id":"IN-MP"},"geometry":{"type":"Polygon","coordinates":[[[73.98296,22.51477],[74.29418,22.39182],[74.15685,22.32911],[74.06776,22.38246],[74.07875,22.22205],[74.11651,22.2149],[74.13333,22.10552],[74.17625,22.08341],[74.09763,22.01563],[74.14947,21.95323],[74.2353,21.92807],[74.43717,22.03282],[74.52609,21.90769],[74.50859,21.72091],[74.5479,21.71692],[74.58377,21.66476],[74.66686,21.65152],[74.75475,21.61689],[74.89654,21.62934],[75.06134,21.56486],[75.11455,21.45306],[75.22304,21.40928],[75.40912,21.38754],[75.90539,21.3901],[76.08821,21.3666],[76.15653,21.2625],[76.14151,21.23154],[76.17937,21.19833],[76.1107,21.16216],[76.17301,21.08386],[76.40338,21.07713],[76.66774,21.27897],[76.61933,21.33351],[76.73812,21.4112],[76.74473,21.4398],[76.79151,21.47958],[76.77246,21.50642],[76.78997,21.59407],[76.90429,21.60349],[77.07733,21.72474],[77.2586,21.71437],[77.28435,21.75965],[77.49893,21.76332],[77.54596,21.70017],[77.6081,21.53165],[77.41104,21.54315],[77.49343,21.37763],[78.0079,21.41455],[78.27793,21.58386],[78.42178,21.60077],[78.43654,21.50099],[78.52787,21.52414],[78.69832,21.4823],[78.9347,21.49268],[78.91136,21.59295],[79.14585,21.62583],[79.28935,21.69108],[79.40076,21.67561],[79.42274,21.69539],[79.49981,21.66588],[79.53638,21.5366],[79.74031,21.60269],[79.79764,21.58258],[79.85549,21.53468],[79.91489,21.52207],[79.93154,21.5556],[80.06732,21.55528],[80.13427,21.61115],[80.20946,21.6354],[80.32344,21.57587],[80.37546,21.52342],[80.41803,21.44428],[80.39597,21.4084],[80.40747,21.37619],[80.44052,21.37444],[80.45768,21.40129],[80.52566,21.39298],[80.60789,21.32263],[80.65784,21.32967],[80.7344,21.46648],[80.73921,21.74212],[80.81851,21.75009],[80.90606,22.13112],[80.98571,22.03441],[81.11171,22.44339],[81.35684,22.52175],[81.39358,22.44498],[81.77192,22.66043],[81.75991,22.85086],[81.93929,22.95776],[81.93886,23.07823],[82.1149,23.10247],[82.16245,23.15077],[82.13687,23.16608],[82.20382,23.3164],[82.06066,23.37991],[81.94942,23.49347],[81.75338,23.56619],[81.62567,23.48875],[81.57091,23.58113],[81.69467,23.71605],[81.59614,23.88834],[81.66274,23.92821],[81.72214,23.83999],[81.79218,23.80764],[81.90685,23.85569],[82.49599,23.7844],[82.66593,23.86511],[82.65666,23.90216],[82.80876,23.96492],[82.79708,24.00319],[82.70851,24.0945],[82.6637,24.12779],[82.67744,24.15724],[82.72808,24.13124],[82.7703,24.29797],[82.76618,24.3754],[82.71606,24.37305],[82.72481,24.54368],[82.8,24.58069],[82.77236,24.63344],[82.67692,24.69942],[82.54491,24.65044],[82.40518,24.70488],[82.42286,24.59286],[82.30339,24.60675],[82.29343,24.66729],[82.20794,24.79265],[82.12177,24.78782],[82.01637,24.84594],[81.96281,24.83441],[81.90359,24.88923],[81.90376,24.99943],[81.8011,25.00255],[81.73004,25.04874],[81.69605,25.03863],[81.68283,25.0646],[81.60078,25.06569],[81.63459,25.10767],[81.60026,25.1229],[81.62841,25.15336],[81.59322,25.17931],[81.51786,25.18987],[81.5316,25.14497],[81.48181,25.13285],[81.49709,25.04936],[81.35942,25.16051],[81.27822,25.16703],[81.22741,24.95431],[80.82435,24.91991],[80.79371,24.96287],[80.85362,24.99134],[80.8719,25.11373],[80.90692,25.15592],[80.88272,25.18552],[80.86838,25.17542],[80.86323,25.18366],[80.84289,25.18389],[80.83937,25.1037],[80.80676,25.13728],[80.70333,25.13021],[80.77972,25.05823],[80.7011,25.05543],[80.62042,25.0971],[80.63398,25.12865],[80.60377,25.15118],[80.59484,25.08979],[80.54695,25.06227],[80.51107,25.09881],[80.47425,25.09741],[80.5054,25.08116],[80.506,25.04268],[80.54609,25.0286],[80.52927,25.00006],[80.45991,24.97578],[80.43554,25.03949],[80.49605,25.02938],[80.50086,25.03879],[80.47566,25.04314],[80.44884,25.07864],[80.38679,25.05154],[80.39434,25.01157],[80.33374,24.99383],[80.26937,25.0265],[80.34542,25.12259],[80.41648,25.1633],[80.28104,25.42544],[80.25255,25.40265],[80.20053,25.40901],[80.12998,25.33875],[80.09256,25.35348],[79.86459,25.23615],[79.85893,25.09104],[79.75404,25.13829],[79.6368,25.13067],[79.56813,25.16983],[79.49449,25.08264],[79.46805,25.12492],[79.39081,25.11171],[79.4914,25.26596],[79.47458,25.2863],[79.42377,25.25463],[79.39733,25.28521],[79.34961,25.28738],[79.35029,25.33145],[79.29244,25.34899],[79.26034,25.27869],[79.36283,25.23786],[79.27562,25.24485],[79.31802,25.13891],[79.08302,25.17962],[79.031,25.1344],[79.06379,25.20696],[79.00302,25.27822],[78.8832,25.16113],[78.87531,25.236],[78.97899,25.27264],[78.96234,25.32587],[78.87702,25.34728],[78.83531,25.23584],[78.84492,25.35643],[78.99238,25.35736],[78.97212,25.43738],[78.92354,25.43567],[78.95565,25.54507],[78.92681,25.56753],[78.83583,25.5082],[78.87187,25.458],[78.83188,25.42847],[78.81995,25.44001],[78.83849,25.46202],[78.74133,25.49705],[78.73017,25.46784],[78.76665,25.42583],[78.79978,25.43815],[78.82321,25.42242],[78.74579,25.41009],[78.77188,25.37698],[78.77471,25.35209],[78.71686,25.34619],[78.70425,25.37349],[78.66022,25.38668],[78.72013,25.43722],[78.64425,25.43691],[78.6379,25.40249],[78.60975,25.41505],[78.60116,25.398],[78.56134,25.38807],[78.58709,25.3448],[78.52872,25.35907],[78.55327,25.31051],[78.42109,25.28164],[78.57748,25.26534],[78.60546,25.09523],[78.65335,25.05154],[78.63447,24.96271],[78.67807,24.89842],[78.77489,24.85404],[78.77592,24.72017],[78.7512,24.65122],[78.77437,24.58709],[78.88269,24.64233],[78.97024,24.49105],[78.91239,24.46558],[78.98483,24.44527],[78.9759,24.35397],[78.8166,24.17713],[78.70021,24.22943],[78.51276,24.39181],[78.39328,24.26292],[78.32839,24.32582],[78.36891,24.37993],[78.26548,24.45347],[78.27424,24.47488],[78.22402,24.52651],[78.27501,24.59349],[78.23621,24.76584],[78.16841,24.87584],[78.33938,24.99726],[78.34384,25.08248],[78.44804,25.12337],[78.42658,25.14948],[78.45851,25.16361],[78.40272,25.18102],[78.30136,25.36473],[78.33869,25.40087],[78.3514,25.45404],[78.38281,25.43986],[78.41697,25.47566],[78.43225,25.47396],[78.41156,25.52238],[78.43766,25.56296],[78.46263,25.5453],[78.49027,25.57891],[78.58975,25.55521],[78.61498,25.58154],[78.65198,25.56001],[78.8178,25.61923],[78.80905,25.69629],[78.74914,25.73589],[78.89677,25.88594],[78.88458,25.90864],[79.01435,26.0813],[78.95067,26.13278],[79.00989,26.15512],[78.98586,26.18655],[79.04954,26.20319],[78.9953,26.24246],[79.06019,26.22706],[79.09812,26.29941],[79.13898,26.33773],[79.08473,26.35711],[79.09692,26.4014],[79.14825,26.43937],[79.05384,26.45459],[79.07478,26.4824],[78.98895,26.59497],[79.01641,26.60126],[79.01813,26.67231],[78.96105,26.67047],[78.91882,26.71004],[78.87548,26.69761],[78.81402,26.76201],[78.72064,26.79388],[78.57971,26.74913],[78.36238,26.87001],[78.21029,26.82407],[78.16806,26.82353],[78.18883,26.78086],[78.09554,26.78791],[78.12154,26.74653],[78.09408,26.73434],[78.09047,26.66947],[77.99941,26.69462],[77.96267,26.65904],[77.9007,26.6572],[77.89512,26.61277],[77.83435,26.60556],[77.82474,26.54784],[77.75419,26.5443],[77.72518,26.49845],[77.6772,26.50322],[77.62158,26.45512],[77.55892,26.43522],[77.53807,26.40985],[77.4367,26.40217],[77.44305,26.36364],[77.35173,26.35926],[77.2925,26.2861],[77.2077,26.23153],[77.12711,26.22929],[77.09312,26.18409],[76.91493,26.09533],[76.80662,25.9906],[76.79408,25.94353],[76.72491,25.90138],[76.6377,25.9115],[76.62105,25.86864],[76.59187,25.87745],[76.5172,25.80066],[76.52269,25.73573],[76.47291,25.70604],[76.49591,25.66303],[76.51891,25.535],[76.53968,25.50216],[76.56406,25.45009],[76.58792,25.42932],[76.59942,25.39304],[76.72267,25.33518],[76.86112,25.33192],[76.93691,25.28071],[77.08436,25.33619],[77.20315,25.30616],[77.27508,25.42746],[77.35645,25.42932],[77.36057,25.26487],[77.41069,25.22109],[77.39559,25.11979],[77.31044,25.07938],[77.27053,25.11482],[77.18316,25.10915],[77.15269,25.08606],[77.14616,25.09189],[77.08505,25.05776],[77.00368,25.07471],[76.87837,25.04112],[76.86498,24.961],[76.92008,24.91446],[76.91245,24.88838],[76.94257,24.85918],[76.90489,24.84204],[76.79271,24.82304],[76.84043,24.77496],[76.85065,24.74612],[76.95304,24.75805],[77.03012,24.71299],[77.07046,24.63406],[77.05604,24.52783],[76.97673,24.45785],[76.91631,24.48144],[76.91648,24.54478],[76.88266,24.52299],[76.8552,24.54876],[76.81477,24.53314],[76.85091,24.46652],[76.83511,24.35773],[76.86344,24.27466],[76.92343,24.21949],[76.95407,24.19279],[76.91639,24.18747],[76.90292,24.12936],[76.80129,24.11902],[76.75718,24.16774],[76.7134,24.16171],[76.67495,24.19154],[76.70156,24.24336],[76.69195,24.28248],[76.66886,24.26402],[76.6171,24.25792],[76.5911,24.24422],[76.56869,24.20634],[76.57736,24.17627],[76.5135,24.15748],[76.50664,24.19913],[76.47557,24.21737],[76.41892,24.22011],[76.41763,24.2061],[76.31893,24.24485],[76.28271,24.20884],[76.21215,24.21283],[76.18846,24.33364],[76.15859,24.28483],[76.13954,24.27372],[76.12615,24.08737],[76.03981,24.0637],[75.96307,24.01855],[75.98865,23.92946],[75.88514,23.88097],[75.84943,23.88395],[75.77579,23.84808],[75.74232,23.89588],[75.69717,23.90012],[75.72961,23.82319],[75.69271,23.81251],[75.7018,23.78063],[75.68824,23.75408],[75.65202,23.79743],[75.59452,23.79673],[75.56061,23.81966],[75.58267,23.8341],[75.52499,23.88097],[75.4589,23.92036],[75.47058,23.97574],[75.51881,24.02357],[75.5861,23.9908],[75.62404,23.99346],[75.67314,24.0151],[75.71313,23.96303],[75.76961,24.00115],[75.78077,24.04035],[75.8472,24.07107],[75.75038,24.14189],[75.77545,24.20688],[75.81922,24.23272],[75.81012,24.29797],[75.76772,24.29829],[75.72524,24.39087],[75.79433,24.47261],[75.8363,24.41393],[75.90145,24.44418],[75.91054,24.48121],[75.88952,24.51471],[75.91999,24.52963],[75.87776,24.58428],[75.82729,24.60769],[75.84772,24.6492],[75.78489,24.76491],[75.61374,24.67806],[75.57271,24.72282],[75.44311,24.68227],[75.16965,24.75213],[75.25497,24.81696],[75.23815,24.85482],[75.31831,24.80901],[75.41925,24.85404],[75.42629,24.8855],[75.31007,24.85902],[75.26149,24.87242],[75.28947,24.91041],[75.27282,24.96302],[75.32123,24.89827],[75.33462,24.95119],[75.30956,24.99383],[75.3535,25.03723],[75.15129,25.04532],[75.16656,25.00457],[75.11335,24.96442],[75.11884,24.8816],[75.07129,24.87537],[75.03816,24.84469],[74.97585,24.85809],[74.83843,24.97384],[74.82376,24.90916],[74.87731,24.78829],[75.03318,24.75431],[74.88641,24.635],[74.80195,24.79281],[74.77312,24.66012],[74.81088,24.66371],[74.74651,24.52432],[74.71097,24.49542],[74.74539,24.4684],[74.81775,24.46652],[74.8138,24.43746],[74.85208,24.44511],[74.85671,24.42495],[74.78994,24.35773],[74.7793,24.29093],[74.74771,24.23992],[74.87337,24.2432],[74.91027,24.20595],[74.88006,24.1751],[74.89362,24.15881],[74.90564,24.10837],[74.93971,24.05336],[74.98632,24.03274],[74.97276,24.014],[74.9586,23.97096],[74.9398,23.95856],[74.90169,23.86731],[74.91414,23.83756],[74.90341,23.79508],[74.91971,23.79602],[74.93465,23.73074],[74.9071,23.67636],[74.92727,23.64059],[74.8132,23.53046],[74.78702,23.54321],[74.72737,23.49733],[74.6109,23.45521],[74.51253,23.32475],[74.53948,23.29102],[74.68626,23.27115],[74.75097,23.20711],[74.66548,23.18486],[74.54051,23.1031],[74.47288,23.08289],[74.39769,23.11004],[74.3201,23.0573],[74.38259,22.90021],[74.44507,22.92266],[74.46945,22.86605],[74.37675,22.6346],[74.26929,22.64633],[74.18346,22.55187],[73.98296,22.51477]]]}},{"type":"Feature","properties":{"id":"IN-RJ"},"geometry":{"type":"Polygon","coordinates":[[[69.50904,26.74892],[69.88555,26.56836],[70.05584,26.60398],[70.17532,26.55362],[70.17532,26.24118],[70.08193,26.08094],[70.0985,25.93238],[70.2687,25.71156],[70.37444,25.67443],[70.53649,25.68928],[70.60378,25.71898],[70.67382,25.68186],[70.66695,25.39314],[70.89148,25.15064],[70.94002,24.92843],[71.09405,24.69017],[71.30126,24.62766],[71.57901,24.67946],[71.65626,24.6442],[71.79805,24.67197],[71.83341,24.62173],[71.87461,24.66605],[71.9522,24.63921],[72.04593,24.70317],[72.14618,24.63359],[72.27493,24.62751],[72.24952,24.54493],[72.35303,24.56304],[72.46925,24.41292],[72.53877,24.51589],[72.59954,24.46886],[72.68417,24.46418],[72.70992,24.36836],[72.96157,24.35241],[72.97874,24.46933],[73.09169,24.49542],[73.07916,24.39916],[73.131,24.34725],[73.21134,24.37383],[73.0632,24.18966],[73.2431,24.00319],[73.37116,24.11761],[73.42231,23.92601],[73.34094,23.78063],[73.48136,23.72501],[73.51089,23.61401],[73.63655,23.65678],[73.63088,23.45348],[73.72049,23.41394],[73.82984,23.44104],[73.90657,23.31624],[73.96064,23.37913],[73.98502,23.33658],[74.02381,23.33232],[74.04287,23.29559],[74.13351,23.26752],[74.11634,23.18376],[74.1469,23.14967],[74.24594,23.18107],[74.28216,23.09252],[74.3201,23.0573],[74.39769,23.11004],[74.47288,23.08289],[74.54051,23.1031],[74.66548,23.18486],[74.75097,23.20711],[74.68626,23.27115],[74.53948,23.29102],[74.51253,23.32475],[74.6109,23.45521],[74.72737,23.49733],[74.78702,23.54321],[74.8132,23.53046],[74.92727,23.64059],[74.9071,23.67636],[74.93465,23.73074],[74.91971,23.79602],[74.90341,23.79508],[74.91414,23.83756],[74.90169,23.86731],[74.9398,23.95856],[74.9586,23.97096],[74.97276,24.014],[74.98632,24.03274],[74.93971,24.05336],[74.90564,24.10837],[74.89362,24.15881],[74.88006,24.1751],[74.91027,24.20595],[74.87337,24.2432],[74.74771,24.23992],[74.7793,24.29093],[74.78994,24.35773],[74.85671,24.42495],[74.85208,24.44511],[74.8138,24.43746],[74.81775,24.46652],[74.74539,24.4684],[74.71097,24.49542],[74.74651,24.52432],[74.81088,24.66371],[74.77312,24.66012],[74.80195,24.79281],[74.88641,24.635],[75.03318,24.75431],[74.87731,24.78829],[74.82376,24.90916],[74.83843,24.97384],[74.97585,24.85809],[75.03816,24.84469],[75.07129,24.87537],[75.11884,24.8816],[75.11335,24.96442],[75.16656,25.00457],[75.15129,25.04532],[75.3535,25.03723],[75.30956,24.99383],[75.33462,24.95119],[75.32123,24.89827],[75.27282,24.96302],[75.28947,24.91041],[75.26149,24.87242],[75.31007,24.85902],[75.42629,24.8855],[75.41925,24.85404],[75.31831,24.80901],[75.23815,24.85482],[75.25497,24.81696],[75.16965,24.75213],[75.44311,24.68227],[75.57271,24.72282],[75.61374,24.67806],[75.78489,24.76491],[75.84772,24.6492],[75.82729,24.60769],[75.87776,24.58428],[75.91999,24.52963],[75.88952,24.51471],[75.91054,24.48121],[75.90145,24.44418],[75.8363,24.41393],[75.79433,24.47261],[75.72524,24.39087],[75.76772,24.29829],[75.81012,24.29797],[75.81922,24.23272],[75.77545,24.20688],[75.75038,24.14189],[75.8472,24.07107],[75.78077,24.04035],[75.76961,24.00115],[75.71313,23.96303],[75.67314,24.0151],[75.62404,23.99346],[75.5861,23.9908],[75.51881,24.02357],[75.47058,23.97574],[75.4589,23.92036],[75.52499,23.88097],[75.58267,23.8341],[75.56061,23.81966],[75.59452,23.79673],[75.65202,23.79743],[75.68824,23.75408],[75.7018,23.78063],[75.69271,23.81251],[75.72961,23.82319],[75.69717,23.90012],[75.74232,23.89588],[75.77579,23.84808],[75.84943,23.88395],[75.88514,23.88097],[75.98865,23.92946],[75.96307,24.01855],[76.03981,24.0637],[76.12615,24.08737],[76.13954,24.27372],[76.15859,24.28483],[76.18846,24.33364],[76.21215,24.21283],[76.28271,24.20884],[76.31893,24.24485],[76.41763,24.2061],[76.41892,24.22011],[76.47557,24.21737],[76.50664,24.19913],[76.5135,24.15748],[76.57736,24.17627],[76.56869,24.20634],[76.5911,24.24422],[76.6171,24.25792],[76.66886,24.26402],[76.69195,24.28248],[76.70156,24.24336],[76.67495,24.19154],[76.7134,24.16171],[76.75718,24.16774],[76.80129,24.11902],[76.90292,24.12936],[76.91639,24.18747],[76.95407,24.19279],[76.92343,24.21949],[76.86344,24.27466],[76.83511,24.35773],[76.85091,24.46652],[76.81477,24.53314],[76.8552,24.54876],[76.88266,24.52299],[76.91648,24.54478],[76.91631,24.48144],[76.97673,24.45785],[77.05604,24.52783],[77.07046,24.63406],[77.03012,24.71299],[76.95304,24.75805],[76.85065,24.74612],[76.84043,24.77496],[76.79271,24.82304],[76.90489,24.84204],[76.94257,24.85918],[76.91245,24.88838],[76.92008,24.91446],[76.86498,24.961],[76.87837,25.04112],[77.00368,25.07471],[77.08505,25.05776],[77.14616,25.09189],[77.15269,25.08606],[77.18316,25.10915],[77.27053,25.11482],[77.31044,25.07938],[77.39559,25.11979],[77.41069,25.22109],[77.36057,25.26487],[77.35645,25.42932],[77.27508,25.42746],[77.20315,25.30616],[77.08436,25.33619],[76.93691,25.28071],[76.86112,25.33192],[76.72267,25.33518],[76.59942,25.39304],[76.58792,25.42932],[76.56406,25.45009],[76.53968,25.50216],[76.51891,25.535],[76.49591,25.66303],[76.47291,25.70604],[76.52269,25.73573],[76.5172,25.80066],[76.59187,25.87745],[76.62105,25.86864],[76.6377,25.9115],[76.72491,25.90138],[76.79408,25.94353],[76.80662,25.9906],[76.91493,26.09533],[77.09312,26.18409],[77.12711,26.22929],[77.2077,26.23153],[77.2925,26.2861],[77.35173,26.35926],[77.44305,26.36364],[77.4367,26.40217],[77.53807,26.40985],[77.55892,26.43522],[77.62158,26.45512],[77.6772,26.50322],[77.72518,26.49845],[77.75419,26.5443],[77.82474,26.54784],[77.83435,26.60556],[77.89512,26.61277],[77.9007,26.6572],[77.96267,26.65904],[77.99941,26.69462],[78.09047,26.66947],[78.09408,26.73434],[78.12154,26.74653],[78.09554,26.78791],[78.18883,26.78086],[78.16806,26.82353],[78.21029,26.82407],[78.19793,26.87981],[78.26831,26.87813],[78.26333,26.93507],[78.13459,26.95099],[78.08275,26.90431],[78.0503,26.90706],[78.04275,26.87001],[78.0182,26.90201],[77.98181,26.89321],[77.90594,26.91824],[77.90662,26.88594],[77.74646,26.94089],[77.72397,26.87874],[77.56261,26.8184],[77.50562,26.83173],[77.48348,26.74331],[77.40211,26.82744],[77.48142,26.89574],[77.60536,26.93523],[77.6548,26.96996],[77.68072,26.96598],[77.7705,27.00147],[77.74595,27.03007],[77.719,26.99689],[77.68672,27.04521],[77.66183,27.01814],[77.56467,27.03068],[77.57137,27.07257],[77.52107,27.06493],[77.52511,27.10131],[77.60604,27.11972],[77.61754,27.17028],[77.6651,27.16974],[77.68123,27.19395],[77.64759,27.21212],[77.65557,27.24112],[77.61712,27.25836],[77.61351,27.29086],[77.62201,27.33761],[77.58253,27.32205],[77.49464,27.38198],[77.44056,27.39089],[77.42202,27.42442],[77.43679,27.45679],[77.40005,27.47804],[77.39911,27.50225],[77.34023,27.52448],[77.34829,27.54381],[77.33645,27.57151],[77.35851,27.58817],[77.32031,27.59517],[77.35971,27.65525],[77.33448,27.70632],[77.29963,27.70632],[77.31388,27.76922],[77.28092,27.81114],[77.23783,27.7851],[77.20084,27.82222],[77.15732,27.81524],[77.15723,27.78585],[77.10917,27.78699],[77.04608,27.82511],[77.03201,27.79413],[77.08694,27.73884],[77.00677,27.7554],[76.97948,27.65464],[76.8885,27.69705],[76.92008,27.70085],[76.90566,27.7753],[76.94068,27.8588],[76.93433,27.99803],[76.97055,28.13769],[76.88816,28.18521],[76.90566,28.20049],[76.87408,28.207],[76.87408,28.22061],[76.83949,28.22001],[76.83151,28.20889],[76.80327,28.20866],[76.81237,28.1893],[76.79271,28.18733],[76.80087,28.14889],[76.78456,28.15026],[76.74156,28.1221],[76.71358,28.12604],[76.68096,28.08742],[76.64611,28.08864],[76.66757,28.01289],[76.59667,27.99849],[76.539,27.97333],[76.54861,28.04175],[76.50252,28.03054],[76.45849,28.04819],[76.50054,28.0594],[76.45608,28.09696],[76.49969,28.09999],[76.50072,28.11907],[76.47188,28.1501],[76.43445,28.1439],[76.44184,28.1165],[76.4281,28.10825],[76.42132,28.14125],[76.36768,28.13527],[76.3603,28.16085],[76.31017,28.17886],[76.27584,28.17144],[76.35755,28.12233],[76.29919,28.09984],[76.35841,28.07016],[76.34073,28.02637],[76.30931,28.0188],[76.26897,28.07198],[76.23464,28.04956],[76.1634,28.05501],[76.20614,28.02486],[76.17267,28.01849],[76.15602,27.98166],[76.18246,27.96271],[76.16769,27.90463],[76.2276,27.82268],[76.17954,27.79641],[76.10881,27.85774],[76.04564,27.83619],[75.97131,27.84393],[75.98693,27.90341],[75.92205,27.93542],[75.9811,27.9477],[75.96376,27.99697],[75.99809,27.99652],[75.96719,28.02819],[76.01509,28.03365],[76.04375,28.08167],[75.93835,28.08833],[75.99843,28.12558],[76.0338,28.16766],[76.06727,28.16494],[76.07723,28.12997],[76.0968,28.14587],[75.91415,28.36919],[75.76704,28.41344],[75.79055,28.43714],[75.6594,28.53808],[75.62095,28.60049],[75.56156,28.61421],[75.53581,28.74734],[75.50817,28.78812],[75.51349,28.83505],[75.47538,28.92974],[75.52877,28.96684],[75.51933,29.00573],[75.4486,29.00949],[75.44345,29.05752],[75.39393,29.06142],[75.37445,29.14121],[75.41273,29.18214],[75.40406,29.26491],[75.33651,29.22918],[75.33514,29.29163],[75.22768,29.23008],[75.1863,29.26798],[75.11824,29.22199],[75.07867,29.22911],[75.05207,29.28774],[74.97138,29.27681],[74.92332,29.37813],[74.84384,29.40176],[74.75904,29.35584],[74.66051,29.3717],[74.65106,29.3316],[74.60523,29.32726],[74.56609,29.42165],[74.53777,29.42808],[74.54807,29.46605],[74.58309,29.46067],[74.62188,29.52985],[74.57244,29.56449],[74.61158,29.76199],[74.48404,29.74142],[74.47528,29.79849],[74.57399,29.87964],[74.5182,29.90926],[74.52094,29.94303],[73.89507,29.97397],[73.88545,30.01069],[73.90528,30.04918],[73.97034,30.12159],[73.97225,30.19829],[73.80299,30.06969],[73.58665,30.01848],[73.3962,29.94707],[73.28094,29.56646],[73.08654,29.23877],[73.01337,29.16422],[72.94272,29.02487],[72.40402,28.78283],[72.29495,28.66367],[72.20329,28.3869],[71.9244,28.11555],[71.89921,27.96035],[70.79054,27.68423],[70.60927,28.02178],[70.37307,28.01208],[70.12502,27.8057],[70.03136,27.56627],[69.58519,27.18109],[69.50904,26.74892]]]}},{"type":"Feature","properties":{"id":"IN-DL"},"geometry":{"type":"Polygon","coordinates":[[[76.83915,28.58301],[76.84584,28.55037],[76.86249,28.54487],[76.87391,28.5282],[76.88738,28.51998],[76.88043,28.50543],[76.89116,28.50037],[76.90262,28.51172],[76.95334,28.50509],[76.97845,28.5213],[76.99111,28.51365],[76.9969,28.51949],[77.00982,28.51466],[77.01703,28.52104],[77.01476,28.52398],[77.00098,28.53114],[77.00544,28.53947],[77.0139,28.54068],[77.02424,28.5328],[77.03209,28.53118],[77.04344,28.52513],[77.0434,28.52409],[77.04862,28.52107],[77.0463,28.5167],[77.06703,28.51199],[77.07153,28.51787],[77.07235,28.52025],[77.07505,28.51877],[77.08003,28.51815],[77.09265,28.51434],[77.09863,28.51146],[77.09536,28.50677],[77.11973,28.4952],[77.11277,28.4731],[77.13013,28.44042],[77.14651,28.43692],[77.16161,28.42922],[77.17316,28.4037],[77.17818,28.40921],[77.22058,28.41344],[77.24169,28.42763],[77.24628,28.4496],[77.23237,28.45963],[77.23483,28.46982],[77.27527,28.49358],[77.2895,28.49708],[77.30053,28.49419],[77.30063,28.49059],[77.30891,28.4886],[77.31464,28.48396],[77.32623,28.49037],[77.34632,28.51782],[77.29886,28.55723],[77.29293,28.57634],[77.29916,28.58772],[77.30422,28.58587],[77.31049,28.59085],[77.31332,28.59661],[77.3246,28.59789],[77.33066,28.60098],[77.33645,28.60174],[77.3419,28.60524],[77.34087,28.62299],[77.31594,28.64152],[77.31988,28.65188],[77.32027,28.66234],[77.32551,28.67827],[77.32997,28.67861],[77.33345,28.68147],[77.32409,28.69864],[77.33207,28.71317],[77.3134,28.71366],[77.29971,28.71008],[77.29628,28.70526],[77.29036,28.70602],[77.2865,28.7143],[77.29083,28.72273],[77.28688,28.7248],[77.27667,28.73564],[77.26057,28.73564],[77.25547,28.73861],[77.25658,28.7444],[77.26006,28.75039],[77.25512,28.7558],[77.24886,28.75524],[77.23839,28.75908],[77.20659,28.78451],[77.20418,28.80527],[77.20985,28.81429],[77.2229,28.82091],[77.21157,28.8573],[77.17457,28.85865],[77.15681,28.8367],[77.14282,28.83858],[77.14616,28.85572],[77.13406,28.86301],[77.12265,28.8573],[77.11063,28.86918],[77.0926,28.87143],[77.08333,28.88451],[77.0757,28.86895],[77.05759,28.86933],[77.04119,28.83324],[76.99622,28.84144],[76.98077,28.82113],[76.96841,28.82895],[76.96146,28.81474],[76.95116,28.81798],[76.94292,28.79955],[76.95433,28.79045],[76.94807,28.78097],[76.95579,28.76841],[76.94403,28.75419],[76.95811,28.7432],[76.96068,28.73161],[76.94832,28.71264],[76.96781,28.69917],[76.95776,28.68448],[76.95502,28.66988],[76.93759,28.67093],[76.92378,28.64999],[76.94523,28.63146],[76.9345,28.61798],[76.91657,28.63417],[76.90738,28.62393],[76.8903,28.63274],[76.86429,28.58602],[76.83915,28.58301]]]}},{"type":"Feature","properties":{"id":"IN-CH"},"geometry":{"type":"Polygon","coordinates":[[[76.70667,30.75964],[76.70744,30.74264],[76.71315,30.74095],[76.73096,30.72055],[76.72993,30.70708],[76.73911,30.70538],[76.73645,30.69272],[76.74452,30.69357],[76.7513,30.68512],[76.7625,30.68586],[76.77739,30.67058],[76.77958,30.67575],[76.80524,30.66478],[76.82692,30.68235],[76.83013,30.68169],[76.82687,30.69409],[76.84859,30.71431],[76.84747,30.73054],[76.83228,30.7384],[76.84013,30.76012],[76.82524,30.76304],[76.81293,30.75872],[76.80417,30.7654],[76.82048,30.77185],[76.81404,30.78052],[76.79434,30.76975],[76.7834,30.77078],[76.77962,30.77812],[76.78615,30.77904],[76.7825,30.7877],[76.77117,30.79574],[76.75186,30.78623],[76.75216,30.78306],[76.73585,30.76684],[76.70667,30.75964]]]}},{"type":"Feature","properties":{"id":"IN-HR"},"geometry":{"type":"Polygon","coordinates":[[[74.47528,29.79849],[74.48404,29.74142],[74.61158,29.76199],[74.57244,29.56449],[74.62188,29.52985],[74.58309,29.46067],[74.54807,29.46605],[74.53777,29.42808],[74.56609,29.42165],[74.60523,29.32726],[74.65106,29.3316],[74.66051,29.3717],[74.75904,29.35584],[74.84384,29.40176],[74.92332,29.37813],[74.97138,29.27681],[75.05207,29.28774],[75.07867,29.22911],[75.11824,29.22199],[75.1863,29.26798],[75.22768,29.23008],[75.33514,29.29163],[75.33651,29.22918],[75.40406,29.26491],[75.41273,29.18214],[75.37445,29.14121],[75.39393,29.06142],[75.44345,29.05752],[75.4486,29.00949],[75.51933,29.00573],[75.52877,28.96684],[75.47538,28.92974],[75.51349,28.83505],[75.50817,28.78812],[75.53581,28.74734],[75.56156,28.61421],[75.62095,28.60049],[75.6594,28.53808],[75.79055,28.43714],[75.76704,28.41344],[75.91415,28.36919],[76.0968,28.14587],[76.07723,28.12997],[76.06727,28.16494],[76.0338,28.16766],[75.99843,28.12558],[75.93835,28.08833],[76.04375,28.08167],[76.01509,28.03365],[75.96719,28.02819],[75.99809,27.99652],[75.96376,27.99697],[75.9811,27.9477],[75.92205,27.93542],[75.98693,27.90341],[75.97131,27.84393],[76.04564,27.83619],[76.10881,27.85774],[76.17954,27.79641],[76.2276,27.82268],[76.16769,27.90463],[76.18246,27.96271],[76.15602,27.98166],[76.17267,28.01849],[76.20614,28.02486],[76.1634,28.05501],[76.23464,28.04956],[76.26897,28.07198],[76.30931,28.0188],[76.34073,28.02637],[76.35841,28.07016],[76.29919,28.09984],[76.35755,28.12233],[76.27584,28.17144],[76.31017,28.17886],[76.3603,28.16085],[76.36768,28.13527],[76.42132,28.14125],[76.4281,28.10825],[76.44184,28.1165],[76.43445,28.1439],[76.47188,28.1501],[76.50072,28.11907],[76.49969,28.09999],[76.45608,28.09696],[76.50054,28.0594],[76.45849,28.04819],[76.50252,28.03054],[76.54861,28.04175],[76.539,27.97333],[76.59667,27.99849],[76.66757,28.01289],[76.64611,28.08864],[76.68096,28.08742],[76.71358,28.12604],[76.74156,28.1221],[76.78456,28.15026],[76.80087,28.14889],[76.79271,28.18733],[76.81237,28.1893],[76.80327,28.20866],[76.83151,28.20889],[76.83949,28.22001],[76.87408,28.22061],[76.87408,28.207],[76.90566,28.20049],[76.88816,28.18521],[76.97055,28.13769],[76.93433,27.99803],[76.94068,27.8588],[76.90566,27.7753],[76.92008,27.70085],[76.8885,27.69705],[76.97948,27.65464],[77.00677,27.7554],[77.08694,27.73884],[77.03201,27.79413],[77.04608,27.82511],[77.10917,27.78699],[77.15723,27.78585],[77.15732,27.81524],[77.20084,27.82222],[77.23783,27.7851],[77.28092,27.81114],[77.34649,27.85395],[77.41928,27.8541],[77.41567,27.88339],[77.46253,27.88081],[77.48416,27.93284],[77.54116,27.93147],[77.53635,27.99167],[77.47283,28.08409],[77.54699,28.17613],[77.49961,28.20216],[77.54613,28.24012],[77.47781,28.27928],[77.48828,28.35515],[77.4979,28.40891],[77.46811,28.3997],[77.47361,28.41918],[77.45704,28.43586],[77.43086,28.4259],[77.41584,28.44039],[77.42477,28.46122],[77.41447,28.47465],[77.37902,28.46416],[77.34632,28.51782],[77.32623,28.49037],[77.31464,28.48396],[77.30891,28.4886],[77.30063,28.49059],[77.30053,28.49419],[77.2895,28.49708],[77.27527,28.49358],[77.23483,28.46982],[77.23237,28.45963],[77.24628,28.4496],[77.24169,28.42763],[77.22058,28.41344],[77.17818,28.40921],[77.17316,28.4037],[77.16161,28.42922],[77.14651,28.43692],[77.13013,28.44042],[77.11277,28.4731],[77.11973,28.4952],[77.09536,28.50677],[77.09863,28.51146],[77.09265,28.51434],[77.08003,28.51815],[77.07505,28.51877],[77.07235,28.52025],[77.07153,28.51787],[77.06703,28.51199],[77.0463,28.5167],[77.04862,28.52107],[77.0434,28.52409],[77.04344,28.52513],[77.03209,28.53118],[77.02424,28.5328],[77.0139,28.54068],[77.00544,28.53947],[77.00098,28.53114],[77.01476,28.52398],[77.01703,28.52104],[77.00982,28.51466],[76.9969,28.51949],[76.99111,28.51365],[76.97845,28.5213],[76.95334,28.50509],[76.90262,28.51172],[76.89116,28.50037],[76.88043,28.50543],[76.88738,28.51998],[76.87391,28.5282],[76.86249,28.54487],[76.84584,28.55037],[76.83915,28.58301],[76.86429,28.58602],[76.8903,28.63274],[76.90738,28.62393],[76.91657,28.63417],[76.9345,28.61798],[76.94523,28.63146],[76.92378,28.64999],[76.93759,28.67093],[76.95502,28.66988],[76.95776,28.68448],[76.96781,28.69917],[76.94832,28.71264],[76.96068,28.73161],[76.95811,28.7432],[76.94403,28.75419],[76.95579,28.76841],[76.94807,28.78097],[76.95433,28.79045],[76.94292,28.79955],[76.95116,28.81798],[76.96146,28.81474],[76.96841,28.82895],[76.98077,28.82113],[76.99622,28.84144],[77.04119,28.83324],[77.05759,28.86933],[77.0757,28.86895],[77.08333,28.88451],[77.0926,28.87143],[77.11063,28.86918],[77.12265,28.8573],[77.13406,28.86301],[77.14616,28.85572],[77.14282,28.83858],[77.15681,28.8367],[77.17457,28.85865],[77.21157,28.8573],[77.22753,28.89368],[77.14187,29.09577],[77.13363,29.47696],[77.09896,29.51431],[77.10754,29.62077],[77.14839,29.6876],[77.13174,29.74723],[77.19509,29.89944],[77.27577,29.99463],[77.28898,30.04606],[77.42048,30.10147],[77.42271,30.15848],[77.46425,30.17095],[77.58613,30.31006],[77.57549,30.39804],[77.21242,30.46761],[77.19594,30.5216],[77.10754,30.56048],[77.18324,30.60615],[77.14359,30.72029],[77.00643,30.74449],[77.01553,30.80643],[76.95141,30.81999],[76.91399,30.8978],[76.85786,30.86554],[76.82189,30.92777],[76.77263,30.90097],[76.84782,30.83282],[76.84013,30.76012],[76.83228,30.7384],[76.84747,30.73054],[76.84859,30.71431],[76.82687,30.69409],[76.83013,30.68169],[76.83194,30.66298],[76.8458,30.66958],[76.84627,30.65703],[76.85897,30.66058],[76.87631,30.68165],[76.87082,30.64352],[76.88532,30.64433],[76.92017,30.62218],[76.91562,30.55272],[76.88773,30.54548],[76.94395,30.5182],[76.91064,30.48329],[76.90223,30.4389],[76.93897,30.39049],[76.90781,30.35295],[76.85803,30.43387],[76.82198,30.41285],[76.77735,30.42336],[76.78464,30.43705],[76.74937,30.42943],[76.71692,30.39605],[76.72327,30.36524],[76.74242,30.37043],[76.75426,30.35717],[76.66482,30.30516],[76.62217,30.26796],[76.55994,30.26292],[76.55067,30.22265],[76.58929,30.25372],[76.6504,30.20344],[76.64972,30.17674],[76.62208,30.20374],[76.59856,30.19246],[76.64216,30.15121],[76.52904,30.06939],[76.43823,30.14572],[76.43102,30.18846],[76.3948,30.1803],[76.396,30.10949],[76.33111,30.14245],[76.36493,30.09508],[76.2749,30.11498],[76.20906,30.157],[76.20632,30.14661],[76.25112,30.09018],[76.21438,29.99642],[76.18563,29.94824],[76.18597,29.88917],[76.24237,29.88255],[76.2446,29.85344],[76.19902,29.84257],[76.18469,29.82292],[76.14469,29.8183],[76.12667,29.79581],[76.09697,29.81026],[76.04633,29.74798],[75.97715,29.72845],[75.86814,29.74992],[75.83295,29.81272],[75.71597,29.81175],[75.71064,29.7494],[75.67434,29.77376],[75.58473,29.74604],[75.46766,29.80214],[75.4056,29.7602],[75.39256,29.71794],[75.35488,29.69901],[75.35419,29.67888],[75.29196,29.61017],[75.32037,29.57995],[75.25265,29.54441],[75.2245,29.57868],[75.23446,29.60204],[75.16768,29.66911],[75.20484,29.68342],[75.19609,29.73158],[75.25033,29.7365],[75.19111,29.84183],[75.15764,29.77555],[75.09756,29.80892],[75.10923,29.91982],[75.06992,29.87994],[75.00125,29.86342],[74.90032,29.95597],[74.81483,29.99136],[74.7163,29.96489],[74.70479,29.97575],[74.63115,29.90494],[74.52094,29.94303],[74.5182,29.90926],[74.57399,29.87964],[74.47528,29.79849]]]}},{"type":"Feature","properties":{"id":"IN-HP"},"geometry":{"type":"Polygon","coordinates":[[[75.58284,32.07384],[75.69648,32.0641],[75.89836,31.95944],[75.98384,31.81573],[75.93681,31.81222],[76.16546,31.39526],[76.1834,31.2985],[76.31283,31.3147],[76.30777,31.34264],[76.34674,31.3455],[76.34038,31.37826],[76.32931,31.37899],[76.33107,31.40064],[76.37077,31.40331],[76.37386,31.43715],[76.45917,31.28764],[76.58277,31.2696],[76.64714,31.20986],[76.58655,31.16052],[76.63667,31.11357],[76.61367,31.04778],[76.62414,30.99938],[76.73091,30.95184],[76.77263,30.90097],[76.82189,30.92777],[76.85786,30.86554],[76.91399,30.8978],[76.95141,30.81999],[77.01553,30.80643],[77.00643,30.74449],[77.14359,30.72029],[77.18324,30.60615],[77.10754,30.56048],[77.19594,30.5216],[77.21242,30.46761],[77.57549,30.39804],[77.58132,30.42366],[77.65583,30.4355],[77.76723,30.49468],[77.81633,30.50637],[77.82371,30.55161],[77.78174,30.55494],[77.7408,30.60127],[77.79333,30.61501],[77.78646,30.66065],[77.74543,30.68191],[77.75238,30.71188],[77.72097,30.74227],[77.7038,30.74412],[77.7305,30.7911],[77.74045,30.81071],[77.76131,30.82567],[77.7444,30.85021],[77.75453,30.87902],[77.80517,30.85036],[77.81204,30.92564],[77.76088,30.91916],[77.75333,30.96966],[77.83787,30.95236],[77.8147,30.96333],[77.82491,31.00865],[77.79573,31.02234],[77.8298,31.03072],[77.81324,31.05778],[77.95675,31.15949],[78.03915,31.15934],[78.32239,31.27855],[78.99581,31.10821],[78.77898,31.31209],[78.71032,31.50197],[78.84516,31.60631],[78.69933,31.78723],[78.78036,31.99478],[78.74404,32.00384],[78.68754,32.10256],[78.49609,32.2762],[78.4645,32.45367],[78.38897,32.53938],[78.32565,32.75263],[77.97477,32.58905],[77.88345,32.7942],[77.66784,32.97007],[77.32795,32.82305],[76.76902,33.26337],[76.31584,33.1341],[75.95329,32.88362],[75.77888,32.9355],[75.93132,32.64428],[75.83845,32.50831],[75.93852,32.39619],[75.85338,32.38402],[75.77579,32.29177],[75.65219,32.25142],[75.63812,32.25142],[75.61692,32.23647],[75.63992,32.22775],[75.62164,32.18919],[75.65546,32.19653],[75.67108,32.14727],[75.58284,32.07384]]]}},{"type":"Feature","properties":{"id":"IN-PB"},"geometry":{"type":"Polygon","coordinates":[[[73.88545,30.01069],[73.89507,29.97397],[74.52094,29.94303],[74.63115,29.90494],[74.70479,29.97575],[74.7163,29.96489],[74.81483,29.99136],[74.90032,29.95597],[75.00125,29.86342],[75.06992,29.87994],[75.10923,29.91982],[75.09756,29.80892],[75.15764,29.77555],[75.19111,29.84183],[75.25033,29.7365],[75.19609,29.73158],[75.20484,29.68342],[75.16768,29.66911],[75.23446,29.60204],[75.2245,29.57868],[75.25265,29.54441],[75.32037,29.57995],[75.29196,29.61017],[75.35419,29.67888],[75.35488,29.69901],[75.39256,29.71794],[75.4056,29.7602],[75.46766,29.80214],[75.58473,29.74604],[75.67434,29.77376],[75.71064,29.7494],[75.71597,29.81175],[75.83295,29.81272],[75.86814,29.74992],[75.97715,29.72845],[76.04633,29.74798],[76.09697,29.81026],[76.12667,29.79581],[76.14469,29.8183],[76.18469,29.82292],[76.19902,29.84257],[76.2446,29.85344],[76.24237,29.88255],[76.18597,29.88917],[76.18563,29.94824],[76.21438,29.99642],[76.25112,30.09018],[76.20632,30.14661],[76.20906,30.157],[76.2749,30.11498],[76.36493,30.09508],[76.33111,30.14245],[76.396,30.10949],[76.3948,30.1803],[76.43102,30.18846],[76.43823,30.14572],[76.52904,30.06939],[76.64216,30.15121],[76.59856,30.19246],[76.62208,30.20374],[76.64972,30.17674],[76.6504,30.20344],[76.58929,30.25372],[76.55067,30.22265],[76.55994,30.26292],[76.62217,30.26796],[76.66482,30.30516],[76.75426,30.35717],[76.74242,30.37043],[76.72327,30.36524],[76.71692,30.39605],[76.74937,30.42943],[76.78464,30.43705],[76.77735,30.42336],[76.82198,30.41285],[76.85803,30.43387],[76.90781,30.35295],[76.93897,30.39049],[76.90223,30.4389],[76.91064,30.48329],[76.94395,30.5182],[76.88773,30.54548],[76.91562,30.55272],[76.92017,30.62218],[76.88532,30.64433],[76.87082,30.64352],[76.87631,30.68165],[76.85897,30.66058],[76.84627,30.65703],[76.8458,30.66958],[76.83194,30.66298],[76.83013,30.68169],[76.82692,30.68235],[76.80524,30.66478],[76.77958,30.67575],[76.77739,30.67058],[76.7625,30.68586],[76.7513,30.68512],[76.74452,30.69357],[76.73645,30.69272],[76.73911,30.70538],[76.72993,30.70708],[76.73096,30.72055],[76.71315,30.74095],[76.70744,30.74264],[76.70667,30.75964],[76.73585,30.76684],[76.75216,30.78306],[76.75186,30.78623],[76.77117,30.79574],[76.7825,30.7877],[76.78615,30.77904],[76.77962,30.77812],[76.7834,30.77078],[76.79434,30.76975],[76.81404,30.78052],[76.82048,30.77185],[76.80417,30.7654],[76.81293,30.75872],[76.82524,30.76304],[76.84013,30.76012],[76.84782,30.83282],[76.77263,30.90097],[76.73091,30.95184],[76.62414,30.99938],[76.61367,31.04778],[76.63667,31.11357],[76.58655,31.16052],[76.64714,31.20986],[76.58277,31.2696],[76.45917,31.28764],[76.37386,31.43715],[76.37077,31.40331],[76.33107,31.40064],[76.32931,31.37899],[76.34038,31.37826],[76.34674,31.3455],[76.30777,31.34264],[76.31283,31.3147],[76.1834,31.2985],[76.16546,31.39526],[75.93681,31.81222],[75.98384,31.81573],[75.89836,31.95944],[75.69648,32.0641],[75.58284,32.07384],[75.67108,32.14727],[75.65546,32.19653],[75.62164,32.18919],[75.63992,32.22775],[75.61692,32.23647],[75.63812,32.25142],[75.65219,32.25142],[75.77579,32.29177],[75.85338,32.38402],[75.93852,32.39619],[75.83845,32.50831],[75.78506,32.47095],[75.72875,32.4527],[75.74129,32.43126],[75.6867,32.3917],[75.57855,32.37474],[75.54027,32.33762],[75.53512,32.27711],[75.50954,32.28256],[75.48654,32.31927],[75.49289,32.34733],[75.46285,32.32753],[75.43032,32.31593],[75.34475,32.35103],[75.33265,32.32703],[75.32226,32.29946],[75.37719,32.2716],[75.36672,32.22325],[75.32432,32.21527],[75.25763,32.09951],[74.99516,32.04147],[74.93079,32.06657],[74.8774,32.04991],[74.81595,31.96439],[74.60866,31.88776],[74.55253,31.76655],[74.48884,31.71809],[74.57498,31.60382],[74.62248,31.54686],[74.58626,31.51175],[74.65355,31.45685],[74.64574,31.41796],[74.60334,31.42507],[74.53223,31.30321],[74.51013,31.13848],[74.56023,31.08303],[74.60952,31.09586],[74.60008,31.13334],[74.69836,31.12467],[74.68497,31.05594],[74.55948,31.04359],[74.53957,30.98997],[74.41829,30.93594],[74.37074,30.85493],[74.32371,30.84756],[74.27959,30.74006],[74.23427,30.72294],[74.10793,30.61147],[74.09694,30.56684],[74.07154,30.52426],[74.01849,30.52441],[73.95566,30.46938],[73.93232,30.49483],[73.93953,30.40796],[73.88993,30.36305],[73.95736,30.28466],[73.97225,30.19829],[73.97034,30.12159],[73.90528,30.04918],[73.88545,30.01069]]]}},{"type":"Feature","properties":{"id":"IN-AR"},"geometry":{"type":"Polygon","coordinates":[[[91.55819,27.6144],[91.65007,27.48287],[92.01132,27.47352],[92.11512,27.27667],[92.04702,27.26861],[92.03457,27.07334],[92.10834,26.98082],[92.11469,26.89405],[92.6441,26.98495],[92.63757,27.03496],[92.87223,27.00652],[93.02089,26.91656],[93.38035,26.96308],[93.49227,26.94043],[93.66462,26.96858],[93.83457,27.07532],[93.82392,27.12025],[93.80272,27.12888],[93.80702,27.15019],[94.15729,27.47888],[94.26441,27.52532],[94.23059,27.6338],[94.46027,27.55896],[94.66215,27.64582],[94.70815,27.65433],[94.92376,27.77803],[94.99843,27.77393],[95.30965,27.86897],[95.51891,27.88111],[95.60285,27.95452],[95.82395,27.97151],[96.00179,27.95847],[95.75297,27.72577],[95.87923,27.5378],[95.86189,27.43333],[95.95064,27.4315],[96.01312,27.37283],[95.89965,27.2766],[95.49041,27.2473],[95.45436,27.20517],[95.45745,27.14882],[95.19481,27.03619],[95.23618,26.68266],[95.30339,26.65372],[95.437,26.7083],[95.81603,27.01335],[95.93002,27.04149],[96.04949,27.19428],[96.15591,27.24572],[96.40779,27.29818],[96.55761,27.29928],[96.73888,27.36638],[96.88445,27.25046],[96.84531,27.18939],[97.14675,27.09041],[97.17422,27.14052],[96.91431,27.45752],[96.90696,27.61236],[97.29919,27.92233],[97.35824,27.87256],[97.38845,28.01329],[97.35412,28.06663],[97.31292,28.06784],[97.34547,28.21385],[97.1289,28.3619],[96.98882,28.32564],[96.88445,28.39452],[96.85561,28.4875],[96.6455,28.61657],[96.48895,28.42955],[96.40929,28.51526],[96.61391,28.72742],[96.3626,29.10607],[96.20467,29.02325],[96.18682,29.11087],[96.31316,29.18643],[96.05361,29.38167],[95.84899,29.31464],[95.75149,29.32063],[95.72086,29.20797],[95.50842,29.13487],[95.41091,29.13007],[95.3038,29.13847],[95.26122,29.07727],[95.2214,29.10727],[95.11291,29.09527],[95.0978,29.14446],[94.81353,29.17804],[94.69318,29.31739],[94.2752,29.11687],[94.35897,29.01965],[93.72797,28.68821],[93.62377,28.68426],[93.37623,28.53687],[93.19221,28.52903],[93.14635,28.37035],[92.93609,28.23181],[92.67486,28.15018],[92.65472,28.07632],[92.73025,28.05814],[92.7275,27.98662],[92.42538,27.80092],[92.32101,27.79363],[92.27432,27.89077],[91.87471,27.71605],[91.83437,27.78411],[91.73738,27.77332],[91.66975,27.82435],[91.61189,27.83786],[91.57842,27.82313],[91.64794,27.7756],[91.55819,27.6144]]]}},{"type":"Feature","properties":{"id":"IN-ML"},"geometry":{"type":"Polygon","coordinates":[[[89.81185,25.37078],[89.84086,25.31854],[89.83288,25.29553],[89.87837,25.2835],[89.90567,25.30864],[90.11724,25.22419],[90.31568,25.19049],[90.44151,25.14233],[90.50794,25.172],[90.63463,25.17418],[90.65926,25.18979],[90.68819,25.16424],[90.7378,25.15818],[90.78088,25.18102],[90.82071,25.16299],[90.91598,25.16144],[91.07082,25.1936],[91.1939,25.20214],[91.24746,25.19764],[91.27106,25.20789],[91.29261,25.18428],[91.46985,25.15251],[91.47156,25.13557],[91.55422,25.15126],[91.57748,25.17224],[91.61361,25.17643],[91.5979,25.1459],[91.62013,25.14489],[91.62692,25.12306],[91.69438,25.13432],[91.71275,25.16431],[91.74322,25.14722],[91.75334,25.17496],[91.7875,25.16532],[91.95565,25.18117],[91.97916,25.16866],[92.0044,25.18358],[92.03186,25.18342],[92.03538,25.18863],[92.10388,25.1776],[92.13027,25.16311],[92.1431,25.14396],[92.20653,25.13533],[92.22773,25.09803],[92.34652,25.0737],[92.34807,25.05224],[92.42832,25.0293],[92.79464,25.21612],[92.78125,25.32758],[92.56771,25.469],[92.64152,25.53624],[92.65697,25.58456],[92.5811,25.55854],[92.4036,25.75212],[92.15984,25.67123],[92.14851,25.8104],[92.22095,25.90771],[92.15263,25.94446],[92.2788,26.06804],[91.92672,26.00819],[91.87067,26.0412],[91.87823,26.09795],[91.82467,26.11791],[91.78879,26.08353],[91.24969,25.71887],[90.94242,25.94199],[90.61763,25.89984],[90.48065,26.0031],[90.12325,25.95804],[90.01974,25.8925],[89.89296,25.73581],[90.01922,25.60314],[89.88223,25.55142],[89.87194,25.47458],[89.85297,25.47078],[89.83958,25.44908],[89.81185,25.37078]]]}},{"type":"Feature","properties":{"id":"IN-AS"},"geometry":{"type":"Polygon","coordinates":[[[89.7068,26.18008],[89.73401,26.17677],[89.71993,26.16637],[89.72117,26.15674],[89.74499,26.15959],[89.76465,26.1334],[89.75555,26.10858],[89.78456,26.10519],[89.78198,26.08461],[89.79537,26.08916],[89.79555,26.06788],[89.77272,26.03704],[89.811,26.04336],[89.82722,25.97532],[89.83503,26.01197],[89.8752,25.97895],[89.82241,25.94507],[89.88996,25.9443],[89.83503,25.87111],[89.81683,25.81441],[89.86232,25.73465],[89.84704,25.69529],[89.86515,25.66357],[89.87151,25.66086],[89.88292,25.61807],[89.8655,25.54453],[89.85219,25.53942],[89.86138,25.51541],[89.85074,25.49248],[89.85297,25.47078],[89.87194,25.47458],[89.88223,25.55142],[90.01922,25.60314],[89.89296,25.73581],[90.01974,25.8925],[90.12325,25.95804],[90.48065,26.0031],[90.61763,25.89984],[90.94242,25.94199],[91.24969,25.71887],[91.78879,26.08353],[91.82467,26.11791],[91.87823,26.09795],[91.87067,26.0412],[91.92672,26.00819],[92.2788,26.06804],[92.15263,25.94446],[92.22095,25.90771],[92.14851,25.8104],[92.15984,25.67123],[92.4036,25.75212],[92.5811,25.55854],[92.65697,25.58456],[92.64152,25.53624],[92.56771,25.469],[92.78125,25.32758],[92.79464,25.21612],[92.42832,25.0293],[92.42197,24.99189],[92.4193,24.96543],[92.45887,24.97049],[92.47029,24.96186],[92.44823,24.93991],[92.48805,24.94909],[92.48599,24.92567],[92.49921,24.90558],[92.48308,24.8633],[92.44188,24.87693],[92.43827,24.85622],[92.38652,24.85147],[92.37356,24.8753],[92.36377,24.87195],[92.34592,24.89453],[92.34043,24.87833],[92.29871,24.91555],[92.27588,24.90558],[92.24018,24.90792],[92.24052,24.85933],[92.29511,24.75478],[92.21991,24.50307],[92.27725,24.38431],[92.20756,24.23882],[92.297,24.25244],[92.42265,24.25338],[92.44239,24.14299],[92.53183,24.17698],[92.55655,24.24915],[92.61114,24.25087],[92.62229,24.33145],[92.68615,24.35085],[92.76323,24.52213],[92.8137,24.3915],[93.02432,24.4062],[93.11119,24.79912],[93.10998,24.81229],[93.12088,24.81961],[93.1202,24.80886],[93.13625,24.80948],[93.15299,24.78821],[93.17015,24.80138],[93.19702,24.81166],[93.38516,25.2419],[93.47047,25.30492],[93.45211,25.43335],[93.39134,25.4628],[93.33074,25.54817],[93.54463,25.70766],[93.54343,25.72738],[93.63887,25.81318],[93.70325,25.8498],[93.69415,25.89783],[93.7023,25.92724],[93.75715,25.94646],[93.77449,25.97208],[93.79526,25.90462],[93.77603,25.84887],[93.78736,25.81202],[93.79637,25.80746],[93.83903,25.85814],[93.8792,25.84253],[93.9349,25.89428],[93.98005,25.91705],[93.99902,26.16468],[94.09687,26.31142],[94.17308,26.3448],[94.17343,26.43307],[94.27762,26.56027],[94.30801,26.45459],[94.3983,26.53294],[94.40397,26.61323],[94.79501,26.80231],[94.91672,26.94456],[94.99671,26.91992],[95.07104,26.94915],[95.19481,27.03619],[95.45745,27.14882],[95.45436,27.20517],[95.49041,27.2473],[95.89965,27.2766],[96.01312,27.37283],[95.95064,27.4315],[95.86189,27.43333],[95.87923,27.5378],[95.75297,27.72577],[96.00179,27.95847],[95.82395,27.97151],[95.60285,27.95452],[95.51891,27.88111],[95.30965,27.86897],[94.99843,27.77393],[94.92376,27.77803],[94.70815,27.65433],[94.66215,27.64582],[94.46027,27.55896],[94.23059,27.6338],[94.26441,27.52532],[94.15729,27.47888],[93.80702,27.15019],[93.80272,27.12888],[93.82392,27.12025],[93.83457,27.07532],[93.66462,26.96858],[93.49227,26.94043],[93.38035,26.96308],[93.02089,26.91656],[92.87223,27.00652],[92.63757,27.03496],[92.6441,26.98495],[92.11469,26.89405],[92.08637,26.85684],[92.02045,26.84995],[91.87351,26.93018],[91.82458,26.86358],[91.7391,26.82315],[91.50067,26.79223],[90.67715,26.77215],[90.54914,26.81671],[90.40838,26.90829],[90.29972,26.84857],[90.23174,26.85868],[90.18882,26.76768],[90.05527,26.72859],[89.86124,26.73307],[89.86885,26.46258],[89.84653,26.40078],[89.72705,26.26771],[89.7068,26.18008]]]}},{"type":"Feature","properties":{"id":"IN-NL"},"geometry":{"type":"Polygon","coordinates":[[[93.33074,25.54817],[93.39134,25.4628],[93.45211,25.43335],[93.47047,25.30492],[93.60282,25.19872],[93.69449,25.3614],[93.8071,25.46528],[93.75989,25.51703],[93.83199,25.55808],[94.0524,25.56753],[94.12055,25.52044],[94.17703,25.54801],[94.21617,25.49658],[94.40877,25.53128],[94.42903,25.58518],[94.50302,25.61877],[94.58919,25.69459],[94.55692,25.51239],[94.68069,25.45815],[94.81421,25.49204],[95.04272,25.72382],[95.03585,25.93056],[95.18556,26.07338],[95.11428,26.1019],[95.12555,26.28318],[95.12801,26.38397],[95.05798,26.45408],[95.23618,26.68266],[95.19481,27.03619],[95.07104,26.94915],[94.99671,26.91992],[94.91672,26.94456],[94.79501,26.80231],[94.40397,26.61323],[94.3983,26.53294],[94.30801,26.45459],[94.27762,26.56027],[94.17343,26.43307],[94.17308,26.3448],[94.09687,26.31142],[93.99902,26.16468],[93.98005,25.91705],[93.9349,25.89428],[93.8792,25.84253],[93.83903,25.85814],[93.79637,25.80746],[93.78736,25.81202],[93.77603,25.84887],[93.79526,25.90462],[93.77449,25.97208],[93.75715,25.94646],[93.7023,25.92724],[93.69415,25.89783],[93.70325,25.8498],[93.63887,25.81318],[93.54343,25.72738],[93.54463,25.70766],[93.33074,25.54817]]]}},{"type":"Feature","properties":{"id":"IN-MN"},"geometry":{"type":"Polygon","coordinates":[[[92.98416,24.11354],[93.32351,24.04468],[93.34735,24.10151],[93.41415,24.07854],[93.46633,23.97067],[93.50616,23.94432],[93.62871,24.00922],[93.75952,24.0003],[93.80279,23.92549],[93.92089,23.95812],[94.14149,23.83427],[94.2366,24.03329],[94.28844,24.23092],[94.30518,24.23984],[94.32362,24.27692],[94.3365,24.32895],[94.35333,24.33364],[94.46765,24.57366],[94.54919,24.63687],[94.54696,24.70816],[94.60204,24.70889],[94.73937,25.00545],[94.74212,25.13606],[94.57458,25.20318],[94.68069,25.45815],[94.55692,25.51239],[94.58919,25.69459],[94.50302,25.61877],[94.42903,25.58518],[94.40877,25.53128],[94.21617,25.49658],[94.17703,25.54801],[94.12055,25.52044],[94.0524,25.56753],[93.83199,25.55808],[93.75989,25.51703],[93.8071,25.46528],[93.69449,25.3614],[93.60282,25.19872],[93.47047,25.30492],[93.38516,25.2419],[93.19702,24.81166],[93.17015,24.80138],[93.15299,24.78821],[93.13625,24.80948],[93.1202,24.80886],[93.12088,24.81961],[93.10998,24.81229],[93.11119,24.79912],[93.02432,24.4062],[92.98416,24.11354]]]}},{"type":"Feature","properties":{"id":"IN-TR"},"geometry":{"type":"Polygon","coordinates":[[[91.14618,23.69294],[91.20746,23.68823],[91.20583,23.64924],[91.16008,23.66276],[91.16163,23.59734],[91.22437,23.51496],[91.22016,23.50103],[91.25016,23.48269],[91.24746,23.45297],[91.27089,23.43127],[91.28106,23.37798],[91.29741,23.37881],[91.29428,23.35226],[91.32634,23.36424],[91.2969,23.32602],[91.32445,23.24615],[91.32848,23.15945],[91.37028,23.07128],[91.41423,23.05501],[91.38015,23.18171],[91.40659,23.28589],[91.46693,23.22825],[91.54632,23.0133],[91.61571,22.93929],[91.74347,23.00651],[91.81677,23.08052],[91.79154,23.215],[91.75832,23.28913],[91.84209,23.40693],[91.95642,23.47361],[91.95093,23.73284],[92.04706,23.64229],[92.15417,23.73409],[92.26352,23.72344],[92.31777,23.86543],[92.297,24.25244],[92.20756,24.23882],[92.27725,24.38431],[92.21991,24.50307],[92.16258,24.53306],[92.12173,24.37461],[91.96603,24.3799],[91.89943,24.12654],[91.84553,24.20798],[91.75283,24.23835],[91.73807,24.14158],[91.65292,24.22095],[91.64125,24.10993],[91.57911,24.07577],[91.3732,24.11448],[91.39663,24.04834],[91.37689,23.97527],[91.30462,23.9944],[91.29406,23.96727],[91.28265,23.97707],[91.26634,23.94766],[91.27793,23.92177],[91.23853,23.92632],[91.24892,23.90828],[91.22574,23.89486],[91.25432,23.84007],[91.22763,23.79453],[91.226,23.73962],[91.16249,23.74701],[91.14618,23.69294]]]}},{"type":"Feature","properties":{"id":"IN-MZ"},"geometry":{"type":"Polygon","coordinates":[[[92.26352,23.72344],[92.40119,23.2385],[92.34455,23.2344],[92.37665,22.9435],[92.5181,22.71441],[92.60029,22.1522],[92.56616,22.13554],[92.60949,21.97638],[92.67532,22.03547],[92.70416,22.16017],[92.86208,22.05456],[92.90588,21.93826],[92.93899,22.02656],[92.99804,21.98964],[92.99255,22.05965],[93.04885,22.20595],[93.15734,22.18687],[93.14224,22.24535],[93.19991,22.25425],[93.18206,22.43716],[93.13537,22.45873],[93.11477,22.54374],[93.134,22.59573],[93.09417,22.69459],[93.134,22.92498],[93.12988,23.05772],[93.2878,23.00464],[93.38478,23.13698],[93.36862,23.35426],[93.38781,23.36139],[93.39981,23.38828],[93.38805,23.4728],[93.43475,23.68299],[93.3908,23.7622],[93.3908,23.92925],[93.36059,23.93176],[93.32351,24.04468],[92.98416,24.11354],[93.02432,24.4062],[92.8137,24.3915],[92.76323,24.52213],[92.68615,24.35085],[92.62229,24.33145],[92.61114,24.25087],[92.55655,24.24915],[92.53183,24.17698],[92.44239,24.14299],[92.42265,24.25338],[92.297,24.25244],[92.31777,23.86543],[92.26352,23.72344]]]}},{"type":"Feature","properties":{"id":"CN-SN"},"geometry":{"type":"Polygon","coordinates":[[[105.49432,32.91403],[105.60504,32.70064],[106.02527,32.85853],[106.11007,32.72144],[106.41941,32.62434],[106.84204,32.7327],[107.05799,32.70208],[107.1215,32.48543],[107.25437,32.40083],[107.402,32.56562],[107.48989,32.38315],[107.64919,32.41213],[107.9647,32.13782],[108.02616,32.22209],[108.18872,32.23385],[108.47934,32.2581],[108.50646,32.20263],[109.20066,31.85248],[109.27619,31.71823],[109.58381,31.72933],[109.62364,32.10177],[109.49901,32.3028],[109.55703,32.48543],[109.71393,32.61508],[110.04524,32.55144],[110.19218,32.62029],[110.13725,32.81238],[110.03391,32.86041],[110.02481,32.87482],[109.86259,32.911],[109.79049,32.87929],[109.75925,32.91273],[109.79324,33.0691],[109.42794,33.15479],[109.61299,33.2783],[110.03974,33.19158],[110.16197,33.20996],[110.22926,33.15882],[110.46958,33.17721],[110.55713,33.26653],[110.7003,33.09384],[110.97358,33.25806],[111.02045,33.33884],[111.0189,33.56771],[110.83797,33.66778],[110.73497,33.8168],[110.58734,33.87184],[110.66802,33.95304],[110.57979,34.04184],[110.64056,34.16096],[110.35388,34.519],[110.39869,34.56142],[110.36161,34.56905],[110.375,34.60283],[110.27406,34.61611],[110.226,34.70309],[110.22891,34.89437],[110.36281,35.13451],[110.39371,35.29215],[110.60348,35.62576],[110.43594,36.1606],[110.49602,36.52922],[110.38873,36.68521],[110.44538,36.7287],[110.37586,36.87865],[110.4095,36.89692],[110.38066,37.01242],[110.45997,37.04503],[110.67798,37.29481],[110.6409,37.42852],[110.74665,37.45169],[110.78098,37.55682],[110.75626,37.63516],[110.79711,37.65827],[110.69789,37.70881],[110.58906,37.92632],[110.51198,37.9603],[110.5204,38.00252],[110.49688,38.02213],[110.5149,38.20905],[110.80192,38.44713],[110.87333,38.45842],[110.88775,38.65522],[110.95745,38.75836],[111.00963,38.90706],[110.97495,38.97836],[111.04637,39.02158],[111.09134,39.02985],[111.13718,39.07011],[111.23897,39.30003],[111.19228,39.30508],[111.11881,39.36403],[111.09289,39.35859],[111.04808,39.43022],[111.16893,39.58928],[110.88363,39.50854],[110.68759,39.26522],[110.59661,39.27744],[110.52005,39.3834],[110.43662,39.38261],[110.38135,39.30826],[110.19853,39.48072],[110.11648,39.43486],[110.1309,39.39017],[110.16094,39.38062],[110.20814,39.28076],[109.89212,39.14523],[109.9007,39.10715],[109.72423,39.06451],[109.62432,38.86377],[109.55223,38.80386],[109.50656,38.82419],[109.01321,38.38714],[108.93356,38.17883],[109.04754,38.0989],[109.07226,38.02321],[109.03209,38.02023],[108.93836,37.9182],[108.83193,38.0662],[108.78936,37.68436],[108.02753,37.6512],[107.96607,37.79269],[107.65674,37.86753],[107.28183,37.48412],[107.25299,37.31447],[107.33264,37.15621],[107.27462,37.09462],[107.30071,36.92162],[107.66944,36.83017],[108.0574,36.59678],[108.30562,36.55625],[108.45737,36.42984],[108.6383,36.43039],[108.69804,36.10903],[108.64654,35.95021],[108.50303,35.89044],[108.62182,35.54284],[108.61083,35.3133],[108.47248,35.27],[108.23902,35.25963],[108.20468,35.307],[107.96333,35.24309],[107.81398,35.27547],[107.73673,35.314],[107.70721,35.25893],[107.64095,35.24477],[107.68215,35.21561],[107.76077,35.07187],[107.86514,34.98809],[107.63854,34.93266],[107.56542,34.97262],[107.52319,34.91155],[107.29728,34.93773],[107.20733,34.87832],[107.03155,35.05473],[106.8338,35.0803],[106.71964,35.10404],[106.69406,35.08142],[106.61184,35.07257],[106.5533,35.09308],[106.48395,35.0165],[106.57373,34.77503],[106.34267,34.56382],[106.33048,34.51928],[106.36147,34.51419],[106.37778,34.52147],[106.39031,34.51129],[106.40593,34.5243],[106.45245,34.53152],[106.46318,34.53024],[106.47399,34.52183],[106.53802,34.48844],[106.55459,34.49028],[106.58025,34.46693],[106.5945,34.46729],[106.59493,34.45236],[106.60995,34.44896],[106.61828,34.42078],[106.63338,34.39444],[106.6611,34.38325],[106.67716,34.3846],[106.69518,34.36972],[106.71295,34.37092],[106.67621,34.25863],[106.50798,34.27282],[106.56738,34.1317],[106.44498,33.94193],[106.40567,33.90846],[106.48807,33.85872],[106.45511,33.81595],[106.58454,33.5986],[106.51519,33.50246],[106.40327,33.61276],[106.18869,33.55312],[106.0905,33.61347],[105.95764,33.61061],[105.8337,33.47856],[105.83593,33.38243],[105.69276,33.39088],[105.82134,33.25648],[105.95352,33.22834],[105.97017,33.1489],[105.91884,33.13553],[105.93687,33.02147],[105.85945,32.93896],[105.62187,32.88304],[105.49432,32.91403]]]}},{"type":"Feature","properties":{"id":"CN-GZ"},"geometry":{"type":"Polygon","coordinates":[[[103.61377,27.00591],[103.68621,27.06248],[103.78063,26.949],[103.7051,26.83173],[103.8153,26.53417],[104.00104,26.51389],[104.15279,26.67323],[104.34162,26.62444],[104.40925,26.73028],[104.47963,26.58422],[104.56958,26.59957],[104.68666,26.3691],[104.59258,26.31926],[104.5174,26.17084],[104.46865,26.0139],[104.3066,25.65452],[104.36428,25.58177],[104.42161,25.58595],[104.43243,25.47427],[104.5271,25.53105],[104.5428,25.51587],[104.55413,25.52857],[104.5392,25.40606],[104.64134,25.36],[104.64666,25.2939],[104.70708,25.29763],[104.7076,25.28428],[104.75137,25.26891],[104.79532,25.25603],[104.82467,25.14808],[104.75189,25.21627],[104.72339,25.19717],[104.67515,25.06849],[104.71206,25.00348],[104.60203,24.89313],[104.53868,24.81852],[104.54229,24.77535],[104.52444,24.73357],[104.73884,24.62017],[105.02655,24.79265],[105.03702,24.87927],[105.08577,24.92894],[105.20507,24.99741],[105.4387,24.92551],[105.49209,24.8154],[105.69808,24.77177],[105.80314,24.70192],[105.93275,24.73061],[105.95849,24.68196],[106.016,24.63344],[106.04776,24.69147],[106.13256,24.73404],[106.17565,24.76896],[106.20243,24.857],[106.14269,24.95804],[106.18595,24.95804],[106.21341,24.98325],[106.29615,24.97765],[106.43726,25.02121],[106.45133,25.03879],[106.5121,25.05574],[106.5327,25.07922],[106.5842,25.08995],[106.63467,25.13378],[106.63621,25.16734],[106.68823,25.18133],[106.72393,25.16408],[106.76067,25.18552],[106.78934,25.17371],[106.8913,25.18894],[106.9095,25.25183],[106.99378,25.24096],[107.01061,25.3541],[106.98142,25.36419],[106.96083,25.44203],[106.9919,25.4504],[107.0125,25.49705],[107.06708,25.51889],[107.06005,25.56288],[107.14313,25.56722],[107.21454,25.61119],[107.22776,25.57093],[107.31874,25.49844],[107.3075,25.41079],[107.41384,25.39428],[107.42603,25.28847],[107.48233,25.30414],[107.46568,25.21736],[107.59752,25.25758],[107.6497,25.32106],[107.69313,25.19282],[107.75133,25.24811],[107.77622,25.11358],[108.11096,25.21363],[108.17859,25.45319],[108.34648,25.535],[108.49822,25.4535],[108.61976,25.30306],[108.60671,25.49348],[108.6989,25.6344],[108.77683,25.63518],[108.79709,25.53144],[108.86301,25.55994],[109.07278,25.5206],[109.03724,25.6214],[109.07089,25.73743],[108.93665,25.68361],[108.89562,25.68825],[108.89476,25.71826],[109.03827,25.80112],[109.13663,25.76851],[109.17989,25.8192],[109.2041,25.74176],[109.26675,25.71949],[109.47669,26.03334],[109.45678,26.2971],[109.2925,26.2771],[109.25731,26.3668],[109.37662,26.46319],[109.40134,26.53647],[109.38503,26.60571],[109.35533,26.66172],[109.3107,26.66049],[109.28512,26.70145],[109.36374,26.7039],[109.43962,26.76231],[109.51721,26.75419],[109.51171,26.88242],[109.47549,26.90324],[109.44374,26.86358],[109.42897,26.89191],[109.55326,26.94869],[109.51635,27.06753],[109.46931,27.07364],[109.46296,27.13125],[109.40408,27.15921],[109.26795,27.13141],[109.25182,27.15386],[109.15552,27.07211],[109.09852,27.07166],[109.12839,27.12988],[109.02523,27.10681],[108.88137,27.0053],[108.78936,27.08908],[108.91828,27.13629],[108.90506,27.21341],[109.01819,27.28117],[109.0169,27.28506],[109.05055,27.29285],[109.03844,27.33517],[109.10659,27.3495],[109.13681,27.44781],[109.2937,27.42541],[109.45197,27.5722],[109.46639,27.6814],[109.41215,27.72456],[109.36906,27.73079],[109.35859,27.76345],[109.32821,27.79519],[109.33885,27.84196],[109.31756,27.87823],[109.30143,27.95832],[109.31636,27.99531],[109.37713,28.02774],[109.35087,28.06183],[109.29216,28.03895],[109.36529,28.25479],[109.39807,28.27717],[109.36692,28.28087],[109.34795,28.26689],[109.35353,28.29961],[109.30864,28.28185],[109.26804,28.31375],[109.28495,28.3772],[109.26341,28.38845],[109.25019,28.45035],[109.26993,28.49849],[109.26898,28.50437],[109.25285,28.49343],[109.23191,28.48031],[109.20942,28.48423],[109.17354,28.45299],[109.14899,28.3485],[109.08239,28.24602],[109.09749,28.17674],[109.01922,28.21834],[108.99707,28.15828],[108.91141,28.22409],[108.75091,28.2073],[108.72001,28.29228],[108.76739,28.31465],[108.77872,28.42492],[108.70731,28.50098],[108.63178,28.46506],[108.69512,28.40831],[108.65959,28.3343],[108.60706,28.32523],[108.56414,28.37931],[108.60637,28.42401],[108.56552,28.5423],[108.60877,28.54924],[108.63109,28.6457],[108.52981,28.65112],[108.45874,28.62852],[108.33103,28.68637],[108.38012,28.80647],[108.28708,29.09577],[108.22837,29.02405],[108.18477,29.07207],[108.14323,29.05737],[108.0622,29.09037],[108.00899,29.04206],[107.90376,29.02825],[107.84042,28.96309],[107.75184,29.21031],[107.69657,29.14376],[107.4044,29.19472],[107.35959,29.00723],[107.43633,28.95047],[107.37865,28.85339],[107.22467,28.74583],[107.1833,28.88977],[106.98228,28.88556],[106.97679,28.75893],[106.91379,28.81339],[106.82504,28.76389],[106.8659,28.62973],[106.80702,28.59477],[106.76238,28.62732],[106.77251,28.57517],[106.72307,28.54758],[106.72977,28.45224],[106.56154,28.50143],[106.65183,28.66649],[106.5715,28.70624],[106.46781,28.84106],[106.44927,28.78631],[106.52601,28.67537],[106.47743,28.53567],[106.36756,28.52662],[106.3655,28.47593],[106.10269,28.63937],[105.96691,28.76284],[105.88142,28.602],[105.7386,28.61903],[105.71001,28.58987],[105.69482,28.59635],[105.68778,28.58934],[105.68839,28.5702],[105.61071,28.46144],[105.65895,28.308],[105.88382,28.25358],[105.86082,28.14526],[105.97103,28.11468],[106.12561,28.17485],[106.20397,28.13754],[106.26457,28.06372],[106.23865,28.01986],[106.31521,27.97954],[106.33872,27.8245],[106.20861,27.76436],[106.0802,27.78107],[105.9288,27.732],[105.77825,27.72228],[105.64212,27.66999],[105.50617,27.77105],[105.39562,27.76831],[105.30515,27.70875],[105.3012,27.61677],[105.24473,27.57235],[105.21057,27.37603],[105.07186,27.43059],[104.86656,27.29338],[104.85145,27.34523],[104.58057,27.31961],[104.50984,27.40712],[104.37217,27.47233],[104.17579,27.26317],[104.01889,27.38152],[104.00447,27.42937],[103.93512,27.44674],[103.83865,27.27202],[103.62716,27.11872],[103.61377,27.00591]]]}},{"type":"Feature","properties":{"id":"CN-CQ"},"geometry":{"type":"Polygon","coordinates":[[[105.27717,29.57375],[105.37261,29.42285],[105.443,29.41612],[105.41381,29.31723],[105.45844,29.32831],[105.47183,29.2816],[105.65586,29.25435],[105.69705,29.30017],[105.74048,29.04131],[105.76435,28.99102],[105.78426,28.98231],[105.80022,28.94521],[105.87181,28.938],[105.90425,28.90382],[105.90983,28.92478],[105.97995,28.98081],[106.03969,28.95528],[106.03763,28.91381],[106.21238,28.92012],[106.36756,28.52662],[106.47743,28.53567],[106.52601,28.67537],[106.44927,28.78631],[106.46781,28.84106],[106.5715,28.70624],[106.65183,28.66649],[106.56154,28.50143],[106.72977,28.45224],[106.72307,28.54758],[106.77251,28.57517],[106.76238,28.62732],[106.80702,28.59477],[106.8659,28.62973],[106.82504,28.76389],[106.91379,28.81339],[106.97679,28.75893],[106.98228,28.88556],[107.1833,28.88977],[107.22467,28.74583],[107.37865,28.85339],[107.43633,28.95047],[107.35959,29.00723],[107.4044,29.19472],[107.69657,29.14376],[107.75184,29.21031],[107.84042,28.96309],[107.90376,29.02825],[108.00899,29.04206],[108.0622,29.09037],[108.14323,29.05737],[108.18477,29.07207],[108.22837,29.02405],[108.28708,29.09577],[108.38012,28.80647],[108.33103,28.68637],[108.45874,28.62852],[108.52981,28.65112],[108.63109,28.6457],[108.60877,28.54924],[108.56552,28.5423],[108.60637,28.42401],[108.56414,28.37931],[108.60706,28.32523],[108.65959,28.3343],[108.69512,28.40831],[108.63178,28.46506],[108.70731,28.50098],[108.77872,28.42492],[108.76739,28.31465],[108.72001,28.29228],[108.75091,28.2073],[108.91141,28.22409],[108.99707,28.15828],[109.01922,28.21834],[109.09749,28.17674],[109.08239,28.24602],[109.14899,28.3485],[109.17354,28.45299],[109.20942,28.48423],[109.23191,28.48031],[109.26898,28.50437],[109.26735,28.51708],[109.27572,28.52153],[109.29104,28.57227],[109.31576,28.58595],[109.29671,28.62913],[109.19594,28.60208],[109.18556,28.63184],[109.29885,28.73018],[109.23508,28.78699],[109.23671,28.88887],[109.31877,29.05827],[109.23122,29.09442],[109.22864,29.12157],[109.17509,29.17074],[109.10951,29.18064],[109.10419,29.36691],[109.05029,29.4055],[108.97647,29.33085],[108.90918,29.32651],[108.93785,29.42943],[108.86249,29.45574],[108.90781,29.59584],[108.84756,29.65553],[108.75143,29.69864],[108.74095,29.65777],[108.67624,29.72696],[108.67401,29.8457],[108.6098,29.86863],[108.54749,29.81696],[108.50337,29.71161],[108.3657,29.82411],[108.38905,29.86759],[108.51299,29.87548],[108.57822,30.25906],[108.40072,30.38827],[108.41789,30.49306],[108.58268,30.49276],[108.63796,30.53979],[108.6383,30.58398],[108.68225,30.59019],[108.72396,30.50282],[108.82644,30.50193],[108.96703,30.62978],[109.04273,30.65637],[109.11861,30.6385],[109.08256,30.59418],[109.13749,30.52086],[109.30469,30.63584],[109.35413,30.49128],[109.52236,30.66464],[109.55532,30.64825],[109.68303,30.77133],[109.90825,30.909],[109.99237,30.8869],[110.06,30.78136],[110.17192,30.97289],[110.10635,31.08601],[110.20042,31.15405],[110.12695,31.41108],[109.73522,31.58497],[109.72595,31.7083],[109.58381,31.72933],[109.27619,31.71823],[109.20066,31.85248],[108.50646,32.20263],[108.36553,32.182],[108.46492,32.07195],[108.35643,32.07384],[108.273,31.9475],[108.54011,31.67559],[108.41548,31.6145],[108.3403,31.5165],[108.22151,31.50684],[108.17481,31.3334],[107.998,31.22718],[108.08933,31.21045],[108.00899,31.08557],[108.05551,31.05205],[107.91543,30.93359],[107.99114,30.91076],[107.84866,30.79405],[107.76832,30.82058],[107.71373,30.89633],[107.63683,30.81233],[107.49435,30.8567],[107.42637,30.7318],[107.50431,30.63732],[107.19291,30.18727],[107.03121,30.03477],[106.97542,30.08677],[106.92066,30.02897],[106.83448,30.04665],[106.76582,30.01738],[106.72531,30.03462],[106.6702,30.14468],[106.6084,30.29627],[106.47623,30.30413],[106.22388,30.18045],[106.16775,30.2518],[106.16432,30.30591],[105.81275,30.44186],[105.7125,30.31865],[105.71817,30.2598],[105.61809,30.273],[105.6459,30.20448],[105.53947,30.17154],[105.58359,30.12092],[105.73448,30.02867],[105.72006,29.85583],[105.61466,29.84898],[105.56076,29.73784],[105.47475,29.68119],[105.38497,29.64405],[105.27717,29.57375]]]}},{"type":"Feature","properties":{"id":"CN-HI"},"geometry":{"type":"Polygon","coordinates":[[[107.44022,18.66249],[110.2534,15.19951],[112.88221,15.61902],[111.04979,20.2622],[108.26073,20.07614],[107.44022,18.66249]]]}},{"type":"Feature","properties":{"id":"CN-HN"},"geometry":{"type":"Polygon","coordinates":[[[108.78936,27.08908],[108.88137,27.0053],[109.02523,27.10681],[109.12839,27.12988],[109.09852,27.07166],[109.15552,27.07211],[109.25182,27.15386],[109.26795,27.13141],[109.40408,27.15921],[109.46296,27.13125],[109.46931,27.07364],[109.51635,27.06753],[109.55326,26.94869],[109.42897,26.89191],[109.44374,26.86358],[109.47549,26.90324],[109.51171,26.88242],[109.51721,26.75419],[109.43962,26.76231],[109.36374,26.7039],[109.28512,26.70145],[109.3107,26.66049],[109.35533,26.66172],[109.38503,26.60571],[109.40134,26.53647],[109.37662,26.46319],[109.25731,26.3668],[109.2925,26.2771],[109.45678,26.2971],[109.47669,26.03334],[109.62604,26.0503],[109.72372,25.99523],[109.66827,25.89165],[109.81847,25.87142],[109.79255,26.02686],[109.88336,26.05323],[109.96095,26.20843],[110.09176,26.16499],[110.07562,26.03411],[110.22102,26.04722],[110.24745,25.96298],[110.32779,25.98088],[110.61172,26.33465],[110.75317,26.25277],[110.9262,26.26694],[110.96328,26.3851],[111.08362,26.31434],[111.28927,26.2674],[111.19331,25.95665],[111.25579,25.85922],[111.33441,25.90493],[111.48033,25.87513],[111.42059,25.76959],[111.30678,25.72088],[111.31347,25.47613],[110.97015,25.10922],[110.95521,25.01281],[110.99058,24.92084],[111.0922,24.95306],[111.10542,25.0405],[111.19794,25.07658],[111.26386,25.1487],[111.33218,25.09943],[111.39879,25.12803],[111.41664,25.04174],[111.46522,25.03241],[111.43192,24.97345],[111.46007,24.92458],[111.47981,24.79889],[111.42745,24.6832],[111.52633,24.63952],[111.6961,24.78455],[112.01728,24.74714],[112.16062,24.86915],[112.17143,24.92909],[112.11238,24.96722],[112.15049,25.02323],[112.18912,25.18474],[112.357,25.18288],[112.56488,25.12912],[112.65174,25.13751],[112.70393,25.08622],[112.7798,24.89328],[113.00571,24.93999],[112.96829,25.16998],[113.02854,25.20059],[112.98923,25.24857],[112.8713,25.24857],[112.84984,25.34278],[112.91636,25.30306],[112.96897,25.35504],[113.01721,25.34976],[113.12141,25.42436],[113.14561,25.47814],[113.27333,25.52276],[113.38542,25.40761],[113.58009,25.31951],[113.74093,25.36496],[113.78334,25.32385],[113.94126,25.44668],[113.9756,25.5892],[113.90504,25.7113],[114.01525,25.90339],[114.01834,26.04398],[114.23086,26.15312],[114.20888,26.21166],[113.9368,26.15605],[114.08374,26.43461],[114.104,26.57624],[113.9023,26.6206],[113.85801,26.66786],[113.83895,26.79342],[113.91517,26.94716],[113.77715,27.11781],[113.86264,27.2888],[113.8702,27.38304],[113.62232,27.33731],[113.5921,27.42602],[113.62112,27.5139],[113.59691,27.62711],[113.7763,27.81903],[113.72085,27.8755],[113.74351,27.9477],[114.03945,28.06289],[113.99173,28.16736],[114.17472,28.26023],[114.25678,28.38339],[114.20408,28.49177],[114.07653,28.55919],[114.14932,28.77187],[114.0204,28.89563],[114.00349,28.95798],[113.96736,28.94243],[113.93714,29.05061],[113.86882,29.03906],[113.82041,29.10417],[113.67862,29.07777],[113.69132,29.2187],[113.58953,29.26348],[113.7569,29.44767],[113.62403,29.52118],[113.7363,29.58928],[113.63159,29.67313],[113.55588,29.67731],[113.54215,29.76363],[113.56601,29.84942],[113.42027,29.75141],[113.22097,29.55195],[113.14441,29.44976],[113.04107,29.52656],[112.93498,29.47487],[112.9264,29.69014],[112.8955,29.79328],[112.79388,29.73963],[112.68093,29.59734],[112.4509,29.63987],[112.27958,29.49967],[112.2377,29.66314],[112.10861,29.66001],[112.06706,29.73188],[111.95548,29.82843],[111.79447,29.91209],[111.6053,29.89006],[111.51912,29.93232],[111.39827,29.91462],[111.24481,30.04383],[110.94646,30.06493],[110.75729,30.12523],[110.75386,30.04888],[110.52246,30.06523],[110.49602,29.90792],[110.64742,29.77272],[110.52143,29.6955],[110.46375,29.71101],[110.37517,29.63435],[110.14514,29.78374],[109.7874,29.76586],[109.67548,29.59823],[109.62793,29.63181],[109.5191,29.61629],[109.48511,29.55718],[109.45678,29.55613],[109.4597,29.51566],[109.42897,29.53388],[109.43532,29.49205],[109.41541,29.49788],[109.41335,29.45349],[109.37267,29.43062],[109.3876,29.37349],[109.34263,29.37604],[109.34658,29.28145],[109.26401,29.23098],[109.26727,29.12667],[109.22864,29.12157],[109.23122,29.09442],[109.31877,29.05827],[109.23671,28.88887],[109.23508,28.78699],[109.29885,28.73018],[109.18556,28.63184],[109.19594,28.60208],[109.29671,28.62913],[109.31576,28.58595],[109.29104,28.57227],[109.27572,28.52153],[109.26735,28.51708],[109.26898,28.50437],[109.26993,28.49849],[109.25019,28.45035],[109.26341,28.38845],[109.28495,28.3772],[109.26804,28.31375],[109.30864,28.28185],[109.35353,28.29961],[109.34795,28.26689],[109.36692,28.28087],[109.39807,28.27717],[109.36529,28.25479],[109.29216,28.03895],[109.35087,28.06183],[109.37713,28.02774],[109.31636,27.99531],[109.30143,27.95832],[109.31756,27.87823],[109.33885,27.84196],[109.32821,27.79519],[109.35859,27.76345],[109.36906,27.73079],[109.41215,27.72456],[109.46639,27.6814],[109.45197,27.5722],[109.2937,27.42541],[109.13681,27.44781],[109.10659,27.3495],[109.03844,27.33517],[109.05055,27.29285],[109.0169,27.28506],[109.01819,27.28117],[108.90506,27.21341],[108.91828,27.13629],[108.78936,27.08908]]]}},{"type":"Feature","properties":{"id":"CN-HB"},"geometry":{"type":"Polygon","coordinates":[[[108.3657,29.82411],[108.50337,29.71161],[108.54749,29.81696],[108.6098,29.86863],[108.67401,29.8457],[108.67624,29.72696],[108.74095,29.65777],[108.75143,29.69864],[108.84756,29.65553],[108.90781,29.59584],[108.86249,29.45574],[108.93785,29.42943],[108.90918,29.32651],[108.97647,29.33085],[109.05029,29.4055],[109.10419,29.36691],[109.10951,29.18064],[109.17509,29.17074],[109.22864,29.12157],[109.26727,29.12667],[109.26401,29.23098],[109.34658,29.28145],[109.34263,29.37604],[109.3876,29.37349],[109.37267,29.43062],[109.41335,29.45349],[109.41541,29.49788],[109.43532,29.49205],[109.42897,29.53388],[109.4597,29.51566],[109.45678,29.55613],[109.48511,29.55718],[109.5191,29.61629],[109.62793,29.63181],[109.67548,29.59823],[109.7874,29.76586],[110.14514,29.78374],[110.37517,29.63435],[110.46375,29.71101],[110.52143,29.6955],[110.64742,29.77272],[110.49602,29.90792],[110.52246,30.06523],[110.75386,30.04888],[110.75729,30.12523],[110.94646,30.06493],[111.24481,30.04383],[111.39827,29.91462],[111.51912,29.93232],[111.6053,29.89006],[111.79447,29.91209],[111.95548,29.82843],[112.06706,29.73188],[112.10861,29.66001],[112.2377,29.66314],[112.27958,29.49967],[112.4509,29.63987],[112.68093,29.59734],[112.79388,29.73963],[112.8955,29.79328],[112.9264,29.69014],[112.93498,29.47487],[113.04107,29.52656],[113.14441,29.44976],[113.22097,29.55195],[113.42027,29.75141],[113.56601,29.84942],[113.54215,29.76363],[113.55588,29.67731],[113.63159,29.67313],[113.7363,29.58928],[113.62403,29.52118],[113.7569,29.44767],[113.58953,29.26348],[113.69132,29.2187],[113.67862,29.07777],[113.82041,29.10417],[113.86882,29.03906],[113.93714,29.05061],[114.06555,29.20911],[114.24974,29.23742],[114.25884,29.35165],[114.49367,29.3301],[114.67306,29.39085],[114.90703,29.40311],[114.94428,29.48458],[114.91527,29.48936],[114.88231,29.43899],[114.85639,29.48264],[114.89725,29.52925],[114.9575,29.50654],[114.94548,29.55688],[115.06496,29.56733],[115.15577,29.5046],[115.15045,29.59793],[115.09551,29.68238],[115.27301,29.62763],[115.39918,29.68253],[115.46854,29.75215],[115.45755,29.76318],[115.47025,29.81011],[115.51136,29.84064],[115.69152,29.84555],[115.82988,29.75245],[115.93803,29.71906],[116.13338,29.82783],[116.12531,29.90093],[116.0733,29.964],[116.07313,30.05958],[116.08222,30.12434],[116.0551,30.16086],[116.05922,30.20923],[115.9806,30.29183],[115.90627,30.30961],[115.88069,30.38398],[115.91777,30.42706],[115.90988,30.51968],[115.7698,30.6701],[115.77512,30.74891],[115.86164,30.77443],[115.84087,30.84358],[115.98712,30.93918],[116.06798,30.96009],[116.05287,31.0138],[115.89597,31.09292],[115.86679,31.15229],[115.77049,31.10409],[115.70165,31.20296],[115.64414,31.21911],[115.57823,31.14465],[115.53668,31.19209],[115.52063,31.258],[115.45309,31.28119],[115.45798,31.3202],[115.44425,31.31492],[115.43678,31.34894],[115.37567,31.34762],[115.3694,31.40639],[115.30151,31.38566],[115.28151,31.40456],[115.25859,31.39049],[115.24297,31.39262],[115.2567,31.41189],[115.24692,31.42404],[115.2089,31.43357],[115.21379,31.51226],[115.22769,31.55762],[115.17178,31.60306],[115.12942,31.60544],[115.10873,31.53223],[115.08882,31.5198],[115.09427,31.51061],[115.07916,31.51017],[115.04754,31.51541],[115.0187,31.52945],[114.98557,31.47076],[114.97192,31.50384],[114.93887,31.48383],[114.93222,31.46959],[114.91518,31.48386],[114.87373,31.47003],[114.84952,31.48606],[114.83502,31.45971],[114.78146,31.48255],[114.76601,31.52353],[114.6916,31.52821],[114.61915,31.58701],[114.53882,31.55703],[114.56354,31.77006],[114.48509,31.73926],[114.28699,31.75765],[114.22759,31.83994],[114.17369,31.85568],[113.9804,31.75298],[113.92684,31.87959],[113.8314,31.85568],[113.75278,31.9906],[113.78471,32.04271],[113.72016,32.0881],[113.76411,32.17357],[113.73355,32.2581],[113.76583,32.30686],[113.71467,32.43329],[113.58661,32.35226],[113.41701,32.27145],[113.1887,32.43155],[113.12725,32.38083],[113.03386,32.42431],[112.70359,32.35502],[112.55149,32.39851],[112.44094,32.34574],[112.35614,32.373],[112.32009,32.32572],[112.21435,32.39358],[112.14998,32.3788],[112.1038,32.40503],[112.05436,32.47443],[112.00012,32.45415],[111.94587,32.51497],[111.87171,32.50657],[111.85644,32.53263],[111.82382,32.53118],[111.66709,32.6252],[111.56101,32.59194],[111.46488,32.73732],[111.41201,32.75797],[111.37115,32.83171],[111.22352,32.91533],[111.25717,32.96892],[111.24069,33.03644],[111.13821,33.04824],[111.18181,33.10692],[111.12593,33.1443],[111.11735,33.16349],[111.03109,33.15551],[111.05478,33.19431],[110.97358,33.25806],[110.7003,33.09384],[110.55713,33.26653],[110.46958,33.17721],[110.22926,33.15882],[110.16197,33.20996],[110.03974,33.19158],[109.61299,33.2783],[109.42794,33.15479],[109.79324,33.0691],[109.75925,32.91273],[109.79049,32.87929],[109.86259,32.911],[110.02481,32.87482],[110.03391,32.86041],[110.13725,32.81238],[110.19218,32.62029],[110.04524,32.55144],[109.71393,32.61508],[109.55703,32.48543],[109.49901,32.3028],[109.62364,32.10177],[109.58381,31.72933],[109.72595,31.7083],[109.73522,31.58497],[110.12695,31.41108],[110.20042,31.15405],[110.10635,31.08601],[110.17192,30.97289],[110.06,30.78136],[109.99237,30.8869],[109.90825,30.909],[109.68303,30.77133],[109.55532,30.64825],[109.52236,30.66464],[109.35413,30.49128],[109.30469,30.63584],[109.13749,30.52086],[109.08256,30.59418],[109.11861,30.6385],[109.04273,30.65637],[108.96703,30.62978],[108.82644,30.50193],[108.72396,30.50282],[108.68225,30.59019],[108.6383,30.58398],[108.63796,30.53979],[108.58268,30.49276],[108.41789,30.49306],[108.40072,30.38827],[108.57822,30.25906],[108.51299,29.87548],[108.38905,29.86759],[108.3657,29.82411]]]}},{"type":"Feature","properties":{"id":"CN-HA"},"geometry":{"type":"Polygon","coordinates":[[[110.35388,34.519],[110.64056,34.16096],[110.57979,34.04184],[110.66802,33.95304],[110.58734,33.87184],[110.73497,33.8168],[110.83797,33.66778],[111.0189,33.56771],[111.02045,33.33884],[110.97358,33.25806],[111.05478,33.19431],[111.03109,33.15551],[111.11735,33.16349],[111.12593,33.1443],[111.18181,33.10692],[111.13821,33.04824],[111.24069,33.03644],[111.25717,32.96892],[111.22352,32.91533],[111.37115,32.83171],[111.41201,32.75797],[111.46488,32.73732],[111.56101,32.59194],[111.66709,32.6252],[111.82382,32.53118],[111.85644,32.53263],[111.87171,32.50657],[111.94587,32.51497],[112.00012,32.45415],[112.05436,32.47443],[112.1038,32.40503],[112.14998,32.3788],[112.21435,32.39358],[112.32009,32.32572],[112.35614,32.373],[112.44094,32.34574],[112.55149,32.39851],[112.70359,32.35502],[113.03386,32.42431],[113.12725,32.38083],[113.1887,32.43155],[113.41701,32.27145],[113.58661,32.35226],[113.71467,32.43329],[113.76583,32.30686],[113.73355,32.2581],[113.76411,32.17357],[113.72016,32.0881],[113.78471,32.04271],[113.75278,31.9906],[113.8314,31.85568],[113.92684,31.87959],[113.9804,31.75298],[114.17369,31.85568],[114.22759,31.83994],[114.28699,31.75765],[114.48509,31.73926],[114.56354,31.77006],[114.53882,31.55703],[114.61915,31.58701],[114.6916,31.52821],[114.76601,31.52353],[114.78146,31.48255],[114.83502,31.45971],[114.84952,31.48606],[114.87373,31.47003],[114.91518,31.48386],[114.93222,31.46959],[114.93887,31.48383],[114.97192,31.50384],[114.98557,31.47076],[115.0187,31.52945],[115.04754,31.51541],[115.07916,31.51017],[115.09427,31.51061],[115.08882,31.5198],[115.10873,31.53223],[115.12942,31.60544],[115.17178,31.60306],[115.22769,31.55762],[115.21379,31.51226],[115.2089,31.43357],[115.24692,31.42404],[115.2567,31.41189],[115.24297,31.39262],[115.25859,31.39049],[115.28151,31.40456],[115.30151,31.38566],[115.3694,31.40639],[115.38288,31.44741],[115.36811,31.49938],[115.48124,31.62999],[115.49119,31.67573],[115.63651,31.76006],[115.72946,31.76466],[115.75384,31.79005],[115.81014,31.76378],[115.88421,31.77918],[115.91262,31.80843],[115.88756,31.8446],[115.93872,32.06861],[115.92275,32.1006],[115.93151,32.15381],[115.91846,32.19246],[115.89443,32.39489],[115.86258,32.4674],[115.88275,32.49644],[115.84344,32.50947],[115.92241,32.57213],[115.89117,32.58008],[115.85426,32.54059],[115.78439,32.47761],[115.76671,32.5073],[115.74216,32.47819],[115.69152,32.49456],[115.69805,32.4711],[115.62097,32.4075],[115.60037,32.42851],[115.55385,32.40416],[115.46888,32.52105],[115.39961,32.57683],[115.30649,32.55969],[115.28743,32.58703],[115.1889,32.59946],[115.19834,32.669],[115.17414,32.69082],[115.19662,32.85262],[114.88128,32.97295],[114.90428,33.16385],[114.95595,33.15293],[115.00831,33.09988],[115.14427,33.08866],[115.29396,33.14933],[115.2931,33.19948],[115.33258,33.26266],[115.30803,33.42857],[115.36159,33.51993],[115.41618,33.55741],[115.63316,33.58602],[115.56278,33.77686],[115.60947,33.78314],[115.6232,33.87611],[115.53909,33.87754],[115.59059,34.02563],[115.72843,34.07669],[115.99536,33.97639],[115.97751,33.90547],[116.03691,33.87725],[116.05545,33.85787],[116.05373,33.80625],[116.15312,33.7092],[116.26298,33.73747],[116.32375,33.77258],[116.43688,33.80054],[116.4525,33.8496],[116.58124,33.89934],[116.63806,33.8858],[116.64613,33.97326],[116.52803,34.11876],[116.55378,34.17275],[116.53781,34.23451],[116.57867,34.27537],[116.51721,34.29523],[116.36924,34.27565],[116.34916,34.33308],[116.21852,34.38027],[116.16153,34.45476],[116.19655,34.56905],[116.0091,34.60269],[115.82199,34.56199],[115.69255,34.60071],[115.6826,34.55718],[115.52192,34.57782],[115.45326,34.65015],[115.42373,34.81831],[115.2404,34.84846],[115.24623,34.91042],[115.18856,34.91296],[115.21739,34.9563],[115.15079,34.96418],[115.11749,35.00609],[114.96162,34.9774],[114.82841,35.00806],[114.87785,35.1067],[114.83631,35.16595],[114.92317,35.2027],[114.93381,35.25206],[114.9932,35.28037],[115.1065,35.42095],[115.23078,35.41661],[115.28589,35.47087],[115.35524,35.49366],[115.34185,35.56337],[115.39489,35.56295],[115.41618,35.65311],[115.69942,35.7635],[115.70526,35.84815],[115.74783,35.82505],[115.7722,35.86011],[115.84567,35.84954],[115.8879,35.88501],[115.86044,35.91477],[115.90301,35.92714],[115.90421,35.9605],[116.04034,35.96703],[116.05871,36.03605],[116.10557,36.07768],[116.09029,36.11832],[115.98609,36.04673],[115.88705,36.02647],[115.85151,36.00342],[115.81529,36.01606],[115.64809,35.92909],[115.63831,35.90698],[115.62784,35.92172],[115.57376,35.91268],[115.46047,35.8647],[115.36519,35.77395],[115.32691,35.79553],[115.36176,35.91783],[115.35129,35.96258],[115.43609,36.00995],[115.47935,36.14951],[115.30288,36.08378],[115.24108,36.19109],[115.11474,36.1897],[115.03578,36.10598],[114.98737,36.06602],[114.90978,36.04896],[114.92291,36.09523],[114.90257,36.11603],[114.90617,36.14009],[114.85794,36.14633],[114.85107,36.12983],[114.76026,36.12567],[114.734,36.15478],[114.68267,36.13829],[114.631,36.13565],[114.62345,36.12303],[114.60851,36.12761],[114.5771,36.11804],[114.5795,36.13981],[114.48354,36.18236],[114.45573,36.19926],[114.41213,36.203],[114.3984,36.22641],[114.38123,36.21671],[114.32991,36.25604],[114.31875,36.24067],[114.20837,36.26988],[114.19464,36.23845],[114.13404,36.27693],[114.05336,36.27583],[114.05284,36.32314],[114.02915,36.32978],[114.02675,36.3555],[114.00375,36.34319],[113.978,36.35937],[113.72446,36.35785],[113.64978,36.15506],[113.68892,35.98995],[113.63227,35.9905],[113.65287,35.83507],[113.58146,35.8256],[113.58249,35.744],[113.62163,35.61907],[113.53649,35.65255],[113.49666,35.5251],[113.28329,35.47534],[113.29822,35.42738],[113.18527,35.44948],[113.11557,35.33165],[112.99095,35.37253],[112.91576,35.25487],[112.81276,35.25851],[112.73929,35.20775],[112.62428,35.26216],[112.6126,35.22234],[112.34207,35.22038],[112.05505,35.27981],[112.05265,35.05248],[111.82228,35.07159],[111.58058,34.85889],[111.4956,34.83099],[111.3842,34.81662],[111.33579,34.83353],[111.22558,34.79153],[111.15005,34.81859],[111.1037,34.75684],[111.03813,34.74922],[110.82458,34.62529],[110.375,34.60283],[110.36161,34.56905],[110.39869,34.56142],[110.35388,34.519]]]}},{"type":"Feature","properties":{"id":"CN-SD"},"geometry":{"type":"Polygon","coordinates":[[[114.82841,35.00806],[114.96162,34.9774],[115.11749,35.00609],[115.15079,34.96418],[115.21739,34.9563],[115.18856,34.91296],[115.24623,34.91042],[115.2404,34.84846],[115.42373,34.81831],[115.45326,34.65015],[115.52192,34.57782],[115.6826,34.55718],[115.69255,34.60071],[115.82199,34.56199],[116.0091,34.60269],[116.19655,34.56905],[116.3689,34.63659],[116.41456,34.89804],[116.6621,34.93857],[116.77505,34.91831],[116.78398,34.97965],[116.96456,34.88367],[117.16575,34.6558],[117.5077,34.47033],[117.8112,34.54106],[117.79197,34.65015],[118.10028,34.65298],[118.17237,34.37801],[118.39416,34.43239],[118.49372,34.69138],[118.74847,34.71621],[118.88442,35.04349],[119.30259,35.07833],[122.7354,36.70228],[123.90497,38.79949],[120.97044,38.45359],[117.84484,38.26514],[117.81515,38.26392],[117.80107,38.22469],[117.76485,38.13347],[117.73824,38.12334],[117.70494,38.07755],[117.56023,38.06295],[117.50444,37.9427],[117.42685,37.83866],[117.32522,37.86319],[117.26497,37.83825],[117.17708,37.84896],[117.15957,37.83825],[117.07014,37.84856],[117.02156,37.83175],[116.91186,37.84585],[116.83427,37.83581],[116.81161,37.84327],[116.75548,37.80612],[116.74501,37.75985],[116.67274,37.72252],[116.65695,37.68558],[116.60837,37.6319],[116.57026,37.60906],[116.487,37.53137],[116.45456,37.51653],[116.42263,37.47662],[116.39774,37.50877],[116.36804,37.52361],[116.36993,37.56308],[116.33371,37.5756],[116.29371,37.56036],[116.28152,37.48248],[116.26702,37.4786],[116.27423,37.46218],[116.2344,37.48861],[116.2199,37.47662],[116.23758,37.45428],[116.22865,37.42416],[116.25766,37.42811],[116.27757,37.4048],[116.23603,37.36497],[116.10214,37.38025],[115.97854,37.34041],[115.96034,37.23661],[115.90061,37.20626],[115.90713,37.17673],[115.87451,37.15156],[115.86971,37.0801],[115.77152,36.9924],[115.79177,36.96854],[115.75984,36.91668],[115.70079,36.87687],[115.67882,36.81423],[115.47866,36.75759],[115.27576,36.49142],[115.47214,36.26254],[115.47935,36.14951],[115.43609,36.00995],[115.35129,35.96258],[115.36176,35.91783],[115.32691,35.79553],[115.36519,35.77395],[115.46047,35.8647],[115.57376,35.91268],[115.62784,35.92172],[115.63831,35.90698],[115.64809,35.92909],[115.81529,36.01606],[115.85151,36.00342],[115.88705,36.02647],[115.98609,36.04673],[116.09029,36.11832],[116.10557,36.07768],[116.05871,36.03605],[116.04034,35.96703],[115.90421,35.9605],[115.90301,35.92714],[115.86044,35.91477],[115.8879,35.88501],[115.84567,35.84954],[115.7722,35.86011],[115.74783,35.82505],[115.70526,35.84815],[115.69942,35.7635],[115.41618,35.65311],[115.39489,35.56295],[115.34185,35.56337],[115.35524,35.49366],[115.28589,35.47087],[115.23078,35.41661],[115.1065,35.42095],[114.9932,35.28037],[114.93381,35.25206],[114.92317,35.2027],[114.83631,35.16595],[114.87785,35.1067],[114.82841,35.00806]]]}},{"type":"Feature","properties":{"id":"CN-JX"},"geometry":{"type":"Polygon","coordinates":[[[113.5921,27.42602],[113.62232,27.33731],[113.8702,27.38304],[113.86264,27.2888],[113.77715,27.11781],[113.91517,26.94716],[113.83895,26.79342],[113.85801,26.66786],[113.9023,26.6206],[114.104,26.57624],[114.08374,26.43461],[113.9368,26.15605],[114.20888,26.21166],[114.23086,26.15312],[114.01834,26.04398],[114.01525,25.90339],[113.90504,25.7113],[113.9756,25.5892],[113.94126,25.44668],[113.99963,25.4442],[114.02675,25.2568],[114.18331,25.3192],[114.29403,25.29561],[114.30707,25.33844],[114.3881,25.32277],[114.54465,25.42622],[114.6655,25.32633],[114.70739,25.32959],[114.74069,25.23537],[114.66207,25.20121],[114.7455,25.12539],[114.53624,25.04377],[114.39239,24.94621],[114.40269,24.90449],[114.32338,24.75743],[114.15824,24.64857],[114.33094,24.61736],[114.41659,24.4873],[114.65194,24.58771],[114.69932,24.5315],[114.74464,24.61986],[114.87596,24.56632],[114.92059,24.69334],[114.93826,24.64826],[115.06599,24.70753],[115.10822,24.66792],[115.27988,24.75774],[115.35507,24.74121],[115.40657,24.79234],[115.4845,24.75587],[115.56758,24.63016],[115.65444,24.61799],[115.69358,24.54056],[115.84327,24.5671],[115.78559,24.63703],[115.79761,24.70098],[115.76499,24.71221],[115.76362,24.79109],[115.82405,24.91539],[115.86696,24.86743],[115.89889,24.87833],[115.88722,24.94154],[115.8625,25.22544],[115.99966,25.32277],[116.00566,25.49968],[116.05373,25.56071],[116.03759,25.62883],[116.16325,25.77701],[116.12274,25.85366],[116.14265,25.87899],[116.36444,25.971],[116.48666,26.12153],[116.46915,26.17793],[116.3998,26.17361],[116.38572,26.23368],[116.51653,26.41278],[116.58485,26.36695],[116.63394,26.47871],[116.53953,26.56365],[116.55532,26.64009],[116.50005,26.70697],[116.55567,26.76477],[116.54125,26.83969],[116.68304,26.98847],[116.92645,27.02548],[117.05451,27.1062],[117.03838,27.15386],[117.17056,27.26943],[117.10224,27.34401],[117.12387,27.43364],[117.07923,27.56824],[117.01847,27.55332],[117.0037,27.63943],[117.03701,27.67349],[117.10121,27.62149],[117.1091,27.69508],[117.19699,27.67638],[117.30239,27.7756],[117.27836,27.87276],[117.32316,27.89901],[117.3369,27.85774],[117.52624,27.99046],[117.64297,27.83497],[117.74579,27.80962],[117.85016,27.94785],[118.08792,27.99167],[118.16413,28.05743],[118.35296,28.09409],[118.36875,28.19369],[118.31245,28.22878],[118.42738,28.29296],[118.48192,28.32772],[118.43218,28.40582],[118.47355,28.47691],[118.40283,28.50165],[118.44411,28.51214],[118.43433,28.52462],[118.42575,28.51908],[118.42171,28.53959],[118.40249,28.56609],[118.41892,28.58983],[118.40746,28.60211],[118.41716,28.61157],[118.42781,28.62913],[118.41931,28.64178],[118.42918,28.67982],[118.39227,28.71038],[118.38789,28.75757],[118.38008,28.759],[118.36292,28.81512],[118.32781,28.81752],[118.2849,28.83324],[118.29889,28.84347],[118.25134,28.92523],[118.18611,28.9084],[118.22293,28.94559],[118.16302,28.98877],[118.12697,28.98832],[118.06783,29.04191],[118.06011,29.08257],[118.03195,29.09832],[118.02423,29.17629],[118.07092,29.25719],[118.07092,29.29029],[118.1607,29.28475],[118.20585,29.35016],[118.18851,29.39743],[118.13358,29.4215],[118.12826,29.51416],[117.99848,29.57689],[117.88227,29.54986],[117.7003,29.55404],[117.66426,29.61137],[117.55542,29.59913],[117.44625,29.71146],[117.41552,29.85225],[117.26823,29.83215],[117.2581,29.90212],[117.20386,29.935],[117.07082,29.83498],[117.11717,29.73143],[116.78638,29.56121],[116.77016,29.59711],[116.71411,29.57315],[116.64939,29.70117],[116.91993,29.94541],[116.74381,30.0581],[116.63171,30.07875],[116.57936,30.04769],[116.54331,29.90658],[116.45902,29.89676],[116.25869,29.78523],[116.20445,29.83513],[116.13338,29.82783],[115.93803,29.71906],[115.82988,29.75245],[115.69152,29.84555],[115.51136,29.84064],[115.47025,29.81011],[115.45755,29.76318],[115.46854,29.75215],[115.39918,29.68253],[115.27301,29.62763],[115.09551,29.68238],[115.15045,29.59793],[115.15577,29.5046],[115.06496,29.56733],[114.94548,29.55688],[114.9575,29.50654],[114.89725,29.52925],[114.85639,29.48264],[114.88231,29.43899],[114.91527,29.48936],[114.94428,29.48458],[114.90703,29.40311],[114.67306,29.39085],[114.49367,29.3301],[114.25884,29.35165],[114.24974,29.23742],[114.06555,29.20911],[113.93714,29.05061],[113.96736,28.94243],[114.00349,28.95798],[114.0204,28.89563],[114.14932,28.77187],[114.07653,28.55919],[114.20408,28.49177],[114.25678,28.38339],[114.17472,28.26023],[113.99173,28.16736],[114.03945,28.06289],[113.74351,27.9477],[113.72085,27.8755],[113.7763,27.81903],[113.59691,27.62711],[113.62112,27.5139],[113.5921,27.42602]]]}},{"type":"Feature","properties":{"id":"CN-AH"},"geometry":{"type":"Polygon","coordinates":[[[114.88128,32.97295],[115.19662,32.85262],[115.17414,32.69082],[115.19834,32.669],[115.1889,32.59946],[115.28743,32.58703],[115.30649,32.55969],[115.39961,32.57683],[115.46888,32.52105],[115.55385,32.40416],[115.60037,32.42851],[115.62097,32.4075],[115.69805,32.4711],[115.69152,32.49456],[115.74216,32.47819],[115.76671,32.5073],[115.78439,32.47761],[115.85426,32.54059],[115.89117,32.58008],[115.92241,32.57213],[115.84344,32.50947],[115.88275,32.49644],[115.86258,32.4674],[115.89443,32.39489],[115.91846,32.19246],[115.93151,32.15381],[115.92275,32.1006],[115.93872,32.06861],[115.88756,31.8446],[115.91262,31.80843],[115.88421,31.77918],[115.81014,31.76378],[115.75384,31.79005],[115.72946,31.76466],[115.63651,31.76006],[115.49119,31.67573],[115.48124,31.62999],[115.36811,31.49938],[115.38288,31.44741],[115.3694,31.40639],[115.37567,31.34762],[115.43678,31.34894],[115.44425,31.31492],[115.45798,31.3202],[115.45309,31.28119],[115.52063,31.258],[115.53668,31.19209],[115.57823,31.14465],[115.64414,31.21911],[115.70165,31.20296],[115.77049,31.10409],[115.86679,31.15229],[115.89597,31.09292],[116.05287,31.0138],[116.06798,30.96009],[115.98712,30.93918],[115.84087,30.84358],[115.86164,30.77443],[115.77512,30.74891],[115.7698,30.6701],[115.90988,30.51968],[115.91777,30.42706],[115.88069,30.38398],[115.90627,30.30961],[115.9806,30.29183],[116.05922,30.20923],[116.0551,30.16086],[116.08222,30.12434],[116.07313,30.05958],[116.0733,29.964],[116.12531,29.90093],[116.13338,29.82783],[116.20445,29.83513],[116.25869,29.78523],[116.45902,29.89676],[116.54331,29.90658],[116.57936,30.04769],[116.63171,30.07875],[116.74381,30.0581],[116.91993,29.94541],[116.64939,29.70117],[116.71411,29.57315],[116.77016,29.59711],[116.78638,29.56121],[117.11717,29.73143],[117.07082,29.83498],[117.20386,29.935],[117.2581,29.90212],[117.26823,29.83215],[117.41552,29.85225],[117.44625,29.71146],[117.55542,29.59913],[117.66426,29.61137],[117.7003,29.55404],[117.88227,29.54986],[117.99848,29.57689],[118.12826,29.51416],[118.13358,29.4215],[118.18851,29.39743],[118.21563,29.42509],[118.31013,29.42494],[118.30464,29.49982],[118.3524,29.482],[118.38678,29.51043],[118.4242,29.50386],[118.43201,29.51192],[118.48978,29.51708],[118.49939,29.57733],[118.61371,29.65591],[118.63938,29.64464],[118.71311,29.71131],[118.74555,29.74932],[118.7289,29.80907],[118.83876,29.89319],[118.84134,29.9402],[118.8882,29.93738],[118.89816,30.02332],[118.847,30.16605],[118.91962,30.20419],[118.87927,30.24705],[118.8767,30.31302],[118.94983,30.35673],[119.05763,30.30857],[119.07875,30.32339],[119.22912,30.28871],[119.25109,30.3468],[119.37503,30.36487],[119.32542,30.53284],[119.23805,30.52988],[119.23788,30.61486],[119.31426,30.62506],[119.33658,30.66464],[119.38671,30.69018],[119.42258,30.63983],[119.47975,30.70745],[119.47957,30.77074],[119.52318,30.77989],[119.57382,30.85743],[119.5515,30.88513],[119.59476,30.97525],[119.63149,31.1329],[119.57656,31.11174],[119.50378,31.1611],[119.35478,31.19694],[119.37332,31.26607],[119.3486,31.3048],[119.34293,31.26211],[119.23358,31.25654],[119.17625,31.3029],[119.07257,31.23394],[118.78761,31.23394],[118.75963,31.27884],[118.69972,31.29982],[118.73851,31.37503],[118.82537,31.37943],[118.88992,31.42954],[118.8652,31.62415],[118.7931,31.62356],[118.761,31.701],[118.72959,31.62999],[118.64204,31.64812],[118.69354,31.7194],[118.47587,31.78246],[118.49956,31.84344],[118.45922,31.86122],[118.4781,31.88382],[118.40909,31.89475],[118.35588,31.93337],[118.3921,32.02088],[118.38008,32.06075],[118.49956,32.12997],[118.49304,32.19798],[118.64204,32.20815],[118.68873,32.35009],[118.6853,32.47385],[118.58539,32.48022],[118.60702,32.52973],[118.55792,32.57169],[118.59329,32.60062],[118.75293,32.60698],[118.77559,32.58616],[118.81095,32.60351],[118.83499,32.57256],[118.90537,32.59252],[118.88614,32.55433],[118.93215,32.56012],[118.96785,32.50773],[119.0269,32.51699],[119.07823,32.45256],[119.1469,32.50773],[119.15256,32.56576],[119.17676,32.59657],[119.21539,32.57661],[119.17762,32.83286],[119.04819,32.87266],[119.01077,32.95884],[118.91601,32.94414],[118.84288,32.96273],[118.84151,32.92008],[118.80632,32.91763],[118.80804,32.85911],[118.73405,32.85449],[118.75053,32.73602],[118.36532,32.72028],[118.35914,32.77111],[118.30593,32.77399],[118.29528,32.85161],[118.24275,32.85276],[118.21975,32.92916],[118.30902,32.96085],[118.2431,32.98073],[118.21598,33.19273],[118.17134,33.22116],[118.15315,33.17491],[118.04569,33.13898],[117.93891,33.23294],[118.04466,33.49216],[118.10302,33.47641],[118.11778,33.62205],[118.18885,33.75916],[117.72262,33.73262],[117.75936,33.8935],[117.70408,33.88723],[117.61894,34.03502],[117.56469,33.98123],[117.50907,34.06176],[117.40917,34.02107],[117.35732,34.08365],[117.17262,34.07711],[117.08816,34.14221],[117.02396,34.16664],[117.04353,34.24217],[116.97521,34.27537],[116.96113,34.38424],[116.3689,34.63659],[116.19655,34.56905],[116.16153,34.45476],[116.21852,34.38027],[116.34916,34.33308],[116.36924,34.27565],[116.51721,34.29523],[116.57867,34.27537],[116.53781,34.23451],[116.55378,34.17275],[116.52803,34.11876],[116.64613,33.97326],[116.63806,33.8858],[116.58124,33.89934],[116.4525,33.8496],[116.43688,33.80054],[116.32375,33.77258],[116.26298,33.73747],[116.15312,33.7092],[116.05373,33.80625],[116.05545,33.85787],[116.03691,33.87725],[115.97751,33.90547],[115.99536,33.97639],[115.72843,34.07669],[115.59059,34.02563],[115.53909,33.87754],[115.6232,33.87611],[115.60947,33.78314],[115.56278,33.77686],[115.63316,33.58602],[115.41618,33.55741],[115.36159,33.51993],[115.30803,33.42857],[115.33258,33.26266],[115.2931,33.19948],[115.29396,33.14933],[115.14427,33.08866],[115.00831,33.09988],[114.95595,33.15293],[114.90428,33.16385],[114.88128,32.97295]]]}},{"type":"Feature","properties":{"id":"CN-FJ"},"geometry":{"type":"Polygon","coordinates":[[[115.8625,25.22544],[115.88722,24.94154],[115.97047,24.91773],[116.08257,24.8489],[116.20428,24.85933],[116.25543,24.80247],[116.3404,24.83161],[116.35808,24.88861],[116.41267,24.84687],[116.36873,24.80933],[116.48735,24.67977],[116.51859,24.60332],[116.58931,24.6506],[116.80269,24.67821],[116.74879,24.5529],[116.84131,24.48418],[116.85075,24.42214],[116.9086,24.32473],[116.91564,24.28702],[116.92886,24.28812],[116.93195,24.22238],[116.99701,24.18935],[116.92251,24.12043],[116.95238,24.01651],[116.97933,24.00099],[116.97229,23.93244],[116.95263,23.9173],[116.97246,23.88167],[116.96156,23.86134],[117.00662,23.86134],[117.02181,23.82994],[117.03743,23.76751],[117.04825,23.73789],[117.05065,23.69137],[117.0637,23.69247],[117.11417,23.65513],[117.16206,23.65183],[117.19253,23.61904],[117.76968,23.10828],[118.41371,24.06775],[118.179,24.33015],[118.09488,24.38193],[118.28244,24.51231],[118.35291,24.51645],[118.42453,24.54644],[118.56434,24.49266],[120.49232,25.22863],[121.03532,26.8787],[120.4323,27.16745],[120.4026,27.20395],[120.4062,27.23082],[120.39316,27.25279],[120.42715,27.25951],[120.40105,27.28163],[120.40346,27.29613],[120.34732,27.34661],[120.33994,27.40331],[120.26621,27.39158],[120.2554,27.43661],[120.21343,27.42122],[120.19309,27.43044],[120.17197,27.42434],[120.13051,27.42526],[120.12914,27.39767],[120.09962,27.39783],[120.04177,27.34035],[119.98855,27.38411],[119.94169,27.35667],[119.9374,27.32083],[119.87028,27.3097],[119.84075,27.32633],[119.83028,27.30482],[119.77741,27.30833],[119.69432,27.44004],[119.69758,27.53978],[119.65364,27.54008],[119.67012,27.57767],[119.62789,27.58498],[119.62986,27.6798],[119.49897,27.65433],[119.48043,27.55439],[119.44301,27.51085],[119.36954,27.53293],[119.25916,27.42571],[119.12475,27.43394],[119.00012,27.50004],[118.8961,27.46624],[118.89678,27.64643],[118.79722,27.93117],[118.72066,27.97636],[118.71585,28.06773],[118.80409,28.15071],[118.75688,28.18022],[118.80924,28.23196],[118.73302,28.25842],[118.71242,28.31601],[118.63637,28.2501],[118.54428,28.28601],[118.53999,28.26515],[118.49158,28.27906],[118.49244,28.24186],[118.43544,28.25184],[118.42738,28.29296],[118.31245,28.22878],[118.36875,28.19369],[118.35296,28.09409],[118.16413,28.05743],[118.08792,27.99167],[117.85016,27.94785],[117.74579,27.80962],[117.64297,27.83497],[117.52624,27.99046],[117.3369,27.85774],[117.32316,27.89901],[117.27836,27.87276],[117.30239,27.7756],[117.19699,27.67638],[117.1091,27.69508],[117.10121,27.62149],[117.03701,27.67349],[117.0037,27.63943],[117.01847,27.55332],[117.07923,27.56824],[117.12387,27.43364],[117.10224,27.34401],[117.17056,27.26943],[117.03838,27.15386],[117.05451,27.1062],[116.92645,27.02548],[116.68304,26.98847],[116.54125,26.83969],[116.55567,26.76477],[116.50005,26.70697],[116.55532,26.64009],[116.53953,26.56365],[116.63394,26.47871],[116.58485,26.36695],[116.51653,26.41278],[116.38572,26.23368],[116.3998,26.17361],[116.46915,26.17793],[116.48666,26.12153],[116.36444,25.971],[116.14265,25.87899],[116.12274,25.85366],[116.16325,25.77701],[116.03759,25.62883],[116.05373,25.56071],[116.00566,25.49968],[115.99966,25.32277],[115.8625,25.22544]]]}},{"type":"Feature","properties":{"id":"CN-ZJ"},"geometry":{"type":"Polygon","coordinates":[[[118.02423,29.17629],[118.03195,29.09832],[118.06011,29.08257],[118.06783,29.04191],[118.12697,28.98832],[118.16302,28.98877],[118.22293,28.94559],[118.18611,28.9084],[118.25134,28.92523],[118.29889,28.84347],[118.2849,28.83324],[118.32781,28.81752],[118.36292,28.81512],[118.38008,28.759],[118.38789,28.75757],[118.39227,28.71038],[118.42918,28.67982],[118.41931,28.64178],[118.42781,28.62913],[118.41716,28.61157],[118.40746,28.60211],[118.41892,28.58983],[118.40249,28.56609],[118.42171,28.53959],[118.42575,28.51908],[118.43433,28.52462],[118.44411,28.51214],[118.40283,28.50165],[118.47355,28.47691],[118.43218,28.40582],[118.48192,28.32772],[118.42738,28.29296],[118.43544,28.25184],[118.49244,28.24186],[118.49158,28.27906],[118.53999,28.26515],[118.54428,28.28601],[118.63637,28.2501],[118.71242,28.31601],[118.73302,28.25842],[118.80924,28.23196],[118.75688,28.18022],[118.80409,28.15071],[118.71585,28.06773],[118.72066,27.97636],[118.79722,27.93117],[118.89678,27.64643],[118.8961,27.46624],[119.00012,27.50004],[119.12475,27.43394],[119.25916,27.42571],[119.36954,27.53293],[119.44301,27.51085],[119.48043,27.55439],[119.49897,27.65433],[119.62986,27.6798],[119.62789,27.58498],[119.67012,27.57767],[119.65364,27.54008],[119.69758,27.53978],[119.69432,27.44004],[119.77741,27.30833],[119.83028,27.30482],[119.84075,27.32633],[119.87028,27.3097],[119.9374,27.32083],[119.94169,27.35667],[119.98855,27.38411],[120.04177,27.34035],[120.09962,27.39783],[120.12914,27.39767],[120.13051,27.42526],[120.17197,27.42434],[120.19309,27.43044],[120.21343,27.42122],[120.2554,27.43661],[120.26621,27.39158],[120.33994,27.40331],[120.34732,27.34661],[120.40346,27.29613],[120.40105,27.28163],[120.42715,27.25951],[120.39316,27.25279],[120.4062,27.23082],[120.4026,27.20395],[120.4323,27.16745],[121.03532,26.8787],[123.5458,31.01942],[121.2712,30.68309],[121.26399,30.73504],[121.20906,30.78756],[121.12152,30.78018],[121.12941,30.83768],[121.09319,30.85861],[121.03088,30.82574],[120.98573,30.82722],[121.01646,30.88042],[120.98693,30.89706],[120.99552,30.94301],[120.98831,30.97039],[120.99706,30.97996],[120.98376,31.02079],[120.89338,31.01983],[120.8884,31.00549],[120.84368,30.99188],[120.8242,31.00836],[120.78266,31.00527],[120.76219,30.99313],[120.76583,30.97977],[120.75515,30.97823],[120.74116,30.96384],[120.72652,30.97363],[120.69193,30.97128],[120.67811,30.95847],[120.69313,30.95243],[120.70644,30.93285],[120.70987,30.88778],[120.69717,30.88557],[120.69756,30.8683],[120.68241,30.87887],[120.67944,30.88734],[120.65146,30.85736],[120.65129,30.84944],[120.62576,30.85872],[120.60044,30.85132],[120.58327,30.85839],[120.55598,30.83533],[120.50087,30.76012],[120.48414,30.76647],[120.4698,30.78822],[120.47161,30.80931],[120.45933,30.81041],[120.45289,30.84306],[120.43968,30.85736],[120.44706,30.87246],[120.43135,30.89088],[120.43041,30.92475],[120.41187,30.93138],[120.41925,30.90384],[120.36071,30.88211],[120.35016,30.90355],[120.35934,30.92534],[120.3547,30.93403],[120.36827,30.94846],[119.91439,31.17051],[119.65793,31.16522],[119.63149,31.1329],[119.59476,30.97525],[119.5515,30.88513],[119.57382,30.85743],[119.52318,30.77989],[119.47957,30.77074],[119.47975,30.70745],[119.42258,30.63983],[119.38671,30.69018],[119.33658,30.66464],[119.31426,30.62506],[119.23788,30.61486],[119.23805,30.52988],[119.32542,30.53284],[119.37503,30.36487],[119.25109,30.3468],[119.22912,30.28871],[119.07875,30.32339],[119.05763,30.30857],[118.94983,30.35673],[118.8767,30.31302],[118.87927,30.24705],[118.91962,30.20419],[118.847,30.16605],[118.89816,30.02332],[118.8882,29.93738],[118.84134,29.9402],[118.83876,29.89319],[118.7289,29.80907],[118.74555,29.74932],[118.71311,29.71131],[118.63938,29.64464],[118.61371,29.65591],[118.49939,29.57733],[118.48978,29.51708],[118.43201,29.51192],[118.4242,29.50386],[118.38678,29.51043],[118.3524,29.482],[118.30464,29.49982],[118.31013,29.42494],[118.21563,29.42509],[118.18851,29.39743],[118.20585,29.35016],[118.1607,29.28475],[118.07092,29.29029],[118.07092,29.25719],[118.02423,29.17629]]]}},{"type":"Feature","properties":{"id":"CN-JS"},"geometry":{"type":"Polygon","coordinates":[[[116.3689,34.63659],[116.96113,34.38424],[116.97521,34.27537],[117.04353,34.24217],[117.02396,34.16664],[117.08816,34.14221],[117.17262,34.07711],[117.35732,34.08365],[117.40917,34.02107],[117.50907,34.06176],[117.56469,33.98123],[117.61894,34.03502],[117.70408,33.88723],[117.75936,33.8935],[117.72262,33.73262],[118.18885,33.75916],[118.11778,33.62205],[118.10302,33.47641],[118.04466,33.49216],[117.93891,33.23294],[118.04569,33.13898],[118.15315,33.17491],[118.17134,33.22116],[118.21598,33.19273],[118.2431,32.98073],[118.30902,32.96085],[118.21975,32.92916],[118.24275,32.85276],[118.29528,32.85161],[118.30593,32.77399],[118.35914,32.77111],[118.36532,32.72028],[118.75053,32.73602],[118.73405,32.85449],[118.80804,32.85911],[118.80632,32.91763],[118.84151,32.92008],[118.84288,32.96273],[118.91601,32.94414],[119.01077,32.95884],[119.04819,32.87266],[119.17762,32.83286],[119.21539,32.57661],[119.17676,32.59657],[119.15256,32.56576],[119.1469,32.50773],[119.07823,32.45256],[119.0269,32.51699],[118.96785,32.50773],[118.93215,32.56012],[118.88614,32.55433],[118.90537,32.59252],[118.83499,32.57256],[118.81095,32.60351],[118.77559,32.58616],[118.75293,32.60698],[118.59329,32.60062],[118.55792,32.57169],[118.60702,32.52973],[118.58539,32.48022],[118.6853,32.47385],[118.68873,32.35009],[118.64204,32.20815],[118.49304,32.19798],[118.49956,32.12997],[118.38008,32.06075],[118.3921,32.02088],[118.35588,31.93337],[118.40909,31.89475],[118.4781,31.88382],[118.45922,31.86122],[118.49956,31.84344],[118.47587,31.78246],[118.69354,31.7194],[118.64204,31.64812],[118.72959,31.62999],[118.761,31.701],[118.7931,31.62356],[118.8652,31.62415],[118.88992,31.42954],[118.82537,31.37943],[118.73851,31.37503],[118.69972,31.29982],[118.75963,31.27884],[118.78761,31.23394],[119.07257,31.23394],[119.17625,31.3029],[119.23358,31.25654],[119.34293,31.26211],[119.3486,31.3048],[119.37332,31.26607],[119.35478,31.19694],[119.50378,31.1611],[119.57656,31.11174],[119.63149,31.1329],[119.65793,31.16522],[119.91439,31.17051],[120.36827,30.94846],[120.3547,30.93403],[120.35934,30.92534],[120.35016,30.90355],[120.36071,30.88211],[120.41925,30.90384],[120.41187,30.93138],[120.43041,30.92475],[120.43135,30.89088],[120.44706,30.87246],[120.43968,30.85736],[120.45289,30.84306],[120.45933,30.81041],[120.47161,30.80931],[120.4698,30.78822],[120.48414,30.76647],[120.50087,30.76012],[120.55598,30.83533],[120.58327,30.85839],[120.60044,30.85132],[120.62576,30.85872],[120.65129,30.84944],[120.65146,30.85736],[120.67944,30.88734],[120.68241,30.87887],[120.69756,30.8683],[120.69717,30.88557],[120.70987,30.88778],[120.70644,30.93285],[120.69313,30.95243],[120.67811,30.95847],[120.69193,30.97128],[120.72652,30.97363],[120.74116,30.96384],[120.75515,30.97823],[120.76583,30.97977],[120.76219,30.99313],[120.78266,31.00527],[120.8242,31.00836],[120.84368,30.99188],[120.8884,31.00549],[120.89338,31.01983],[120.8975,31.08778],[120.85218,31.1063],[120.87724,31.1376],[120.92788,31.14392],[120.9756,31.13334],[121.0247,31.14421],[121.0356,31.13973],[121.04126,31.15626],[121.06212,31.15156],[121.07276,31.16345],[121.05903,31.23658],[121.05817,31.26996],[121.07585,31.27297],[121.08418,31.2938],[121.09705,31.27642],[121.11328,31.2872],[121.14229,31.27679],[121.15722,31.28515],[121.14057,31.31038],[121.12581,31.30348],[121.12237,31.32042],[121.12787,31.33303],[121.10143,31.36147],[121.10959,31.37591],[121.14289,31.388],[121.15224,31.41203],[121.14246,31.44492],[121.23661,31.4947],[121.25335,31.47933],[121.29275,31.49345],[121.30099,31.5067],[121.32425,31.5056],[121.3821,31.54723],[121.09954,31.75853],[121.30554,31.88338],[121.43188,31.76962],[121.76147,31.63818],[122.29378,31.76513],[122.80525,33.30571],[119.30259,35.07833],[118.88442,35.04349],[118.74847,34.71621],[118.49372,34.69138],[118.39416,34.43239],[118.17237,34.37801],[118.10028,34.65298],[117.79197,34.65015],[117.8112,34.54106],[117.5077,34.47033],[117.16575,34.6558],[116.96456,34.88367],[116.78398,34.97965],[116.77505,34.91831],[116.6621,34.93857],[116.41456,34.89804],[116.3689,34.63659]]]}},{"type":"Feature","properties":{"id":"CN-SH"},"geometry":{"type":"Polygon","coordinates":[[[120.85218,31.1063],[120.8975,31.08778],[120.89338,31.01983],[120.98376,31.02079],[120.99706,30.97996],[120.98831,30.97039],[120.99552,30.94301],[120.98693,30.89706],[121.01646,30.88042],[120.98573,30.82722],[121.03088,30.82574],[121.09319,30.85861],[121.12941,30.83768],[121.12152,30.78018],[121.20906,30.78756],[121.26399,30.73504],[121.2712,30.68309],[123.5458,31.01942],[122.29378,31.76513],[121.76147,31.63818],[121.43188,31.76962],[121.30554,31.88338],[121.09954,31.75853],[121.3821,31.54723],[121.32425,31.5056],[121.30099,31.5067],[121.29275,31.49345],[121.25335,31.47933],[121.23661,31.4947],[121.14246,31.44492],[121.15224,31.41203],[121.14289,31.388],[121.10959,31.37591],[121.10143,31.36147],[121.12787,31.33303],[121.12237,31.32042],[121.12581,31.30348],[121.14057,31.31038],[121.15722,31.28515],[121.14229,31.27679],[121.11328,31.2872],[121.09705,31.27642],[121.08418,31.2938],[121.07585,31.27297],[121.05817,31.26996],[121.05903,31.23658],[121.07276,31.16345],[121.06212,31.15156],[121.04126,31.15626],[121.0356,31.13973],[121.0247,31.14421],[120.9756,31.13334],[120.92788,31.14392],[120.87724,31.1376],[120.85218,31.1063]]]}},{"type":"Feature","properties":{"id":"CN-SX"},"geometry":{"type":"Polygon","coordinates":[[[110.226,34.70309],[110.27406,34.61611],[110.375,34.60283],[110.82458,34.62529],[111.03813,34.74922],[111.1037,34.75684],[111.15005,34.81859],[111.22558,34.79153],[111.33579,34.83353],[111.3842,34.81662],[111.4956,34.83099],[111.58058,34.85889],[111.82228,35.07159],[112.05265,35.05248],[112.05505,35.27981],[112.34207,35.22038],[112.6126,35.22234],[112.62428,35.26216],[112.73929,35.20775],[112.81276,35.25851],[112.91576,35.25487],[112.99095,35.37253],[113.11557,35.33165],[113.18527,35.44948],[113.29822,35.42738],[113.28329,35.47534],[113.49666,35.5251],[113.53649,35.65255],[113.62163,35.61907],[113.58249,35.744],[113.58146,35.8256],[113.65287,35.83507],[113.63227,35.9905],[113.68892,35.98995],[113.64978,36.15506],[113.72446,36.35785],[113.58421,36.46133],[113.58489,36.54991],[113.49014,36.72815],[113.78917,36.88071],[113.75587,36.95071],[113.78162,37.04805],[113.75209,37.07408],[113.76892,37.14991],[113.82316,37.17344],[113.97766,37.41434],[114.11636,37.59219],[114.127,37.69387],[114.02984,37.72972],[113.87706,38.02483],[113.82797,38.16263],[113.55194,38.24626],[113.54026,38.51378],[113.61648,38.64865],[113.69424,38.65267],[113.7078,38.70641],[113.76565,38.706],[113.76068,38.73346],[113.82522,38.76238],[113.84342,38.82767],[113.78608,38.86751],[113.75518,38.94499],[114.03224,39.12766],[114.11464,39.05091],[114.3505,39.07251],[114.37866,39.17092],[114.4638,39.19501],[114.42192,39.2886],[114.56748,39.57076],[114.42363,39.60555],[114.39788,39.64165],[114.4075,39.77107],[114.38981,39.86627],[114.20494,39.86442],[114.21695,39.91026],[114.04083,39.89709],[114.02435,39.98606],[113.9059,40.00224],[113.90556,40.01814],[113.95774,40.03523],[113.97422,40.11431],[114.05834,40.06624],[114.09713,40.07623],[114.06005,40.18228],[114.44286,40.2528],[114.5565,40.34523],[114.30519,40.36695],[114.27909,40.42721],[114.29454,40.44093],[114.27738,40.52528],[114.15361,40.71304],[114.12425,40.74517],[114.05731,40.71668],[114.07001,40.5365],[113.94985,40.52006],[113.87157,40.44668],[113.7981,40.5151],[113.6721,40.4425],[113.54249,40.33607],[113.31109,40.3184],[113.24809,40.41349],[112.88761,40.32822],[112.84572,40.20169],[112.73963,40.1626],[112.61947,40.23891],[112.45399,40.29995],[112.30293,40.25463],[112.10758,39.97527],[112.06689,39.91197],[112.04097,39.89511],[112.03222,39.85467],[111.96407,39.79284],[111.91549,39.61388],[111.77799,39.58822],[111.7143,39.60383],[111.66478,39.64112],[111.60924,39.63358],[111.49929,39.66088],[111.43295,39.64006],[111.42488,39.5096],[111.36463,39.47966],[111.34162,39.42041],[111.21288,39.42638],[111.12945,39.4025],[111.11881,39.36403],[111.19228,39.30508],[111.23897,39.30003],[111.13718,39.07011],[111.09134,39.02985],[111.04637,39.02158],[110.97495,38.97836],[111.00963,38.90706],[110.95745,38.75836],[110.88775,38.65522],[110.87333,38.45842],[110.80192,38.44713],[110.5149,38.20905],[110.49688,38.02213],[110.5204,38.00252],[110.51198,37.9603],[110.58906,37.92632],[110.69789,37.70881],[110.79711,37.65827],[110.75626,37.63516],[110.78098,37.55682],[110.74665,37.45169],[110.6409,37.42852],[110.67798,37.29481],[110.45997,37.04503],[110.38066,37.01242],[110.4095,36.89692],[110.37586,36.87865],[110.44538,36.7287],[110.38873,36.68521],[110.49602,36.52922],[110.43594,36.1606],[110.60348,35.62576],[110.39371,35.29215],[110.36281,35.13451],[110.22891,34.89437],[110.226,34.70309]]]}},{"type":"Feature","properties":{"id":"CN-HE"},"geometry":{"type":"Polygon","coordinates":[[[113.49014,36.72815],[113.58489,36.54991],[113.58421,36.46133],[113.72446,36.35785],[113.978,36.35937],[114.00375,36.34319],[114.02675,36.3555],[114.02915,36.32978],[114.05284,36.32314],[114.05336,36.27583],[114.13404,36.27693],[114.19464,36.23845],[114.20837,36.26988],[114.31875,36.24067],[114.32991,36.25604],[114.38123,36.21671],[114.3984,36.22641],[114.41213,36.203],[114.45573,36.19926],[114.48354,36.18236],[114.5795,36.13981],[114.5771,36.11804],[114.60851,36.12761],[114.62345,36.12303],[114.631,36.13565],[114.68267,36.13829],[114.734,36.15478],[114.76026,36.12567],[114.85107,36.12983],[114.85794,36.14633],[114.90617,36.14009],[114.90257,36.11603],[114.92291,36.09523],[114.90978,36.04896],[114.98737,36.06602],[115.03578,36.10598],[115.11474,36.1897],[115.24108,36.19109],[115.30288,36.08378],[115.47935,36.14951],[115.47214,36.26254],[115.27576,36.49142],[115.47866,36.75759],[115.67882,36.81423],[115.70079,36.87687],[115.75984,36.91668],[115.79177,36.96854],[115.77152,36.9924],[115.86971,37.0801],[115.87451,37.15156],[115.90713,37.17673],[115.90061,37.20626],[115.96034,37.23661],[115.97854,37.34041],[116.10214,37.38025],[116.23603,37.36497],[116.27757,37.4048],[116.25766,37.42811],[116.22865,37.42416],[116.23758,37.45428],[116.2199,37.47662],[116.2344,37.48861],[116.27423,37.46218],[116.26702,37.4786],[116.28152,37.48248],[116.29371,37.56036],[116.33371,37.5756],[116.36993,37.56308],[116.36804,37.52361],[116.39774,37.50877],[116.42263,37.47662],[116.45456,37.51653],[116.487,37.53137],[116.57026,37.60906],[116.60837,37.6319],[116.65695,37.68558],[116.67274,37.72252],[116.74501,37.75985],[116.75548,37.80612],[116.81161,37.84327],[116.83427,37.83581],[116.91186,37.84585],[117.02156,37.83175],[117.07014,37.84856],[117.15957,37.83825],[117.17708,37.84896],[117.26497,37.83825],[117.32522,37.86319],[117.42685,37.83866],[117.50444,37.9427],[117.56023,38.06295],[117.70494,38.07755],[117.73824,38.12334],[117.76485,38.13347],[117.80107,38.22469],[117.81515,38.26392],[117.84484,38.26514],[118.33648,38.49229],[117.59559,38.61472],[117.47457,38.61579],[117.36299,38.56575],[117.24369,38.56024],[117.2545,38.60734],[117.21673,38.64328],[117.09468,38.58306],[117.05074,38.64395],[117.06018,38.67961],[117.02447,38.69998],[116.8717,38.68001],[116.85539,38.74658],[116.74827,38.74377],[116.74003,38.8508],[116.71754,38.85214],[116.70158,38.91387],[116.76012,39.04705],[116.8571,39.05411],[116.91839,39.12473],[116.90517,39.14949],[116.85934,39.15522],[116.85127,39.21403],[116.88611,39.224],[116.86225,39.30016],[116.88629,39.33429],[116.87101,39.3408],[116.86861,39.35819],[116.81951,39.33907],[116.82655,39.36177],[116.81247,39.36881],[116.83479,39.37836],[116.83427,39.41046],[116.87084,39.43327],[116.8226,39.43818],[116.81058,39.44958],[116.78037,39.46429],[116.82191,39.48629],[116.81608,39.5288],[116.78123,39.54958],[116.80526,39.57698],[116.78964,39.59047],[116.80732,39.61501],[116.77402,39.5906],[116.75342,39.616],[116.69128,39.61891],[116.72218,39.59272],[116.69952,39.58611],[116.68596,39.6],[116.61849,39.59775],[116.59961,39.62261],[116.56734,39.61917],[116.55189,39.59563],[116.53404,39.60469],[116.50288,39.55025],[116.46563,39.55157],[116.47044,39.53463],[116.45988,39.5292],[116.44289,39.44454],[116.33216,39.45422],[116.23809,39.51649],[116.21646,39.57671],[116.2035,39.57658],[116.17647,39.59127],[116.12952,39.56752],[116.09304,39.57407],[116.03081,39.57036],[116.01871,39.57354],[116.0188,39.58538],[116.01099,39.58617],[115.99296,39.57221],[115.98137,39.59272],[115.97373,39.59385],[115.96927,39.57056],[115.95236,39.56017],[115.90378,39.60529],[115.90576,39.56772],[115.88619,39.56791],[115.88232,39.55058],[115.86602,39.5421],[115.84997,39.55355],[115.81272,39.52993],[115.81795,39.50874],[115.74594,39.51178],[115.72895,39.54488],[115.69032,39.57036],[115.68054,39.60185],[115.6644,39.60978],[115.50802,39.59325],[115.51214,39.64297],[115.4742,39.65288],[115.48107,39.73333],[115.41206,39.77899],[115.58183,39.79996],[115.52089,39.83042],[115.49875,39.92053],[115.41807,39.94949],[115.44227,40.0146],[115.52124,40.08201],[115.76808,40.15736],[115.8467,40.14633],[115.88584,40.22659],[115.96052,40.26354],[115.91082,40.354],[115.85855,40.3591],[115.85383,40.37466],[115.76602,40.44786],[115.77495,40.4617],[115.73272,40.51145],[115.77907,40.56128],[115.81014,40.55893],[115.88705,40.61356],[115.97511,40.58384],[116.15604,40.66215],[116.2453,40.7808],[116.47533,40.76],[116.31689,40.92324],[116.36358,40.941],[116.40649,40.90313],[116.47602,40.89586],[116.44306,40.98145],[116.60862,40.9821],[116.62158,41.06019],[116.6827,41.04155],[116.69883,40.92103],[116.85024,40.83381],[116.97933,40.69209],[117.08009,40.70211],[117.19682,40.69326],[117.31853,40.65785],[117.39921,40.68792],[117.51628,40.65772],[117.47749,40.63167],[117.45174,40.65095],[117.42753,40.62281],[117.41432,40.63701],[117.41912,40.56376],[117.3072,40.57719],[117.24334,40.54759],[117.25536,40.51366],[117.20008,40.50949],[117.25639,40.43976],[117.23321,40.41885],[117.21776,40.37257],[117.23596,40.36786],[117.29055,40.27743],[117.32814,40.2879],[117.33896,40.22948],[117.3829,40.22685],[117.4308,40.25228],[117.55645,40.22594],[117.57465,40.17611],[117.64881,40.13452],[117.64057,40.09199],[117.66048,40.09947],[117.67541,40.0849],[117.69241,40.0933],[117.71627,40.07951],[117.74425,40.0803],[117.77103,40.05757],[117.72451,40.01814],[117.78682,40.01591],[117.77824,39.96185],[117.6943,39.98501],[117.63902,39.9712],[117.54135,39.99987],[117.52658,39.95172],[117.5053,39.94106],[117.51817,39.92303],[117.50272,39.90209],[117.51096,39.89261],[117.51628,39.86771],[117.53654,39.84531],[117.53654,39.8349],[117.55542,39.80128],[117.53757,39.75774],[117.58769,39.74468],[117.64417,39.68763],[117.65945,39.63848],[117.61894,39.60211],[117.68863,39.57208],[117.71009,39.52575],[117.76245,39.59828],[117.92861,39.57499],[117.90081,39.53621],[117.89239,39.47251],[117.86476,39.45196],[117.85634,39.37743],[117.79403,39.37212],[117.80193,39.35474],[117.83257,39.35036],[117.85583,39.36396],[117.83609,39.33542],[117.84115,39.32752],[117.96363,39.31331],[118.06079,39.25166],[118.02577,39.21789],[118.35983,38.73266],[119.98931,39.77661],[119.83955,40.00079],[119.84607,40.03313],[119.81449,40.04995],[119.78118,40.03812],[119.75423,40.06532],[119.729,40.10459],[119.75921,40.14397],[119.63458,40.25568],[119.58137,40.36799],[119.5642,40.54876],[119.26277,40.53154],[119.14638,40.6611],[118.90056,40.74023],[118.88992,40.9576],[119.01695,40.97523],[118.92631,41.06382],[119.07325,41.08608],[119.24972,41.27264],[119.23562,41.3149],[119.19376,41.28116],[118.86863,41.30463],[118.83258,41.36985],[118.73714,41.32655],[118.37699,41.33093],[118.15383,41.67086],[118.12808,41.83196],[118.30043,41.77822],[118.33236,41.8647],[118.26129,41.91837],[118.3073,41.98756],[118.22971,42.01206],[118.28807,42.03832],[118.26232,42.08446],[118.1916,42.0294],[118.11332,42.03271],[118.15898,42.08268],[118.08689,42.10688],[118.10268,42.17154],[117.96329,42.23436],[118.04981,42.29254],[118.01067,42.39202],[117.77343,42.6092],[117.44453,42.59151],[117.39921,42.46449],[117.00782,42.45994],[116.88697,42.37985],[116.90757,42.18477],[116.78054,42.19902],[116.87976,42.02634],[116.63909,41.92859],[116.32461,42.00593],[116.10969,41.83401],[116.10008,41.78334],[116.03725,41.77361],[115.91194,41.93804],[115.81272,41.93101],[115.54458,41.77873],[115.29687,41.69701],[115.38734,41.58668],[115.26975,41.61634],[115.23937,41.56883],[115.12126,41.62442],[114.85879,41.60235],[114.89038,41.63571],[114.89948,41.71111],[114.87785,41.80753],[114.92866,41.82787],[114.93209,41.89895],[114.82257,42.15118],[114.71271,42.11197],[114.56096,42.13184],[114.46947,42.06624],[114.49813,41.96051],[114.33162,41.94314],[114.18605,41.76542],[114.23309,41.69547],[114.20288,41.68624],[114.23103,41.65316],[114.21798,41.50652],[114.0422,41.54764],[113.85612,41.41338],[113.9392,41.39097],[113.9059,41.30128],[113.96358,41.23676],[113.98864,41.26296],[114.01388,41.21934],[113.87981,41.1435],[113.87071,41.10936],[113.82144,41.09487],[113.9035,41.03534],[114.04443,40.87899],[114.04821,40.81289],[114.12425,40.74517],[114.15361,40.71304],[114.27738,40.52528],[114.29454,40.44093],[114.27909,40.42721],[114.30519,40.36695],[114.5565,40.34523],[114.44286,40.2528],[114.06005,40.18228],[114.09713,40.07623],[114.05834,40.06624],[113.97422,40.11431],[113.95774,40.03523],[113.90556,40.01814],[113.9059,40.00224],[114.02435,39.98606],[114.04083,39.89709],[114.21695,39.91026],[114.20494,39.86442],[114.38981,39.86627],[114.4075,39.77107],[114.39788,39.64165],[114.42363,39.60555],[114.56748,39.57076],[114.42192,39.2886],[114.4638,39.19501],[114.37866,39.17092],[114.3505,39.07251],[114.11464,39.05091],[114.03224,39.12766],[113.75518,38.94499],[113.78608,38.86751],[113.84342,38.82767],[113.82522,38.76238],[113.76068,38.73346],[113.76565,38.706],[113.7078,38.70641],[113.69424,38.65267],[113.61648,38.64865],[113.54026,38.51378],[113.55194,38.24626],[113.82797,38.16263],[113.87706,38.02483],[114.02984,37.72972],[114.127,37.69387],[114.11636,37.59219],[113.97766,37.41434],[113.82316,37.17344],[113.76892,37.14991],[113.75209,37.07408],[113.78162,37.04805],[113.75587,36.95071],[113.78917,36.88071],[113.49014,36.72815]]]}},{"type":"Feature","properties":{"id":"CN-TJ"},"geometry":{"type":"Polygon","coordinates":[[[116.70158,38.91387],[116.71754,38.85214],[116.74003,38.8508],[116.74827,38.74377],[116.85539,38.74658],[116.8717,38.68001],[117.02447,38.69998],[117.06018,38.67961],[117.05074,38.64395],[117.09468,38.58306],[117.21673,38.64328],[117.2545,38.60734],[117.24369,38.56024],[117.36299,38.56575],[117.47457,38.61579],[117.59559,38.61472],[118.33648,38.49229],[118.35983,38.73266],[118.02577,39.21789],[118.06079,39.25166],[117.96363,39.31331],[117.84115,39.32752],[117.83609,39.33542],[117.85583,39.36396],[117.83257,39.35036],[117.80193,39.35474],[117.79403,39.37212],[117.85634,39.37743],[117.86476,39.45196],[117.89239,39.47251],[117.90081,39.53621],[117.92861,39.57499],[117.76245,39.59828],[117.71009,39.52575],[117.68863,39.57208],[117.61894,39.60211],[117.65945,39.63848],[117.64417,39.68763],[117.58769,39.74468],[117.53757,39.75774],[117.55542,39.80128],[117.53654,39.8349],[117.53654,39.84531],[117.51628,39.86771],[117.51096,39.89261],[117.50272,39.90209],[117.51817,39.92303],[117.5053,39.94106],[117.52658,39.95172],[117.54135,39.99987],[117.63902,39.9712],[117.6943,39.98501],[117.77824,39.96185],[117.78682,40.01591],[117.72451,40.01814],[117.77103,40.05757],[117.74425,40.0803],[117.71627,40.07951],[117.69241,40.0933],[117.67541,40.0849],[117.66048,40.09947],[117.64057,40.09199],[117.64881,40.13452],[117.57465,40.17611],[117.55645,40.22594],[117.4308,40.25228],[117.3829,40.22685],[117.40161,40.18438],[117.34531,40.17087],[117.34771,40.14214],[117.20334,40.09422],[117.21691,40.06454],[117.18086,40.04746],[117.18893,39.98737],[117.14481,39.94593],[117.14515,39.87641],[117.22085,39.8523],[117.24248,39.85836],[117.25725,39.82897],[117.14481,39.81948],[117.20111,39.76157],[117.16781,39.74996],[117.15511,39.60806],[117.01486,39.65341],[116.95873,39.63372],[116.94345,39.70599],[116.91221,39.70533],[116.90277,39.6875],[116.85058,39.6661],[116.82243,39.63702],[116.82929,39.61864],[116.80732,39.61501],[116.78964,39.59047],[116.80526,39.57698],[116.78123,39.54958],[116.81608,39.5288],[116.82191,39.48629],[116.78037,39.46429],[116.81058,39.44958],[116.8226,39.43818],[116.87084,39.43327],[116.83427,39.41046],[116.83479,39.37836],[116.81247,39.36881],[116.82655,39.36177],[116.81951,39.33907],[116.86861,39.35819],[116.87101,39.3408],[116.88629,39.33429],[116.86225,39.30016],[116.88611,39.224],[116.85127,39.21403],[116.85934,39.15522],[116.90517,39.14949],[116.91839,39.12473],[116.8571,39.05411],[116.76012,39.04705],[116.70158,38.91387]]]}},{"type":"Feature","properties":{"id":"CN-BJ"},"geometry":{"type":"Polygon","coordinates":[[[115.41206,39.77899],[115.48107,39.73333],[115.4742,39.65288],[115.51214,39.64297],[115.50802,39.59325],[115.6644,39.60978],[115.68054,39.60185],[115.69032,39.57036],[115.72895,39.54488],[115.74594,39.51178],[115.81795,39.50874],[115.81272,39.52993],[115.84997,39.55355],[115.86602,39.5421],[115.88232,39.55058],[115.88619,39.56791],[115.90576,39.56772],[115.90378,39.60529],[115.95236,39.56017],[115.96927,39.57056],[115.97373,39.59385],[115.98137,39.59272],[115.99296,39.57221],[116.01099,39.58617],[116.0188,39.58538],[116.01871,39.57354],[116.03081,39.57036],[116.09304,39.57407],[116.12952,39.56752],[116.17647,39.59127],[116.2035,39.57658],[116.21646,39.57671],[116.23809,39.51649],[116.33216,39.45422],[116.44289,39.44454],[116.45988,39.5292],[116.47044,39.53463],[116.46563,39.55157],[116.50288,39.55025],[116.53404,39.60469],[116.55189,39.59563],[116.56734,39.61917],[116.59961,39.62261],[116.61849,39.59775],[116.68596,39.6],[116.69952,39.58611],[116.72218,39.59272],[116.69128,39.61891],[116.75342,39.616],[116.77402,39.5906],[116.80732,39.61501],[116.82929,39.61864],[116.82243,39.63702],[116.85058,39.6661],[116.90277,39.6875],[116.91221,39.70533],[116.94345,39.70599],[116.95873,39.63372],[117.01486,39.65341],[117.15511,39.60806],[117.16781,39.74996],[117.20111,39.76157],[117.14481,39.81948],[117.25725,39.82897],[117.24248,39.85836],[117.22085,39.8523],[117.14515,39.87641],[117.14481,39.94593],[117.18893,39.98737],[117.18086,40.04746],[117.21691,40.06454],[117.20334,40.09422],[117.34771,40.14214],[117.34531,40.17087],[117.40161,40.18438],[117.3829,40.22685],[117.33896,40.22948],[117.32814,40.2879],[117.29055,40.27743],[117.23596,40.36786],[117.21776,40.37257],[117.23321,40.41885],[117.25639,40.43976],[117.20008,40.50949],[117.25536,40.51366],[117.24334,40.54759],[117.3072,40.57719],[117.41912,40.56376],[117.41432,40.63701],[117.42753,40.62281],[117.45174,40.65095],[117.47749,40.63167],[117.51628,40.65772],[117.39921,40.68792],[117.31853,40.65785],[117.19682,40.69326],[117.08009,40.70211],[116.97933,40.69209],[116.85024,40.83381],[116.69883,40.92103],[116.6827,41.04155],[116.62158,41.06019],[116.60862,40.9821],[116.44306,40.98145],[116.47602,40.89586],[116.40649,40.90313],[116.36358,40.941],[116.31689,40.92324],[116.47533,40.76],[116.2453,40.7808],[116.15604,40.66215],[115.97511,40.58384],[115.88705,40.61356],[115.81014,40.55893],[115.77907,40.56128],[115.73272,40.51145],[115.77495,40.4617],[115.76602,40.44786],[115.85383,40.37466],[115.85855,40.3591],[115.91082,40.354],[115.96052,40.26354],[115.88584,40.22659],[115.8467,40.14633],[115.76808,40.15736],[115.52124,40.08201],[115.44227,40.0146],[115.41807,39.94949],[115.49875,39.92053],[115.52089,39.83042],[115.58183,39.79996],[115.41206,39.77899]]]}},{"type":"Feature","properties":{"id":"FM-KSA"},"geometry":{"type":"Polygon","coordinates":[[[161.58988,8.89263],[161.87397,3.18678],[165.35175,6.367],[161.58988,8.89263]]]}},{"type":"Feature","properties":{"id":"FM-PNI"},"geometry":{"type":"Polygon","coordinates":[[[153.83475,11.01511],[154.49668,-0.44964],[156.88247,-1.39237],[161.87397,3.18678],[161.58988,8.89263],[159.04653,10.59067],[153.83475,11.01511]]]}},{"type":"Feature","properties":{"id":"FM-TRK"},"geometry":{"type":"Polygon","coordinates":[[[148.42679,11.45488],[148.49862,1.9204],[154.49668,-0.44964],[153.83475,11.01511],[148.42679,11.45488]]]}},{"type":"Feature","properties":{"id":"FM-YAP"},"geometry":{"type":"Polygon","coordinates":[[[136.04605,12.45908],[136.27107,6.73747],[148.49862,1.9204],[148.42679,11.45488],[136.04605,12.45908]]]}},{"type":"Feature","properties":{"id":"GB-SCT"},"geometry":{"type":"Polygon","coordinates":[[[-14.78497,57.60709],[-7.93366,55.84142],[-6.79943,55.54107],[-6.1908,55.41031],[-4.86305,54.44148],[-4.1819,54.57861],[-3.46344,54.91293],[-3.05814,54.98608],[-3.02759,55.03579],[-3.04338,55.05566],[-2.95828,55.04941],[-2.9142,55.0765],[-2.90184,55.07704],[-2.8585,55.10803],[-2.8276,55.12477],[-2.82571,55.13885],[-2.80735,55.13635],[-2.70315,55.1719],[-2.64375,55.25935],[-2.49046,55.35179],[-2.3521,55.36135],[-2.33682,55.40358],[-2.23228,55.42716],[-2.17443,55.46698],[-2.2382,55.55252],[-2.28481,55.57849],[-2.28781,55.59726],[-2.33768,55.63332],[-2.30798,55.64655],[-2.23546,55.6403],[-2.23108,55.64504],[-2.24824,55.651],[-2.22043,55.66422],[-2.21975,55.67371],[-2.16791,55.70835],[-2.17906,55.71676],[-2.08568,55.76315],[-2.088,55.79085],[-2.0668,55.80075],[-1.16893,55.97338],[-0.3751,61.32236],[-14.78497,57.60709]]]}},{"type":"Feature","properties":{"id":"BE-BRU"},"geometry":{"type":"Polygon","coordinates":[[[4.25089,50.81741],[4.25645,50.8174],[4.26009,50.81099],[4.28301,50.80725],[4.29692,50.80985],[4.30636,50.81398],[4.30181,50.80273],[4.30494,50.7987],[4.31704,50.79623],[4.31921,50.78705],[4.33073,50.77527],[4.34075,50.77356],[4.34788,50.77637],[4.39414,50.76681],[4.48199,50.79302],[4.4474,50.80821],[4.4771,50.82052],[4.46572,50.83548],[4.46813,50.83746],[4.45989,50.85247],[4.44727,50.85464],[4.44345,50.85943],[4.43019,50.86117],[4.41929,50.86956],[4.43697,50.87885],[4.42693,50.89071],[4.43264,50.89437],[4.4062,50.91331],[4.38753,50.90985],[4.37916,50.90151],[4.37788,50.89721],[4.36363,50.90113],[4.34245,50.90226],[4.33011,50.90094],[4.31839,50.89464],[4.29438,50.88833],[4.29904,50.87934],[4.27891,50.86648],[4.28043,50.85992],[4.28988,50.85586],[4.28282,50.85403],[4.28844,50.84777],[4.28376,50.84816],[4.28273,50.83789],[4.27323,50.83876],[4.27164,50.83625],[4.25694,50.8355],[4.25089,50.81741]]]}},{"type":"Feature","properties":{"id":"RU-TA"},"geometry":{"type":"Polygon","coordinates":[[[47.27828,54.71966],[47.39879,54.49546],[47.61474,54.56171],[47.72186,54.73413],[47.87086,54.64642],[48.12663,54.73413],[48.21041,54.72362],[48.46343,54.62456],[48.88366,54.62496],[48.88091,54.79672],[49.12347,54.79811],[49.14871,54.84261],[49.28981,54.89141],[49.47761,54.77653],[49.58473,54.56171],[49.7787,54.57425],[50.2954,54.44049],[50.36682,54.49397],[50.55187,54.33834],[50.66963,54.38895],[50.95836,54.34775],[51.0054,54.54657],[51.30203,54.65913],[51.58596,54.60588],[51.725,54.55215],[51.9358,54.51988],[52.07862,54.36275],[52.29045,54.4357],[52.49851,54.45846],[52.55653,54.32253],[52.70759,54.35595],[53.12747,54.27544],[52.9668,54.18735],[53.38943,53.98213],[53.50032,54.05596],[53.42994,54.258],[53.40042,54.37955],[53.43578,54.56111],[53.58924,54.65675],[53.6531,54.91135],[53.30738,55.04907],[53.14533,55.13355],[53.26618,55.17122],[53.48453,55.21727],[53.62564,55.21511],[54.12586,55.5991],[54.25941,55.69616],[53.71764,55.92458],[53.26309,55.85835],[53.23013,55.89899],[53.38909,56.01239],[53.61876,56.21587],[53.29467,56.26471],[53.35252,56.05459],[52.98294,56.2319],[53.01418,56.44237],[53.08044,56.53961],[52.94963,56.54529],[52.97229,56.42928],[52.73471,56.3506],[52.56099,56.23648],[52.80784,56.19448],[52.85934,56.09138],[52.74604,56.09521],[52.74879,55.98897],[52.44564,56.08008],[52.4604,56.00797],[52.36701,56.02697],[52.20703,56.10268],[52.22419,56.01872],[52.24067,55.949],[52.1466,55.89379],[51.93202,55.95323],[51.81495,55.93093],[51.50287,55.95496],[51.40846,55.9711],[51.43386,56.03311],[51.61445,56.13828],[51.44356,56.12842],[51.31507,56.08468],[50.99201,56.16639],[51.00265,56.19982],[50.87974,56.19467],[50.86566,56.24888],[50.91716,56.28015],[50.86532,56.37903],[50.57075,56.38948],[50.49402,56.48297],[50.53487,56.50817],[50.32836,56.68037],[50.30845,56.63225],[50.1749,56.63791],[50.2003,56.55683],[49.95449,56.42909],[49.96684,56.49529],[49.86247,56.42624],[49.73888,56.52635],[49.57099,56.49112],[49.54799,56.42814],[49.42268,56.44845],[49.39865,56.35402],[49.29702,56.33823],[49.19059,56.39015],[49.17875,56.35497],[49.06064,56.33386],[48.9664,56.16237],[48.84864,56.1156],[48.8289,56.06235],[48.74874,56.04184],[48.7144,56.04836],[48.69466,55.95525],[48.41571,55.91496],[48.38396,55.86712],[48.42601,55.83291],[48.40679,55.7812],[48.16955,55.69152],[48.12938,55.64834],[48.09059,55.69287],[48.01024,55.6311],[48.082,55.61442],[48.11531,55.52474],[47.97901,55.50452],[47.80219,55.34847],[47.66487,55.35296],[47.71636,55.22491],[47.92888,55.32523],[48.01128,55.28361],[48.08132,55.16886],[48.07651,55.08917],[48.03497,55.10764],[47.9021,55.08612],[48.02913,55.03648],[47.99034,54.95494],[47.7658,54.9577],[47.81215,54.79949],[47.72083,54.92931],[47.74658,54.87344],[47.56015,54.84499],[47.42111,54.87937],[47.42111,54.83372],[47.36446,54.8264],[47.38849,54.74979],[47.27828,54.71966]]]}},{"type":"Feature","properties":{"id":"RU-BA"},"geometry":{"type":"Polygon","coordinates":[[[53.14533,55.13355],[53.30738,55.04907],[53.6531,54.91135],[53.58924,54.65675],[53.43578,54.56111],[53.40042,54.37955],[53.42994,54.258],[53.50032,54.05596],[53.65173,53.74911],[53.86321,53.67515],[54.17083,53.36817],[54.54986,53.34727],[54.63706,53.20973],[54.83894,53.26993],[55.16544,52.81355],[55.39066,52.85005],[55.52799,52.38419],[55.90667,52.37497],[55.84556,52.54149],[56.17,52.59679],[56.45839,52.59449],[56.35883,52.46103],[56.45736,52.2942],[56.17206,52.15476],[56.61769,52.12337],[56.69631,52.06642],[56.61048,51.82707],[56.74507,51.73808],[56.79725,51.58261],[56.89544,51.64657],[56.81922,51.75827],[56.99741,51.71894],[57.04101,51.65892],[57.20134,51.56597],[57.47257,51.72702],[57.64835,51.75912],[57.66345,51.86652],[58.11698,51.78313],[58.16642,51.71894],[58.62407,51.82835],[58.68621,52.29693],[58.8953,52.24167],[58.77994,52.66555],[58.90182,53.31487],[58.90422,53.54357],[58.82011,53.60411],[58.90663,53.6546],[58.88774,53.7483],[58.94062,53.96557],[59.19948,53.96759],[59.31484,54.18614],[59.70897,54.15519],[59.76905,54.24757],[59.69421,54.36375],[59.73918,54.44329],[59.65679,54.48818],[59.86038,54.619],[59.9905,54.86791],[59.83428,54.84123],[59.65576,54.91372],[59.25338,54.61105],[57.9158,54.41653],[57.54226,54.6909],[57.13405,54.85111],[57.26177,54.96145],[57.1883,54.9782],[57.19036,55.01562],[57.13405,55.08662],[57.36373,55.31937],[58.02738,55.27168],[58.13552,55.19846],[57.98686,55.09781],[58.02051,54.98844],[57.91477,54.91767],[58.06617,54.9076],[58.18221,55.08504],[58.13278,55.11971],[58.28453,55.16631],[58.45069,54.9977],[58.54614,55.02703],[58.50906,54.96973],[58.73496,54.96736],[58.83041,55.05103],[58.65085,55.14454],[58.57017,55.1618],[58.63231,55.20042],[58.65789,55.16048],[58.68836,55.1744],[58.6948,55.14998],[58.72312,55.1389],[58.72724,55.15494],[58.75522,55.16229],[58.77239,55.21237],[58.68072,55.28928],[58.93478,55.31664],[59.09442,55.30081],[59.16755,55.34832],[59.18575,55.46133],[59.46865,55.47535],[59.64477,55.5599],[59.28222,55.70545],[59.12395,55.80784],[59.25819,55.94362],[59.19382,56.02342],[59.32891,56.07251],[59.32874,56.15979],[59.23175,56.13416],[59.0419,56.17059],[58.95606,56.0519],[58.49567,56.17059],[58.36349,56.07605],[57.89863,56.11761],[57.47171,56.10603],[57.49626,56.21128],[57.41043,56.34889],[57.20168,56.23953],[57.21816,56.16925],[57.0465,56.15052],[56.92016,56.10498],[56.71966,56.23113],[56.57821,56.2931],[56.5068,56.34223],[56.58061,56.39034],[56.35917,56.38844],[56.27626,56.30472],[56.12503,56.3112],[56.07971,56.28844],[56.04125,56.33899],[55.94152,56.37703],[55.91714,56.45689],[55.83835,56.41959],[55.70651,56.43697],[55.64523,56.40079],[55.52181,56.37974],[55.50807,56.36125],[55.45701,56.33148],[55.38207,56.31753],[55.27976,56.3232],[55.19754,56.36439],[55.18209,56.30444],[55.09265,56.35117],[54.98416,56.327],[54.57904,56.54036],[54.43794,56.35193],[54.37133,56.38198],[54.36824,56.31463],[54.4218,56.30872],[54.36378,56.24735],[54.19589,56.19791],[54.00329,56.00701],[53.84159,56.04634],[53.71764,55.92458],[54.25941,55.69616],[54.12586,55.5991],[53.62564,55.21511],[53.48453,55.21727],[53.26618,55.17122],[53.14533,55.13355]]]}},{"type":"Feature","properties":{"id":"RU-CU"},"geometry":{"type":"Polygon","coordinates":[[[45.90774,55.69171],[46.10034,55.56436],[46.42204,55.42862],[46.21982,55.38671],[46.24523,55.29377],[46.02687,55.1671],[46.06155,55.05113],[46.16867,54.99652],[46.2442,55.0878],[46.43886,54.76326],[46.65962,54.67393],[47.19142,54.66132],[47.27828,54.71966],[47.38849,54.74979],[47.36446,54.8264],[47.42111,54.83372],[47.42111,54.87937],[47.56015,54.84499],[47.74658,54.87344],[47.72083,54.92931],[47.81215,54.79949],[47.7658,54.9577],[47.99034,54.95494],[48.02913,55.03648],[47.9021,55.08612],[48.03497,55.10764],[48.07651,55.08917],[48.08132,55.16886],[48.01128,55.28361],[47.92888,55.32523],[47.71636,55.22491],[47.66487,55.35296],[47.80219,55.34847],[47.97901,55.50452],[48.11531,55.52474],[48.082,55.61442],[48.01024,55.6311],[48.09059,55.69287],[48.12938,55.64834],[48.16955,55.69152],[48.40679,55.7812],[48.42601,55.83291],[48.31924,55.83657],[48.23856,55.90746],[48.01437,55.94766],[47.89386,56.11665],[47.43141,56.15319],[47.43278,56.22579],[47.37956,56.22655],[47.39501,56.32281],[47.25254,56.32891],[47.2467,56.31158],[47.15263,56.31539],[47.14679,56.27738],[47.05272,56.28034],[46.91368,56.21109],[46.78115,56.18416],[46.76004,56.14832],[46.65876,56.09904],[46.62237,56.129],[46.47354,56.12689],[46.24282,56.0659],[46.19471,56.03187],[46.0818,55.95189],[46.17073,55.88725],[46.07562,55.87473],[46.10549,55.79819],[46.04198,55.78815],[45.90774,55.69171]]]}},{"type":"Feature","properties":{"id":"RU-CE"},"geometry":{"type":"Polygon","coordinates":[[[44.83726,43.65495],[44.86507,43.5412],[45.1435,43.41502],[45.11535,43.22919],[45.19706,43.10499],[45.06626,42.98355],[45.08617,42.83821],[45.18985,42.82285],[45.08266,42.71749],[45.15318,42.70598],[45.36501,42.55268],[45.78692,42.48358],[46.52091,43.00841],[46.49826,43.08268],[46.38736,43.13631],[46.42547,43.29994],[46.31561,43.4397],[46.44126,43.54705],[46.47045,43.67979],[46.6603,43.8318],[46.46083,43.8776],[46.37466,44.01899],[46.1,43.81074],[45.49713,44.0175],[45.43601,43.87512],[45.20049,43.87661],[45.20118,43.95674],[45.05149,43.95773],[45.06282,43.71826],[44.83726,43.65495]]]}},{"type":"Feature","properties":{"id":"RU-DA"},"geometry":{"type":"Polygon","coordinates":[[[45.0769,44.1886],[45.51223,44.1822],[45.49713,44.0175],[46.1,43.81074],[46.37466,44.01899],[46.46083,43.8776],[46.6603,43.8318],[46.47045,43.67979],[46.44126,43.54705],[46.31561,43.4397],[46.42547,43.29994],[46.38736,43.13631],[46.49826,43.08268],[46.52091,43.00841],[45.78692,42.48358],[45.61676,42.20768],[46.42738,41.91323],[46.5332,41.87389],[46.58924,41.80547],[46.75269,41.8623],[46.8134,41.76252],[47.00955,41.63583],[46.99554,41.59743],[47.03757,41.55434],[47.10762,41.59044],[47.34579,41.27884],[47.49004,41.26366],[47.54504,41.20275],[47.62288,41.22969],[47.75831,41.19455],[47.87973,41.21798],[48.07587,41.49957],[48.22064,41.51472],[48.2878,41.56221],[48.40277,41.60441],[48.42301,41.65444],[48.55078,41.77917],[48.5867,41.84306],[48.80971,41.95365],[47.51959,44.80109],[46.92535,44.78476],[46.21879,44.97159],[45.70312,44.97694],[45.27259,44.27667],[45.0769,44.1886]]]}},{"type":"Feature","properties":{"id":"RU-KB"},"geometry":{"type":"Polygon","coordinates":[[[42.40563,43.23226],[42.66667,43.13917],[42.75889,43.19651],[43.03322,43.08883],[43.0419,43.02413],[43.36538,42.90656],[43.57349,42.93581],[43.74,43.13005],[43.80042,43.32043],[44.10564,43.2467],[44.02049,43.38409],[44.17636,43.35039],[44.43798,43.49303],[44.42974,43.68599],[44.29824,43.68674],[44.26288,43.82585],[44.42115,44.0338],[44.22134,43.91916],[43.94222,44.01775],[43.81725,43.95896],[43.74515,43.81842],[43.43856,43.86795],[43.3184,43.80108],[43.16974,43.99157],[43.14708,43.89467],[42.97679,43.89665],[42.85285,43.80133],[42.67278,43.81025],[42.67621,43.70101],[42.46902,43.61768],[42.42507,43.43696],[42.40563,43.23226]]]}},{"type":"Feature","properties":{"id":"RU-KC"},"geometry":{"type":"Polygon","coordinates":[[[40.65957,43.56212],[41.64935,43.22331],[42.13085,43.20326],[42.40563,43.23226],[42.42507,43.43696],[42.46902,43.61768],[42.67621,43.70101],[42.67278,43.81025],[42.65991,43.93053],[42.31384,44.10583],[42.61733,44.26905],[42.51708,44.38301],[42.46799,44.31304],[42.10475,44.34619],[41.92005,44.40042],[41.93138,44.50507],[41.79267,44.47176],[41.72744,44.48621],[41.61689,44.37344],[41.74083,44.27372],[41.70684,44.18638],[41.54926,44.04342],[41.31666,43.97515],[41.25623,44.08758],[41.15066,44.12185],[41.08989,44.09399],[40.89351,44.14871],[40.92166,43.97576],[40.78828,43.92052],[40.65957,43.56212]]]}},{"type":"Feature","properties":{"id":"RU-ME"},"geometry":{"type":"Polygon","coordinates":[[[45.61386,56.56515],[46.11717,56.14975],[46.03546,56.10498],[46.19471,56.03187],[46.24282,56.0659],[46.47354,56.12689],[46.62237,56.129],[46.65876,56.09904],[46.76004,56.14832],[46.78115,56.18416],[46.91368,56.21109],[47.05272,56.28034],[47.14679,56.27738],[47.15263,56.31539],[47.2467,56.31158],[47.25254,56.32891],[47.39501,56.32281],[47.37956,56.22655],[47.43278,56.22579],[47.43141,56.15319],[47.89386,56.11665],[48.01437,55.94766],[48.23856,55.90746],[48.31924,55.83657],[48.42601,55.83291],[48.38396,55.86712],[48.41571,55.91496],[48.69466,55.95525],[48.7144,56.04836],[48.74874,56.04184],[48.8289,56.06235],[48.84864,56.1156],[48.9664,56.16237],[49.06064,56.33386],[49.17875,56.35497],[49.19059,56.39015],[49.29702,56.33823],[49.39865,56.35402],[49.42268,56.44845],[49.54799,56.42814],[49.57099,56.49112],[49.73888,56.52635],[49.86247,56.42624],[49.96684,56.49529],[49.95449,56.42909],[50.2003,56.55683],[50.1749,56.63791],[50.02796,56.65009],[50.09885,56.73059],[50.05886,56.8743],[49.9556,56.85577],[49.91312,56.88425],[49.77115,56.87299],[49.70575,56.92052],[49.7545,56.94984],[49.65185,57.07041],[49.40259,57.04409],[49.42285,57.00858],[49.25926,57.03045],[49.21514,57.11154],[49.15403,57.14052],[49.22149,57.29268],[49.17274,57.34356],[49.00073,57.28702],[49.04846,57.18901],[48.99644,57.18808],[48.95456,57.07984],[48.83405,57.0969],[48.86238,57.11881],[48.81568,57.17608],[48.6581,57.1476],[48.56025,57.16529],[48.4255,57.10977],[48.35906,57.17068],[48.23375,57.13325],[48.1771,57.07172],[48.21247,57.02298],[47.91858,56.96743],[47.7816,57.04073],[47.6192,56.9253],[47.11109,56.7973],[47.17254,56.90244],[46.89067,56.95227],[46.82922,56.9135],[46.68228,56.97529],[46.59353,56.93055],[45.97091,56.87093],[45.94928,56.74199],[45.61386,56.56515]]]}},{"type":"Feature","properties":{"id":"RU-SE"},"geometry":{"type":"Polygon","coordinates":[[[43.36538,42.90656],[43.81453,42.74297],[43.73119,42.62043],[43.95517,42.55396],[44.22978,42.64904],[44.54202,42.75699],[44.70002,42.74679],[44.63024,42.89181],[44.86181,42.82272],[44.9533,43.04932],[44.77374,43.16036],[44.68002,43.2812],[44.68276,43.20492],[44.56775,43.23669],[44.57805,43.37635],[44.48501,43.51419],[44.61238,43.65222],[44.69478,43.54431],[44.83726,43.65495],[44.75315,43.83304],[44.38407,43.80727],[44.26288,43.82585],[44.29824,43.68674],[44.42974,43.68599],[44.43798,43.49303],[44.17636,43.35039],[44.02049,43.38409],[44.10564,43.2467],[43.80042,43.32043],[43.74,43.13005],[43.57349,42.93581],[43.36538,42.90656]]]}},{"type":"Feature","properties":{"id":"RU-SA"},"geometry":{"type":"Polygon","coordinates":[[[105.80932,64.49645],[108.87176,64.21115],[110.52795,61.16774],[109.59956,59.04057],[116.05952,60.65166],[119.13569,58.10112],[119.75921,56.9075],[123.8159,56.4139],[124.81841,55.86991],[126.13402,55.628],[130.95148,55.71476],[132.89061,59.12523],[138.16401,59.66776],[138.35082,61.08751],[140.73485,62.49456],[144.3933,61.75493],[146.40379,64.19203],[152.39134,64.43489],[154.30296,66.20044],[158.51069,66.14276],[158.07128,68.01785],[162.4713,68.29174],[162.47268,69.65708],[163.13492,69.79589],[158.59861,77.40868],[116.38911,75.59041],[110.85203,74.23886],[110.32468,73.37822],[112.63181,71.18776],[106.28171,69.44984],[106.062,66.24252],[106.91893,65.48334],[105.80932,64.49645]]]}},{"type":"Feature","properties":{"id":"RU-UD"},"geometry":{"type":"Polygon","coordinates":[[[51.12281,57.3569],[51.24847,57.04222],[51.37344,56.9386],[51.42837,56.93907],[51.42562,56.89456],[51.47884,56.90478],[51.53961,56.85347],[51.39507,56.74594],[51.39713,56.66584],[51.17946,56.68112],[51.15852,56.4733],[51.19611,56.43241],[51.29688,56.42453],[51.26787,56.38008],[51.28864,56.32205],[51.40331,56.28148],[51.38983,56.22903],[51.3458,56.20116],[51.37524,56.19849],[51.44777,56.22789],[51.49961,56.20527],[51.42631,56.1919],[51.44356,56.12842],[51.61445,56.13828],[51.43386,56.03311],[51.40846,55.9711],[51.50287,55.95496],[51.81495,55.93093],[51.93202,55.95323],[52.1466,55.89379],[52.24067,55.949],[52.22419,56.01872],[52.20703,56.10268],[52.36701,56.02697],[52.4604,56.00797],[52.44564,56.08008],[52.74879,55.98897],[52.74604,56.09521],[52.85934,56.09138],[52.80784,56.19448],[52.56099,56.23648],[52.73471,56.3506],[52.97229,56.42928],[52.94963,56.54529],[53.08044,56.53961],[53.01418,56.44237],[52.98294,56.2319],[53.35252,56.05459],[53.29467,56.26471],[53.61876,56.21587],[53.38909,56.01239],[53.23013,55.89899],[53.26309,55.85835],[53.71764,55.92458],[53.84159,56.04634],[54.00329,56.00701],[54.19589,56.19791],[54.36378,56.24735],[54.4218,56.30872],[54.36824,56.31463],[54.37133,56.38198],[54.32876,56.48334],[54.23589,56.45604],[54.17942,56.53677],[54.03556,56.58463],[54.08775,56.65547],[53.85635,56.63017],[53.8227,56.8223],[54.01153,56.74293],[54.08878,56.79599],[54.24087,56.98315],[54.45991,57.01531],[54.29374,57.10567],[54.32052,57.30575],[54.11178,57.32874],[54.29511,57.45196],[54.08946,57.5278],[53.99093,57.59189],[54.14062,57.61194],[54.04827,57.65623],[54.165,57.71515],[54.08706,58.00464],[53.93737,58.09457],[53.95042,58.15367],[53.77738,58.24251],[53.89926,58.32337],[53.82614,58.34824],[53.79112,58.45761],[53.4217,58.4303],[52.89504,58.53493],[52.66158,58.40494],[52.01545,58.46928],[51.80053,58.35833],[51.81907,58.28531],[51.72191,58.24992],[51.74543,58.21648],[51.67385,58.14914],[51.83967,58.10364],[51.80499,58.01846],[51.92584,58.00118],[51.81598,57.84219],[51.93099,57.82528],[51.9485,57.76957],[51.74818,57.53057],[51.64123,57.51683],[51.12281,57.3569]]]}},{"type":"Feature","properties":{"id":"RU-IN"},"geometry":{"type":"Polygon","coordinates":[[[44.48501,43.51419],[44.57805,43.37635],[44.56775,43.23669],[44.68276,43.20492],[44.68002,43.2812],[44.77374,43.16036],[44.9533,43.04932],[44.86181,42.82272],[44.63024,42.89181],[44.70002,42.74679],[44.80941,42.61277],[44.88754,42.74934],[45.08266,42.71749],[45.18985,42.82285],[45.08617,42.83821],[45.06626,42.98355],[45.19706,43.10499],[45.11535,43.22919],[45.1435,43.41502],[44.86507,43.5412],[44.83726,43.65495],[44.69478,43.54431],[44.61238,43.65222],[44.48501,43.51419]]]}},{"type":"Feature","properties":{"id":"RU-TY"},"geometry":{"type":"Polygon","coordinates":[[[88.77227,51.56085],[89.96978,50.40676],[89.496,50.45837],[89.33326,50.23304],[89.59711,49.90851],[91.86048,50.73734],[92.07173,50.69585],[92.44714,50.78762],[93.01109,50.79001],[92.99595,50.63183],[94.30823,50.57498],[94.39258,50.22193],[94.49477,50.17832],[94.6121,50.04239],[94.97166,50.04725],[95.02465,49.96941],[95.74757,49.97915],[95.80056,50.04239],[96.97388,49.88413],[97.24639,49.74737],[97.56811,49.84265],[97.56432,49.92801],[97.76871,49.99861],[97.85197,49.91339],[98.29481,50.33561],[98.31373,50.4996],[98.06393,50.61262],[97.9693,50.78044],[98.01472,50.86652],[97.83305,51.00248],[98.05257,51.46696],[98.22053,51.46579],[98.33222,51.71832],[98.74142,51.8637],[98.87768,52.14563],[98.81515,52.52959],[98.91197,52.84508],[99.34249,52.94201],[98.52264,53.0899],[96.75865,53.75723],[96.68517,53.56396],[94.66781,53.39151],[94.55382,52.92877],[92.55981,51.6998],[90.41747,52.16045],[89.77477,51.59413],[88.77227,51.56085]]]}},{"type":"Feature","properties":{"id":"RU-BU"},"geometry":{"type":"Polygon","coordinates":[[[98.81515,52.52959],[98.87768,52.14563],[99.27888,51.96876],[99.75578,51.90108],[99.89203,51.74903],[100.61116,51.73028],[101.39085,51.45753],[101.5044,51.50467],[102.14032,51.35566],[102.32194,50.67982],[102.71178,50.38873],[103.70343,50.13952],[105.32528,50.4648],[106.05562,50.40582],[106.07865,50.33474],[106.47156,50.31909],[106.49628,50.32436],[106.51122,50.34408],[106.80326,50.30177],[107.00007,50.1977],[107.1174,50.04239],[107.36407,49.97612],[107.77379,49.94578],[107.72437,50.14434],[108.69048,50.50556],[108.04778,50.74775],[108.27094,50.84713],[108.59161,51.14144],[108.35781,51.23526],[108.6589,51.50831],[109.24735,51.33447],[109.93331,51.61716],[110.69823,51.51216],[111.79412,52.08457],[114.09301,52.66722],[114.32098,52.99329],[113.77029,53.57864],[115.55419,54.39335],[116.50726,54.49716],[116.88903,54.7587],[115.40038,56.59881],[115.59814,56.909],[116.34658,57.22109],[115.29189,57.10716],[113.97216,56.53223],[112.73208,56.8985],[110.09399,56.97791],[108.52844,56.33709],[109.27825,55.95612],[108.83056,55.84294],[108.5717,54.54458],[108.98437,54.41573],[108.10546,53.26193],[104.57817,51.63037],[104.66743,51.36535],[103.81118,51.12679],[103.80706,51.4386],[103.20625,51.48052],[103.23646,51.80182],[102.82104,51.96796],[102.56698,52.22443],[102.07122,52.25302],[100.64849,52.9387],[100.28388,53.37964],[99.88357,53.31159],[99.34249,52.94201],[98.91197,52.84508],[98.81515,52.52959]]]}},{"type":"Feature","properties":{"id":"RU-KO"},"geometry":{"type":"Polygon","coordinates":[[[45.37902,64.17409],[46.98028,63.61454],[47.4575,62.96802],[47.05993,62.82505],[47.47741,62.13214],[48.34258,62.31326],[48.75457,62.85765],[49.62249,62.76604],[49.37393,62.13502],[49.22973,62.10067],[49.10613,61.64425],[49.50164,61.53448],[49.33135,61.16443],[48.47442,61.01838],[48.57948,60.2289],[48.51013,59.70517],[49.09515,59.66288],[49.77355,59.1534],[50.01937,59.77575],[51.74285,60.07717],[52.04635,60.3011],[52.37457,60.21458],[52.85522,61.04233],[54.98382,60.85895],[57.15087,61.49911],[58.85925,61.49583],[59.64065,61.94734],[59.29321,63.20516],[59.7972,64.90025],[60.39527,65.08775],[60.69671,64.89151],[62.07961,65.75037],[66.22283,67.50751],[65.76965,67.59142],[66.18987,67.94061],[65.27251,67.97463],[65.54992,68.4416],[64.58312,68.37085],[63.58749,67.73807],[62.89672,67.59561],[62.86925,67.36418],[61.31744,67.00206],[53.86321,67.00099],[52.14832,67.10165],[51.72569,66.91821],[51.48536,66.94391],[51.54613,66.77472],[49.00176,66.11217],[49.05944,65.22565],[49.64172,65.29002],[50.67168,64.48107],[49.55245,64.57439],[48.75182,64.33812],[47.29751,64.35358],[45.37902,64.17409]]]}},{"type":"Feature","properties":{"id":"RU-MO"},"geometry":{"type":"Polygon","coordinates":[[[42.22251,54.17469],[42.33615,54.14413],[42.34165,54.18996],[42.4364,54.2568],[42.69012,54.13046],[42.57957,54.16544],[42.53683,54.14795],[42.57305,54.07993],[42.43434,54.11577],[42.24689,54.06482],[42.44773,53.99929],[42.30251,53.79375],[42.64411,53.82568],[42.73509,53.74911],[42.75913,53.8199],[42.8944,53.79578],[43.14949,53.81869],[43.20991,53.84422],[43.09661,53.8928],[43.1948,54.00272],[43.47942,53.99485],[43.74996,53.95749],[43.92402,53.86791],[43.88385,53.81099],[44.12006,53.70341],[44.73083,53.6721],[44.60414,53.79943],[44.60002,53.87763],[44.82284,53.9484],[45.20908,53.96517],[45.29834,53.87125],[45.49026,53.88845],[45.52631,53.92344],[45.70861,53.89533],[45.722,53.9287],[45.85504,53.99596],[45.96542,54.13357],[46.40075,54.21064],[46.37586,54.24165],[46.64142,54.28927],[46.72245,54.36565],[46.49963,54.4349],[46.57447,54.49078],[46.45671,54.47741],[46.52297,54.52586],[46.52675,54.58778],[46.39423,54.56549],[46.49173,54.619],[46.43886,54.76326],[46.2442,55.0878],[46.16867,54.99652],[46.06155,55.05113],[45.97675,55.00065],[45.70415,54.99553],[45.78466,55.11588],[45.74604,55.18131],[45.64046,55.18102],[45.54674,55.13591],[45.56991,55.09909],[45.48751,55.11186],[45.43636,55.05241],[45.42469,54.87828],[45.23277,54.88095],[45.22144,54.78029],[45.01716,54.73631],[45.08497,54.65268],[45.01956,54.57126],[45.14419,54.49237],[44.94918,54.47821],[44.66628,54.49955],[44.60414,54.57643],[44.51831,54.52546],[44.2107,54.60528],[43.96041,54.80385],[43.75717,54.81334],[43.41934,54.93818],[43.49487,54.81809],[43.22227,54.80108],[43.20991,54.89339],[43.04614,54.77138],[43.02726,54.78643],[42.81063,54.76168],[42.72033,54.82086],[42.62626,54.78168],[42.40482,54.82541],[42.40997,54.70756],[42.61733,54.6905],[42.61665,54.61572],[42.68669,54.57196],[42.62266,54.57126],[42.56172,54.36615],[42.22251,54.17469]]]}},{"type":"Feature","properties":{"id":"RU-AD"},"geometry":{"type":"Polygon","coordinates":[[[38.68423,44.95216],[38.82843,44.93673],[38.83701,44.92616],[38.84559,44.92403],[38.84362,44.91686],[38.85272,44.91625],[38.85023,44.90902],[38.86027,44.90756],[38.8619,44.89801],[38.85555,44.89783],[38.98773,44.85489],[39.09141,44.81959],[39.50735,44.81776],[39.6524,44.87107],[39.47284,45.00292],[39.59197,45.06309],[39.70046,44.9541],[39.88311,45.02525],[39.95658,44.89139],[39.94422,44.79353],[40.04276,44.74575],[39.90646,44.70721],[39.89134,44.38669],[40.11107,44.19008],[39.76707,43.97947],[40.41869,43.75026],[40.40565,44.72673],[40.51757,44.76282],[40.51895,44.74014],[40.67275,44.54154],[40.64872,44.46417],[40.78193,44.46466],[40.73112,44.60855],[40.65181,44.69477],[40.58521,44.86243],[40.44788,44.98204],[40.23193,45.11448],[39.948,45.08806],[39.70527,45.1433],[39.68776,45.198],[39.60811,45.21856],[39.3235,45.02161],[39.07768,44.98787],[39.05055,44.95726],[39.0073,45.0034],[38.98035,45.01069],[38.97296,45.00735],[38.97108,44.99187],[38.96412,44.99151],[38.95228,44.99903],[38.94687,45.02009],[38.95872,45.02864],[38.95339,45.03592],[38.93846,45.0358],[38.89924,45.01973],[38.9073,45.05321],[38.88902,45.05024],[38.84473,44.98495],[38.78826,45.00207],[38.75564,45.03253],[38.76937,45.05606],[38.68698,45.06842],[38.68423,44.95216]]]}},{"type":"Feature","properties":{"id":"RU-KL"},"geometry":{"type":"Polygon","coordinates":[[[41.68006,46.20644],[41.69036,46.01794],[42.13325,45.92273],[42.21393,46.12679],[42.34336,45.93682],[42.88719,46.19979],[43.89553,45.94971],[44.12109,45.68171],[45.70312,44.97694],[46.21879,44.97159],[46.92535,44.78476],[47.51959,44.80109],[47.55432,45.65244],[46.37603,45.66588],[46.99195,46.85549],[46.53259,47.40206],[46.87385,47.54745],[46.72579,47.62473],[46.37603,47.3937],[45.88371,47.62699],[45.69213,48.04366],[45.53867,48.00255],[45.1696,48.22124],[44.96103,48.27085],[44.83898,48.05467],[44.54372,48.05697],[44.54947,48.16448],[44.43197,48.2281],[44.32622,48.24113],[44.32193,48.03884],[44.43695,48.051],[44.37858,47.84012],[44.1053,47.90092],[43.62705,47.60986],[43.84128,47.24124],[44.18495,47.55011],[44.35935,47.3844],[43.54774,46.12465],[42.93765,46.45205],[42.74265,46.35782],[42.21908,46.56074],[41.95678,46.54705],[42.21084,46.3564],[41.9094,46.25228],[41.97051,46.17222],[41.68006,46.20644]]]}},{"type":"Feature","properties":{"id":"RU-AL"},"geometry":{"type":"Polygon","coordinates":[[[83.89297,51.10503],[84.318,50.99906],[84.66957,50.78727],[84.08386,50.64249],[84.29706,50.25115],[84.99198,50.06793],[85.24047,49.60239],[86.22413,49.49756],[86.6732,49.80874],[86.79056,49.74787],[86.60591,49.5968],[86.84881,49.51852],[87.03071,49.25142],[87.31465,49.23603],[87.28386,49.11626],[87.478,49.07403],[87.48983,49.13794],[87.81333,49.17354],[87.98977,49.18147],[88.15543,49.30314],[88.17223,49.46934],[88.42449,49.48821],[88.82499,49.44808],[89.70687,49.72535],[89.59711,49.90851],[89.33326,50.23304],[89.496,50.45837],[89.96978,50.40676],[88.77227,51.56085],[88.46878,51.28296],[87.88547,51.49549],[88.12271,51.74212],[87.88307,51.78355],[88.51821,52.3756],[87.89062,52.53627],[87.66677,52.41707],[87.13119,52.61451],[86.80847,52.66305],[86.79508,52.51705],[86.64127,52.52415],[86.58153,52.11388],[86.21211,52.00327],[86.14843,52.06832],[85.91892,52.10639],[85.91428,52.06146],[85.80734,52.03623],[85.90244,52.02408],[85.81918,51.97589],[85.82416,51.92934],[85.85351,51.91727],[85.87549,51.89937],[85.75464,51.8696],[85.71395,51.83641],[85.76219,51.82782],[85.70194,51.73415],[85.50247,51.70256],[85.45492,51.57034],[85.11194,51.52572],[84.72021,51.428],[84.64399,51.35152],[84.24728,51.28339],[84.13141,51.07699],[84.04815,51.15716],[83.89297,51.10503]]]}},{"type":"Feature","properties":{"id":"RU-KK"},"geometry":{"type":"Polygon","coordinates":[[[87.88307,51.78355],[88.12271,51.74212],[87.88547,51.49549],[88.46878,51.28296],[88.77227,51.56085],[89.77477,51.59413],[90.41747,52.16045],[90.92971,52.60283],[91.26617,52.6441],[91.48075,52.90828],[91.50615,52.99577],[91.44573,53.04492],[91.42238,53.09175],[91.48693,53.15788],[91.64314,53.14388],[91.81686,53.24508],[91.86012,53.31282],[91.93153,53.35137],[91.856,53.45657],[91.62322,53.53785],[91.54151,53.64748],[91.57791,53.66539],[91.56898,53.70869],[91.43096,53.81686],[91.58477,54.09645],[90.77041,54.91372],[90.13046,55.12668],[89.34734,55.02329],[88.7697,55.4028],[88.60198,55.42667],[88.43273,55.2574],[88.7091,54.85724],[88.39668,54.33614],[89.21447,54.34295],[89.02976,53.94638],[89.26734,53.8124],[88.72627,53.35424],[89.09499,53.13853],[88.9199,52.97758],[89.25498,52.80774],[88.51821,52.3756],[87.88307,51.78355]]]}},{"type":"Feature","properties":{"id":"RU-KR"},"geometry":{"type":"Polygon","coordinates":[[[29.30622,66.66539],[29.91155,66.13863],[30.12517,65.7552],[29.61914,65.23791],[29.8813,65.22101],[29.84096,65.1109],[29.61914,65.05993],[29.68972,64.80789],[30.05271,64.79072],[30.12329,64.64862],[29.99267,64.58058],[30.06279,64.35782],[30.4762,64.25728],[30.55687,64.09036],[30.25437,63.83364],[29.98213,63.75795],[30.49637,63.46666],[31.23244,63.22239],[31.29294,63.09035],[31.58535,62.91642],[31.38369,62.66284],[31.1634,62.45585],[30.42354,62.02281],[29.64454,61.52023],[29.30666,61.33001],[29.36817,61.27807],[29.55253,61.23886],[29.55047,61.18297],[29.7908,61.17056],[32.91229,60.67788],[33.2563,60.87232],[33.96423,61.03401],[33.47122,61.1929],[34.2073,61.23985],[34.449,61.13793],[34.54238,61.32168],[35.41717,61.14058],[36.77741,61.55083],[37.81494,61.52842],[37.8836,61.73152],[37.73528,62.47172],[36.54739,62.78488],[36.1972,63.42886],[36.06742,63.44836],[35.89284,63.57568],[35.91619,63.60462],[36.51718,63.62247],[36.55082,63.94913],[36.90032,64.23146],[35.44188,64.79518],[35.36497,66.19601],[32.8704,66.58362],[32.68157,66.58485],[31.97296,66.32537],[31.60079,66.66494],[29.30622,66.66539]]]}},{"type":"Feature","properties":{"id":"RU-YEV"},"geometry":{"type":"Polygon","coordinates":[[[130.52147,48.61745],[130.84462,48.30942],[130.65103,48.10052],[130.90915,47.90623],[130.95985,47.6957],[131.09871,47.6852],[131.2635,47.73325],[131.90448,47.68011],[132.57309,47.71741],[132.66989,47.96491],[134.49516,48.42884],[134.76619,48.36286],[135.0024,48.51751],[134.97562,48.62973],[134.72396,48.57297],[134.45857,48.68144],[134.40793,48.74826],[134.24812,48.72052],[134.27438,48.66693],[134.03715,48.63755],[133.85364,48.66659],[133.76326,48.73926],[133.71013,48.76099],[133.59237,48.73128],[133.23119,48.95587],[133.07876,49.21759],[132.01446,49.4592],[131.91696,49.26601],[131.1074,49.23239],[131.01951,49.02999],[131.04663,48.97345],[130.74382,48.9597],[130.61358,48.88862],[130.66946,48.88251],[130.52147,48.61745]]]}},{"type":"Feature","properties":{"id":"RU-YAN"},"geometry":{"type":"Polygon","coordinates":[[[62.07961,65.75037],[63.38561,64.3042],[65.81085,64.56555],[67.7362,64.01931],[68.84856,64.45977],[70.32073,64.34823],[70.48827,64.01389],[71.4331,63.69437],[71.65832,63.20702],[72.99316,63.3829],[74.8986,63.01759],[75.32913,63.09848],[76.68319,63.01074],[77.80791,62.58575],[79.63439,62.58575],[80.83739,63.12954],[84.53429,62.15267],[85.98724,64.06379],[82.09533,67.2019],[82.92205,68.80004],[78.93264,69.83583],[80.84563,70.62264],[79.11254,71.37067],[80.39245,71.88229],[78.81591,72.11782],[77.68431,73.6154],[69.60933,73.67727],[65.01706,69.58823],[64.3579,68.8932],[65.35766,68.79855],[65.54992,68.4416],[65.27251,67.97463],[66.18987,67.94061],[65.76965,67.59142],[66.22283,67.50751],[62.07961,65.75037]]]}},{"type":"Feature","properties":{"id":"RU-NEN"},"geometry":{"type":"Polygon","coordinates":[[[38.45276,68.81889],[44.60174,66.29282],[47.65628,65.82401],[48.08578,66.12746],[49.00176,66.11217],[51.54613,66.77472],[51.48536,66.94391],[51.72569,66.91821],[52.14832,67.10165],[53.86321,67.00099],[61.31744,67.00206],[62.86925,67.36418],[62.89672,67.59561],[63.58749,67.73807],[64.58312,68.37085],[65.54992,68.4416],[65.35766,68.79855],[64.3579,68.8932],[65.01706,69.58823],[59.1064,70.78692],[57.43647,70.2],[48.75604,70.23627],[46.97559,68.59708],[38.45276,68.81889]]]}},{"type":"Feature","properties":{"id":"RU-KHM"},"geometry":{"type":"Polygon","coordinates":[[[59.29321,63.20516],[59.64065,61.94734],[62.6152,61.20349],[62.8308,60.99442],[63.99535,59.36749],[65.96465,58.59688],[69.79476,59.92061],[71.01287,59.82963],[73.12498,58.89897],[75.17394,58.66551],[76.86034,60.14697],[77.06908,60.87032],[82.50731,60.58427],[86.11632,61.47813],[84.53429,62.15267],[80.83739,63.12954],[79.63439,62.58575],[77.80791,62.58575],[76.68319,63.01074],[75.32913,63.09848],[74.8986,63.01759],[72.99316,63.3829],[71.65832,63.20702],[71.4331,63.69437],[70.48827,64.01389],[70.32073,64.34823],[68.84856,64.45977],[67.7362,64.01931],[65.81085,64.56555],[63.38561,64.3042],[62.07961,65.75037],[60.69671,64.89151],[60.39527,65.08775],[59.7972,64.90025],[59.29321,63.20516]]]}},{"type":"Feature","properties":{"id":"IT-32"},"geometry":{"type":"Polygon","coordinates":[[[10.38659,46.67847],[10.40475,46.63671],[10.44686,46.64162],[10.49375,46.62049],[10.45299,46.53081],[10.46198,46.50022],[10.61176,46.46233],[10.55871,46.28337],[10.58721,46.24682],[10.45555,45.99171],[10.52764,45.82664],[10.54528,45.81773],[10.5079,45.79481],[10.55082,45.7838],[10.64875,45.81537],[10.69776,45.84255],[10.84556,45.83466],[10.8962,45.80893],[10.84144,45.71756],[10.89242,45.716],[10.93706,45.67104],[11.03044,45.70803],[11.13889,45.69673],[11.21807,45.84697],[11.26682,45.91951],[11.37393,45.90948],[11.37342,45.97501],[11.57821,46.00733],[11.6879,45.96785],[11.68044,46.04374],[11.70721,46.04541],[11.66825,46.09192],[11.90145,46.1231],[11.96376,46.19563],[11.93338,46.20306],[11.79897,46.34135],[11.89802,46.45151],[11.79339,46.4952],[12.02024,46.55437],[12.07775,46.6426],[12.24323,46.61265],[12.47875,46.67888],[12.37952,46.72452],[12.31378,46.79718],[12.27591,46.88651],[12.2006,46.88854],[12.11675,47.01241],[12.23291,47.04487],[12.20764,47.09821],[11.74789,46.98484],[11.50726,47.00642],[11.40724,46.96689],[11.33355,46.99862],[11.12094,46.93552],[11.01811,46.76214],[10.72974,46.78972],[10.75753,46.82258],[10.66405,46.87614],[10.54783,46.84505],[10.46999,46.85498],[10.38659,46.67847]]]}},{"type":"Feature","properties":{"id":"IT-36"},"geometry":{"type":"Polygon","coordinates":[[[12.31979,46.26599],[12.51789,46.12036],[12.42553,46.09085],[12.42611,45.94701],[12.45647,45.94503],[12.47806,45.92661],[12.50699,45.92434],[12.51042,45.90807],[12.52415,45.90335],[12.50819,45.89699],[12.53205,45.88104],[12.53119,45.86272],[12.54853,45.86135],[12.55565,45.82936],[12.59754,45.81659],[12.59402,45.83561],[12.62989,45.8276],[12.66766,45.78667],[12.73169,45.83723],[12.74306,45.82631],[12.78079,45.8551],[12.80203,45.83881],[12.80053,45.81722],[12.85542,45.85176],[12.87743,45.843],[12.86953,45.83376],[12.89473,45.8203],[12.95708,45.82269],[12.95056,45.85313],[12.99262,45.81522],[12.98129,45.78242],[13.00523,45.76393],[13.00583,45.74788],[13.03914,45.7172],[13.06652,45.67044],[13.21833,45.43996],[13.45644,45.59464],[13.6076,45.64761],[13.7198,45.59352],[13.74587,45.59811],[13.78445,45.5825],[13.84625,45.58227],[13.86771,45.59898],[13.8695,45.60835],[13.9191,45.6322],[13.87933,45.65207],[13.83422,45.68703],[13.83332,45.70855],[13.78968,45.74144],[13.66986,45.79955],[13.59784,45.8072],[13.58858,45.83503],[13.57563,45.8425],[13.58644,45.88173],[13.59565,45.89446],[13.60857,45.89907],[13.61931,45.91782],[13.63815,45.93607],[13.6329,45.94894],[13.64375,45.98296],[13.63458,45.98947],[13.62074,45.98388],[13.58903,45.99009],[13.56759,45.96991],[13.52963,45.96588],[13.51429,45.97808],[13.50104,45.98078],[13.47581,46.00703],[13.50186,46.02031],[13.50998,46.04498],[13.49568,46.04839],[13.49867,46.0595],[13.52102,46.0633],[13.57669,46.09406],[13.64053,46.13587],[13.66815,46.1776],[13.64451,46.18966],[13.56682,46.18703],[13.56114,46.2054],[13.47587,46.22725],[13.4149,46.20846],[13.37671,46.29668],[13.45344,46.32636],[13.43418,46.35992],[13.47019,46.3621],[13.5763,46.40915],[13.5763,46.42613],[13.59777,46.44137],[13.68684,46.43881],[13.7148,46.5222],[13.49599,46.55634],[13.27627,46.56059],[13.09123,46.59661],[12.94445,46.60401],[12.73361,46.63797],[12.61505,46.53737],[12.66036,46.47085],[12.50175,46.44211],[12.31979,46.26599]]]}},{"type":"Feature","properties":{"id":"IT-88"},"geometry":{"type":"Polygon","coordinates":[[[8.06341,41.16624],[8.28559,38.46209],[10.73363,38.54816],[9.72681,41.374],[8.06341,41.16624]]]}},{"type":"Feature","properties":{"id":"IT-82"},"geometry":{"type":"Polygon","coordinates":[[[10.73363,38.54816],[12.02012,35.25036],[12.80065,35.1178],[13.4634,35.88474],[14.12059,36.67252],[15.31045,36.18238],[15.59097,38.21067],[15.66101,38.26999],[15.39183,39.26629],[10.73363,38.54816]]]}},{"type":"Feature","properties":{"id":"IT-23"},"geometry":{"type":"Polygon","coordinates":[[[6.80785,45.71864],[6.98948,45.63869],[7.00037,45.509],[7.09297,45.46959],[7.57575,45.59266],[7.73772,45.55078],[7.82243,45.59665],[7.91118,45.5953],[7.92388,45.73518],[7.85949,45.91485],[7.56343,45.97421],[7.10685,45.85653],[7.04151,45.92435],[6.95315,45.85163],[6.80785,45.83265],[6.80785,45.71864]]]}},{"type":"Feature","properties":{"id":"GB-NIR"},"geometry":{"type":"Polygon","coordinates":[[[-8.1782,54.46614],[-8.14172,54.45063],[-8.16206,54.44204],[-8.04555,54.36292],[-7.99118,54.34675],[-7.95324,54.30721],[-7.86552,54.29318],[-7.8596,54.21779],[-7.81397,54.20159],[-7.69501,54.20731],[-7.55812,54.12239],[-7.4799,54.12239],[-7.44567,54.1539],[-7.32834,54.11475],[-7.30553,54.11869],[-7.34005,54.14698],[-7.29157,54.17191],[-7.28017,54.16714],[-7.29687,54.1354],[-7.29493,54.12013],[-7.26316,54.13863],[-7.25012,54.20063],[-7.14908,54.22732],[-7.19145,54.31296],[-7.02034,54.4212],[-6.87775,54.34682],[-6.85179,54.29176],[-6.87692,54.28036],[-6.83383,54.26291],[-6.81583,54.22791],[-6.74575,54.18788],[-6.70175,54.20218],[-6.6382,54.17071],[-6.66264,54.0666],[-6.62842,54.03503],[-6.47849,54.06947],[-6.36605,54.07234],[-6.36279,54.11248],[-6.32694,54.09337],[-6.29003,54.11278],[-6.26218,54.09785],[-5.81146,53.88396],[-4.86305,54.44148],[-6.1908,55.41031],[-6.79943,55.54107],[-6.71944,55.27952],[-6.9734,55.19878],[-7.2471,55.06933],[-7.34367,55.04808],[-7.4033,55.00391],[-7.40004,54.94498],[-7.44404,54.9403],[-7.4473,54.87003],[-7.47626,54.83084],[-7.54508,54.79401],[-7.54671,54.74606],[-7.64449,54.75265],[-7.75041,54.7103],[-7.83352,54.73854],[-7.93293,54.66603],[-7.68965,54.61736],[-7.77445,54.5839],[-7.82827,54.55539],[-7.85084,54.53358],[-8.00714,54.54528],[-8.04216,54.49297],[-8.09924,54.48405],[-8.09297,54.47697],[-8.1782,54.46614]]]}},{"type":"Feature","properties":{"id":"GB-WLS"},"geometry":{"type":"Polygon","coordinates":[[[-5.79914,52.03902],[-4.80826,51.25744],[-3.10467,51.35742],[-2.65663,51.60394],[-2.6689,51.6463],[-2.68032,51.64667],[-2.66615,51.7015],[-2.68117,51.72117],[-2.66332,51.75461],[-2.67937,51.80362],[-2.64427,51.83079],[-2.767,51.85104],[-2.78228,51.88284],[-2.88425,51.93426],[-2.96982,51.90228],[-3.12595,52.07834],[-3.12458,52.13938],[-3.06861,52.15339],[-3.12303,52.16487],[-3.05093,52.24651],[-2.95583,52.27088],[-3.01239,52.2786],[-2.99943,52.32096],[-2.95107,52.33607],[-2.95918,52.35316],[-3.06183,52.34729],[-3.10561,52.37339],[-3.18187,52.41077],[-3.23478,52.43382],[-3.19873,52.47797],[-2.98759,52.52593],[-3.00939,52.57895],[-3.12166,52.52269],[-3.1311,52.58667],[-3.06346,52.63295],[-3.02175,52.72142],[-2.95291,52.71508],[-3.08698,52.77753],[-3.09363,52.7883],[-3.17324,52.80831],[-3.12861,52.86892],[-3.15462,52.87969],[-3.09556,52.93011],[-3.02317,52.92805],[-3.00338,52.969],[-2.92751,52.93891],[-2.84734,52.94419],[-2.79584,52.89751],[-2.72684,52.92639],[-2.72598,52.98379],[-2.83515,52.99619],[-2.87601,53.08185],[-2.98896,53.15377],[-2.92785,53.17142],[-2.92013,53.17764],[-2.9221,53.18924],[-3.03531,53.25412],[-3.34945,53.45207],[-5.37267,53.63269],[-5.79914,52.03902]]]}},{"type":"Feature","properties":{"id":"ES-GA"},"geometry":{"type":"Polygon","coordinates":[[[-9.57169,43.18446],[-9.14112,41.86623],[-8.87157,41.86488],[-8.81794,41.90375],[-8.75712,41.92833],[-8.74606,41.9469],[-8.7478,41.96282],[-8.6681,41.99703],[-8.65832,42.02972],[-8.64626,42.03668],[-8.63791,42.04691],[-8.59493,42.05708],[-8.58086,42.05147],[-8.54563,42.0537],[-8.5252,42.06264],[-8.52837,42.07658],[-8.4444,42.08377],[-8.42512,42.07199],[-8.40143,42.08052],[-8.38323,42.07683],[-8.36353,42.09065],[-8.33912,42.08358],[-8.32703,42.08726],[-8.32161,42.10218],[-8.29809,42.106],[-8.27296,42.12432],[-8.25957,42.12108],[-8.25007,42.14018],[-8.2262,42.13069],[-8.19748,42.15436],[-8.18771,42.13722],[-8.19406,42.12141],[-8.18837,42.10812],[-8.18534,42.07171],[-8.17623,42.06477],[-8.11282,42.08339],[-8.08847,42.05767],[-8.08796,42.01398],[-8.15476,41.98509],[-8.21961,41.91064],[-8.19551,41.87459],[-8.16944,41.87944],[-8.16455,41.81753],[-8.0961,41.81024],[-8.01136,41.83453],[-7.9804,41.87337],[-7.92336,41.8758],[-7.90672,41.92649],[-7.88493,41.92597],[-7.88638,41.85568],[-7.84939,41.86201],[-7.84188,41.88065],[-7.69848,41.90977],[-7.65774,41.88308],[-7.58603,41.87944],[-7.61904,41.82961],[-7.51981,41.84073],[-7.49803,41.87095],[-7.44922,41.86436],[-7.44759,41.84451],[-7.42699,41.83293],[-7.42864,41.80589],[-7.37092,41.85031],[-7.32366,41.8406],[-7.18677,41.88793],[-7.18549,41.97515],[-7.14115,41.98855],[-7.08574,41.97401],[-7.07596,41.94977],[-7.01078,41.94977],[-6.98144,41.9728],[-6.97631,42.04056],[-7.03708,42.07006],[-6.70612,42.33621],[-6.83564,42.39981],[-6.80242,42.48633],[-7.04858,42.53284],[-7.02026,42.71637],[-6.96799,42.72772],[-6.96009,42.75495],[-6.83813,42.80006],[-6.84984,42.91418],[-6.85667,42.98053],[-6.96129,43.02711],[-6.96747,42.98907],[-6.99949,43.02786],[-6.96374,43.03756],[-6.94653,43.08324],[-6.89958,43.08286],[-6.89435,43.09202],[-6.8104,43.14333],[-6.87151,43.1864],[-6.89932,43.14752],[-6.94876,43.13118],[-6.98163,43.16887],[-6.96292,43.19191],[-7.07287,43.24363],[-7.06111,43.30613],[-7.09588,43.30075],[-7.13416,43.34259],[-7.12789,43.37504],[-7.18926,43.3853],[-7.1687,43.40624],[-7.17566,43.41418],[-7.17347,43.4288],[-7.10605,43.42636],[-7.08502,43.45718],[-7.04695,43.47796],[-6.97771,43.78895],[-7.91565,44.00862],[-9.57169,43.18446]]]}},{"type":"Feature","properties":{"id":"ES-IB"},"geometry":{"type":"Polygon","coordinates":[[[0.74156,38.66836],[1.8512,38.28539],[4.88397,39.94305],[2.8815,40.70665],[0.74156,38.66836]]]}},{"type":"Feature","properties":{"id":"ES-AS"},"geometry":{"type":"Polygon","coordinates":[[[-7.18926,43.3853],[-7.12789,43.37504],[-7.13416,43.34259],[-7.09588,43.30075],[-7.06111,43.30613],[-7.07287,43.24363],[-6.96292,43.19191],[-6.98163,43.16887],[-6.94876,43.13118],[-6.89932,43.14752],[-6.87151,43.1864],[-6.8104,43.14333],[-6.89435,43.09202],[-6.89958,43.08286],[-6.94653,43.08324],[-6.96374,43.03756],[-6.99949,43.02786],[-6.96747,42.98907],[-6.96129,43.02711],[-6.85667,42.98053],[-6.84984,42.91418],[-6.56484,42.9123],[-6.41567,42.94787],[-6.47283,42.96986],[-6.36983,43.05421],[-6.23165,43.00991],[-6.215,43.04518],[-6.1247,43.02083],[-6.09106,43.05872],[-5.97106,43.06437],[-5.96197,43.00715],[-5.78636,42.95579],[-5.7043,43.05948],[-5.59804,43.02347],[-5.39548,43.04844],[-5.38433,43.07565],[-5.09868,43.10323],[-5.0774,43.17864],[-5.00427,43.17263],[-4.89715,43.2382],[-4.83552,43.18077],[-4.72927,43.17864],[-4.73167,43.25495],[-4.64069,43.2712],[-4.6055,43.30088],[-4.54173,43.26801],[-4.51366,43.3005],[-4.54117,43.35785],[-4.51134,43.37941],[-4.52988,43.62298],[-6.97771,43.78895],[-7.04695,43.47796],[-7.08502,43.45718],[-7.10605,43.42636],[-7.17347,43.4288],[-7.17566,43.41418],[-7.1687,43.40624],[-7.18926,43.3853]]]}},{"type":"Feature","properties":{"id":"GB-ENG"},"geometry":{"type":"Polygon","coordinates":[[[-6.81839,49.7273],[1.17405,50.74239],[2.18458,51.52087],[2.65913,51.8564],[-1.16893,55.97338],[-2.0668,55.80075],[-2.088,55.79085],[-2.08568,55.76315],[-2.17906,55.71676],[-2.16791,55.70835],[-2.21975,55.67371],[-2.22043,55.66422],[-2.24824,55.651],[-2.23108,55.64504],[-2.23546,55.6403],[-2.30798,55.64655],[-2.33768,55.63332],[-2.28781,55.59726],[-2.28481,55.57849],[-2.2382,55.55252],[-2.17443,55.46698],[-2.23228,55.42716],[-2.33682,55.40358],[-2.3521,55.36135],[-2.49046,55.35179],[-2.64375,55.25935],[-2.70315,55.1719],[-2.80735,55.13635],[-2.82571,55.13885],[-2.8276,55.12477],[-2.8585,55.10803],[-2.90184,55.07704],[-2.9142,55.0765],[-2.95828,55.04941],[-3.04338,55.05566],[-3.02759,55.03579],[-3.05814,54.98608],[-3.46344,54.91293],[-4.1819,54.57861],[-3.64906,54.12723],[-3.34945,53.45207],[-3.03531,53.25412],[-2.9221,53.18924],[-2.92013,53.17764],[-2.92785,53.17142],[-2.98896,53.15377],[-2.87601,53.08185],[-2.83515,52.99619],[-2.72598,52.98379],[-2.72684,52.92639],[-2.79584,52.89751],[-2.84734,52.94419],[-2.92751,52.93891],[-3.00338,52.969],[-3.02317,52.92805],[-3.09556,52.93011],[-3.15462,52.87969],[-3.12861,52.86892],[-3.17324,52.80831],[-3.09363,52.7883],[-3.08698,52.77753],[-2.95291,52.71508],[-3.02175,52.72142],[-3.06346,52.63295],[-3.1311,52.58667],[-3.12166,52.52269],[-3.00939,52.57895],[-2.98759,52.52593],[-3.19873,52.47797],[-3.23478,52.43382],[-3.18187,52.41077],[-3.10561,52.37339],[-3.06183,52.34729],[-2.95918,52.35316],[-2.95107,52.33607],[-2.99943,52.32096],[-3.01239,52.2786],[-2.95583,52.27088],[-3.05093,52.24651],[-3.12303,52.16487],[-3.06861,52.15339],[-3.12458,52.13938],[-3.12595,52.07834],[-2.96982,51.90228],[-2.88425,51.93426],[-2.78228,51.88284],[-2.767,51.85104],[-2.64427,51.83079],[-2.67937,51.80362],[-2.66332,51.75461],[-2.68117,51.72117],[-2.66615,51.7015],[-2.68032,51.64667],[-2.6689,51.6463],[-2.65663,51.60394],[-3.10467,51.35742],[-4.80826,51.25744],[-6.81839,49.7273]]]}},{"type":"Feature","properties":{"id":"AG-10"},"geometry":{"type":"Polygon","coordinates":[[[-62.27053,17.22145],[-61.45192,17.43725],[-61.45764,17.9187],[-62.12601,17.9235],[-62.27053,17.22145]]]}},{"type":"Feature","properties":{"id":"AG-11"},"geometry":{"type":"Polygon","coordinates":[[[-62.62949,16.82364],[-62.52079,16.69392],[-62.14123,17.02632],[-62.27053,17.22145],[-62.62949,16.82364]]]}},{"type":"Feature","properties":{"id":"FJ-R"},"geometry":{"type":"Polygon","coordinates":[[[176.74941,-12.72206],[177.3413,-12.72206],[177.3413,-12.25949],[176.74941,-12.25949],[176.74941,-12.72206]]]}},{"type":"Feature","properties":{"id":"GE-AJ"},"geometry":{"type":"Polygon","coordinates":[[[41.2657,41.64323],[41.54366,41.52185],[41.7148,41.4932],[41.7124,41.47417],[41.81939,41.43621],[41.95134,41.52466],[42.26387,41.49346],[42.51772,41.43606],[42.60163,41.58516],[42.49554,41.71155],[42.52292,41.78411],[42.40756,41.84245],[42.09102,41.80497],[41.96494,41.85236],[41.95773,41.88585],[41.77216,41.89687],[41.77023,41.91221],[41.2657,41.64323]]]}},{"type":"Feature","properties":{"id":"GE-AB"},"geometry":{"type":"Polygon","coordinates":[[[39.8664,43.20124],[41.29005,42.40875],[41.5496,42.40977],[41.62934,42.4353],[41.66067,42.43701],[41.64745,42.48184],[41.69594,42.51064],[41.76984,42.52133],[41.85173,42.57684],[41.86031,42.6001],[41.94906,42.63951],[41.86992,42.99987],[42.05017,43.06086],[42.13085,43.20326],[41.64935,43.22331],[40.65957,43.56212],[40.10657,43.57344],[40.04445,43.47776],[40.03312,43.44262],[40.01007,43.42411],[40.01552,43.42025],[40.00853,43.40578],[40.0078,43.38551],[39.8664,43.20124]]]}},{"type":"Feature","properties":{"id":"GE-SK"},"geometry":{"type":"Polygon","coordinates":[[[43.42157,41.9688],[43.56388,41.9153],[43.71356,41.80151],[44.20211,41.73391],[44.48656,41.75492],[44.5099,41.8326],[44.57015,41.86904],[44.57153,41.90036],[44.50441,41.99305],[44.48656,42.06191],[44.5626,42.04827],[44.51402,42.08637],[44.59058,42.14533],[44.57136,42.16683],[44.62732,42.24326],[44.56861,42.38441],[44.37789,42.46804],[44.22978,42.64904],[43.95517,42.55396],[43.73119,42.62043],[43.81453,42.74297],[43.58207,42.57356],[43.59975,42.39772],[43.62619,42.382],[43.59349,42.35654],[43.66996,42.31768],[43.56765,42.11274],[43.48388,42.0475],[43.50997,42.0141],[43.42157,41.9688]]]}},{"type":"Feature","properties":{"id":"GR-69"},"geometry":{"type":"Polygon","coordinates":[[[23.95294,40.22974],[24.31686,40.02025],[24.6231,40.10013],[23.95774,40.54824],[24.01774,40.333],[23.95294,40.22974]]]}},{"type":"Feature","properties":{"id":"ID-YO"},"geometry":{"type":"Polygon","coordinates":[[[109.90957,-8.16239],[110.77351,-8.43418],[110.83934,-8.17869],[110.81617,-8.16476],[110.83059,-8.1357],[110.79402,-8.15983],[110.77737,-8.06721],[110.75506,-8.02778],[110.78613,-7.8178],[110.76501,-7.80828],[110.76518,-7.82375],[110.7518,-7.82511],[110.75077,-7.81491],[110.71171,-7.78991],[110.71343,-7.80777],[110.67197,-7.80887],[110.6718,-7.78574],[110.65704,-7.80522],[110.65129,-7.79263],[110.63901,-7.8002],[110.59833,-7.79943],[110.60073,-7.80666],[110.57507,-7.80607],[110.58219,-7.79187],[110.56031,-7.78081],[110.55679,-7.79272],[110.53464,-7.79663],[110.49139,-7.7621],[110.49139,-7.74297],[110.44761,-7.53302],[110.39852,-7.58875],[110.3047,-7.65459],[110.26685,-7.7172],[110.26187,-7.64932],[110.14163,-7.64728],[110.11648,-7.67075],[110.13819,-7.69355],[110.12291,-7.75419],[110.04395,-7.85555],[110.04313,-7.88191],[110.03223,-7.89364],[110.00387,-7.88531],[109.90957,-8.16239]]]}},{"type":"Feature","properties":{"id":"ID-AC"},"geometry":{"type":"Polygon","coordinates":[[[94.88572,6.34977],[95.14454,2.93297],[96.37521,1.33288],[98.21296,2.09997],[98.13022,2.16516],[98.17897,2.24046],[98.19717,2.31199],[98.08353,2.513],[98.10928,2.79354],[97.94517,2.91595],[97.93109,3.20496],[97.86792,3.24986],[98.05761,3.29031],[97.79857,3.72825],[97.92577,3.84284],[98.04164,4.01872],[98.05417,4.13516],[98.07289,4.142],[98.0655,4.1771],[98.09915,4.20535],[98.06653,4.25055],[98.15425,4.26167],[98.15451,4.28932],[98.31939,4.28889],[98.6357,4.47525],[97.67511,5.454],[94.88572,6.34977]]]}},{"type":"Feature","properties":{"id":"ID-PE"},"geometry":{"type":"Polygon","coordinates":[[[137.83378,-4.5422],[139.01138,-4.80362],[138.90151,-4.96098],[140.27068,-5.0882],[140.58105,-5.27352],[140.9987,-5.17984],[140.99934,-3.92043],[138.69964,-3.22278],[138.57639,-3.34755],[138.17367,-3.10417],[138.07479,-3.13228],[138.04733,-3.59908],[138.29486,-3.63951],[138.23959,-3.84918],[137.98416,-3.92179],[137.83378,-4.5422]]]}},{"type":"Feature","properties":{"id":"ID-PA"},"geometry":{"type":"Polygon","coordinates":[[[134.40878,1.79674],[134.84618,-2.17475],[136.56898,-3.05138],[136.52503,-3.25363],[136.93839,-3.12817],[138.07479,-3.13228],[138.17367,-3.10417],[138.57639,-3.34755],[138.69964,-3.22278],[140.99934,-3.92043],[141.00167,0.68547],[134.40878,1.79674]]]}},{"type":"Feature","properties":{"id":"ID-PS"},"geometry":{"type":"Polygon","coordinates":[[[137.03241,-8.98668],[139.41724,-8.97063],[140.88922,-9.34945],[141.00782,-9.1242],[141.01763,-6.90181],[140.85295,-6.72996],[140.99813,-6.3233],[140.9987,-5.17984],[140.58105,-5.27352],[140.27068,-5.0882],[138.90151,-4.96098],[139.01138,-4.80362],[137.83378,-4.5422],[137.64953,-5.5675],[137.03241,-8.98668]]]}},{"type":"Feature","properties":{"id":"ID-PT"},"geometry":{"type":"Polygon","coordinates":[[[134.59213,-3.19536],[135.29388,-3.82862],[135.30349,-4.08276],[135.03227,-4.05674],[134.63744,-4.91309],[137.64953,-5.5675],[137.83378,-4.5422],[137.98416,-3.92179],[138.23959,-3.84918],[138.29486,-3.63951],[138.04733,-3.59908],[138.07479,-3.13228],[136.93839,-3.12817],[136.52503,-3.25363],[136.56898,-3.05138],[134.84618,-2.17475],[134.59213,-3.19536]]]}},{"type":"Feature","properties":{"id":"ID-PB"},"geometry":{"type":"Polygon","coordinates":[[[128.97399,-1.5324],[132.88512,-4.73519],[134.63744,-4.91309],[135.03227,-4.05674],[135.30349,-4.08276],[135.29388,-3.82862],[134.59213,-3.19536],[134.84618,-2.17475],[134.40878,1.79674],[128.97621,3.08804],[129.98748,0.26092],[128.97399,-1.5324]]]}},{"type":"Feature","properties":{"id":"MU-RO"},"geometry":{"type":"Polygon","coordinates":[[[62.98922,-20.11596],[63.91009,-19.90996],[63.32998,-19.13235],[62.98922,-20.11596]]]}},{"type":"Feature","properties":{"id":"MD-SN"},"geometry":{"type":"Polygon","coordinates":[[[28.51164,48.12396],[28.63277,48.08817],[28.51295,48.00301],[28.56857,47.99497],[28.59535,48.03447],[28.69148,48.02173],[28.72838,47.97877],[28.77817,47.98452],[28.83619,47.93819],[28.81954,47.90322],[29.00064,47.87041],[29.02038,47.83747],[28.99154,47.79516],[28.96751,47.7138],[28.99738,47.64619],[28.98141,47.61611],[29.02982,47.56749],[29.01643,47.52241],[29.13076,47.46245],[29.06742,47.41554],[29.18895,47.34859],[29.12595,47.32393],[29.12063,47.27899],[29.29298,47.10892],[29.34516,47.10845],[29.39872,47.00648],[29.29195,46.99524],[29.33315,46.93174],[29.46327,46.95682],[29.48867,46.85267],[29.50721,46.79289],[29.55665,46.77608],[29.61158,46.82731],[29.75389,46.62963],[29.73157,46.60463],[29.88916,46.54302],[29.94409,46.56002],[29.9743,46.75325],[29.94522,46.80055],[29.98632,46.81838],[29.88195,46.88589],[29.75458,46.8604],[29.72986,46.92234],[29.57056,46.94766],[29.62137,47.05069],[29.61038,47.09932],[29.53044,47.07851],[29.49732,47.12878],[29.57696,47.13581],[29.55236,47.25115],[29.59905,47.25721],[29.57399,47.37022],[29.48678,47.36043],[29.47854,47.30366],[29.39889,47.30179],[29.38156,47.37812],[29.30499,47.44202],[29.24543,47.42727],[29.18277,47.44805],[29.11743,47.55001],[29.22414,47.60012],[29.22191,47.7384],[29.27255,47.79953],[29.20663,47.80367],[29.27942,47.88952],[29.19977,47.88837],[29.17453,47.99348],[28.92408,47.96257],[28.8414,48.03392],[28.85232,48.12506],[28.69896,48.13106],[28.53921,48.17453],[28.51164,48.12396]]]}},{"type":"Feature","properties":{"id":"MD-BD"},"geometry":{"type":"Polygon","coordinates":[[[29.4001,46.75397],[29.50721,46.79289],[29.48867,46.85267],[29.40456,46.84774],[29.4001,46.75397]]]}},{"type":"Feature","properties":{"id":"PG-NSB"},"geometry":{"type":"Polygon","coordinates":[[[154.00084,-5.00338],[155.29973,-7.02898],[155.94342,-6.85209],[156.07484,-6.52458],[155.28624,-2.70162],[154.02007,-2.72906],[154.00084,-5.00338]]]}},{"type":"Feature","properties":{"id":"PT-20"},"geometry":{"type":"Polygon","coordinates":[[[-31.64064,39.2663],[-24.73024,36.54496],[-24.15895,38.62546],[-31.55275,39.97713],[-31.64064,39.2663]]]}},{"type":"Feature","properties":{"id":"PT-30"},"geometry":{"type":"Polygon","coordinates":[[[-17.69898,33.17894],[-16.18156,29.73901],[-15.56383,29.89383],[-16.06202,33.33053],[-17.69898,33.17894]]]}},{"type":"Feature","properties":{"id":"KN-N"},"geometry":{"type":"Polygon","coordinates":[[[-62.8252,16.98937],[-62.62949,16.82364],[-62.27053,17.22145],[-62.45247,17.37627],[-62.8252,16.98937]]]}},{"type":"Feature","properties":{"id":"KN-K"},"geometry":{"type":"Polygon","coordinates":[[[-63.11114,17.23125],[-62.8252,16.98937],[-62.45247,17.37627],[-62.76692,17.64353],[-63.11114,17.23125]]]}},{"type":"Feature","properties":{"id":"ST-P"},"geometry":{"type":"Polygon","coordinates":[[[6.69947,1.30108],[7.47763,0.84469],[8.0168,1.79377],[7.23334,2.23756],[6.69947,1.30108]]]}},{"type":"Feature","properties":{"id":"KR-49"},"geometry":{"type":"Polygon","coordinates":[[[125.67809,33.6695],[126.29984,32.61914],[127.37787,33.46942],[126.28234,34.15273],[125.67809,33.6695]]]}},{"type":"Feature","properties":{"id":"TJ-GB"},"geometry":{"type":"Polygon","coordinates":[[[70.29653,37.98689],[70.32889,37.99853],[70.36683,38.04231],[70.48999,38.12004],[70.52973,38.20277],[70.54673,38.24541],[70.60389,38.28202],[70.61239,38.34862],[70.64908,38.34885],[70.69461,38.37056],[70.67603,38.38852],[70.67438,38.40597],[70.69367,38.41832],[70.72485,38.4131],[70.75455,38.4252],[70.77132,38.45548],[70.78581,38.45502],[70.78702,38.45031],[70.79766,38.44944],[70.80521,38.44447],[70.81697,38.44507],[70.82538,38.45394],[70.84376,38.44688],[70.88719,38.46826],[70.92451,38.43039],[70.98936,38.49011],[71.0315,38.45231],[71.05609,38.39959],[71.09484,38.42414],[71.10592,38.42077],[71.10957,38.40671],[71.1451,38.40106],[71.21415,38.32872],[71.26041,38.31135],[71.33216,38.30549],[71.33272,38.27427],[71.37362,38.25691],[71.36461,38.1963],[71.37564,38.1602],[71.34997,38.1494],[71.32942,38.11146],[71.30354,38.04359],[71.28354,38.0417],[71.29427,38.0178],[71.27642,38.00603],[71.26848,37.99156],[71.27457,37.96653],[71.2505,37.92724],[71.2809,37.91995],[71.296,37.93403],[71.32871,37.88564],[71.51565,37.95349],[71.59257,37.92294],[71.58614,37.89551],[71.59832,37.87404],[71.58468,37.84774],[71.59369,37.81432],[71.59255,37.79956],[71.55481,37.78509],[71.54324,37.77104],[71.53052,37.76521],[71.55241,37.73515],[71.54236,37.69407],[71.53138,37.67835],[71.5267,37.63313],[71.51957,37.62035],[71.51168,37.61484],[71.4952,37.53926],[71.50434,37.52701],[71.50616,37.50733],[71.52648,37.47983],[71.49612,37.4279],[71.4973,37.40715],[71.47945,37.40664],[71.47451,37.38625],[71.48906,37.38055],[71.4949,37.36961],[71.48701,37.33312],[71.49821,37.31975],[71.50725,37.31563],[71.48454,37.26017],[71.48339,37.23937],[71.47386,37.2269],[71.4555,37.21418],[71.4494,37.18137],[71.44127,37.11856],[71.43097,37.05855],[71.45578,37.03094],[71.46923,36.99925],[71.46683,36.95222],[71.51502,36.89128],[71.57195,36.74943],[71.67034,36.67268],[71.83977,36.67888],[72.04215,36.82],[72.33673,36.98596],[72.54095,37.00007],[72.66381,37.02014],[72.80862,37.22513],[73.06884,37.31729],[73.29633,37.46495],[73.77197,37.4417],[73.76647,37.33913],[73.61129,37.27469],[73.64974,37.23643],[73.82552,37.22659],[73.8564,37.26158],[74.20308,37.34208],[74.23339,37.41116],[74.56161,37.37734],[74.68383,37.3948],[74.8294,37.3435],[74.88887,37.23275],[75.12328,37.31839],[75.13907,37.42124],[75.06011,37.52779],[74.94338,37.55501],[74.8912,37.67576],[75.00935,37.77486],[74.92416,37.83428],[74.9063,38.03033],[74.82665,38.07359],[74.80331,38.19889],[74.69894,38.22155],[74.69619,38.42947],[74.51217,38.47034],[74.17022,38.65504],[73.97933,38.52945],[73.79806,38.61106],[73.80656,38.66449],[73.7033,38.84782],[73.7445,38.93867],[73.82964,38.91517],[73.81728,39.04007],[73.75823,39.023],[73.60638,39.24534],[73.54572,39.27567],[73.55396,39.3543],[73.5004,39.38402],[73.59241,39.40843],[73.59831,39.46425],[73.45096,39.46677],[73.31912,39.38615],[73.18454,39.35536],[72.85934,39.35116],[72.62027,39.39696],[72.33173,39.33093],[72.23834,39.17248],[72.05657,38.80386],[71.28547,38.48423],[71.21749,38.59862],[71.04789,38.60023],[71.01802,38.80734],[70.7904,38.75167],[70.72105,38.80547],[70.55213,38.71551],[70.61462,38.65495],[70.33859,38.40517],[70.37841,38.27053],[70.29653,37.98689]]]}},{"type":"Feature","properties":{"id":"TZ-06"},"geometry":{"type":"Polygon","coordinates":[[[39.52444,-5.18842],[39.78853,-5.16309],[39.79213,-5.19899],[39.97111,-5.17958],[39.99221,-4.78982],[39.62121,-4.68136],[39.56066,-4.99867],[39.52444,-5.18842]]]}},{"type":"Feature","properties":{"id":"TZ-10"},"geometry":{"type":"Polygon","coordinates":[[[39.47207,-5.46272],[39.94979,-5.5732],[39.97111,-5.17958],[39.79213,-5.19899],[39.78853,-5.16309],[39.52444,-5.18842],[39.47207,-5.46272]]]}},{"type":"Feature","properties":{"id":"TZ-15"},"geometry":{"type":"Polygon","coordinates":[[[38.93279,-6.08456],[39.2376,-6.46344],[39.3283,-6.27161],[39.2871,-6.20609],[39.26925,-6.1525],[39.27972,-6.02806],[38.93279,-6.08456]]]}},{"type":"Feature","properties":{"id":"TZ-07"},"geometry":{"type":"Polygon","coordinates":[[[38.93279,-6.08456],[39.27972,-6.02806],[39.92698,-5.99402],[39.94979,-5.5732],[39.47207,-5.46272],[38.93279,-6.08456]]]}},{"type":"Feature","properties":{"id":"TZ-11"},"geometry":{"type":"Polygon","coordinates":[[[39.2376,-6.46344],[39.89822,-6.52409],[39.92698,-5.99402],[39.27972,-6.02806],[39.26925,-6.1525],[39.2871,-6.20609],[39.3283,-6.27161],[39.2376,-6.46344]]]}},{"type":"Feature","properties":{"id":"TT-TOB"},"geometry":{"type":"Polygon","coordinates":[[[-61.08554,11.23803],[-60.54419,10.87433],[-60.21728,11.59108],[-61.08554,11.23803]]]}},{"type":"Feature","properties":{"id":"UZ-QR"},"geometry":{"type":"Polygon","coordinates":[[[55.99849,44.99862],[56.00314,41.32584],[57.03423,41.25435],[57.12628,41.33429],[56.96218,41.80383],[57.03633,41.92043],[57.6296,42.16519],[57.84932,42.18555],[57.92897,42.24047],[57.90975,42.4374],[57.99214,42.50021],[58.3492,42.43335],[58.40688,42.29535],[58.51674,42.30348],[58.28453,42.55662],[58.14321,42.62159],[58.27504,42.69632],[58.57995,42.64103],[58.6266,42.79314],[59.00636,42.52436],[59.17528,42.53044],[59.2955,42.37064],[59.46212,42.29178],[59.73867,42.29965],[59.89128,42.298],[60.01831,42.2069],[59.96419,42.1428],[60.04659,42.08982],[60.0356,42.01028],[59.90226,41.99968],[60.32764,41.76772],[60.31082,41.74749],[60.08504,41.80997],[60.04869,41.74967],[60.18117,41.60082],[60.04199,41.44478],[60.5078,41.21694],[61.03261,41.25691],[61.22212,41.14946],[61.33199,41.14946],[61.39732,41.19873],[61.4446,41.29407],[62.01711,43.51008],[61.01475,44.41383],[58.58792,45.59067],[55.99849,44.99862]]]}},{"type":"Feature","properties":{"id":"KM-G"},"geometry":{"type":"Polygon","coordinates":[[[42.73663,-11.99939],[43.36759,-12.38139],[43.9345,-11.48591],[43.30354,-11.10267],[42.73663,-11.99939]]]}},{"type":"Feature","properties":{"id":"KM-M"},"geometry":{"type":"Polygon","coordinates":[[[43.36759,-12.38139],[43.85516,-12.6762],[44.42208,-11.78171],[43.9345,-11.48591],[43.36759,-12.38139]]]}},{"type":"Feature","properties":{"id":"KM-A"},"geometry":{"type":"Polygon","coordinates":[[[43.85516,-12.6762],[44.38701,-12.99739],[44.95392,-12.10401],[44.42208,-11.78171],[43.85516,-12.6762]]]}},{"type":"Feature","properties":{"id":"ID-PP"},"geometry":{"type":"Polygon","coordinates":[[[128.97399,-1.5324],[132.88512,-4.73519],[134.63744,-4.91309],[137.64953,-5.5675],[137.03241,-8.98668],[139.41724,-8.97063],[140.88922,-9.34945],[141.00782,-9.1242],[141.01763,-6.90181],[140.85295,-6.72996],[140.99813,-6.3233],[140.9987,-5.17984],[140.99934,-3.92043],[141.00167,0.68547],[134.40878,1.79674],[128.97621,3.08804],[129.98748,0.26092],[128.97399,-1.5324]]]}},{"type":"Feature","properties":{"id":"ID-SM"},"geometry":{"type":"Polygon","coordinates":[[[94.88572,6.34977],[95.14454,2.93297],[96.37521,1.33288],[103.58581,-7.54082],[109.42381,-3.18438],[107.28697,0.26368],[109.6941,2.68827],[108.0658,5.08493],[105.43734,3.24387],[104.56723,1.44271],[104.34728,1.33529],[104.03527,1.2689],[103.75628,1.1424],[103.55749,1.19701],[103.03657,1.30383],[99.75778,3.86466],[98.6357,4.47525],[97.67511,5.454],[94.88572,6.34977]]]}},{"type":"Feature","properties":{"id":"ID-JW"},"geometry":{"type":"Polygon","coordinates":[[[103.58581,-7.54082],[109.90957,-8.16239],[110.77351,-8.43418],[115.13879,-9.59623],[114.41024,-8.36776],[114.42809,-8.07962],[116.36168,-7.57487],[116.56493,-5.43007],[109.42381,-3.18438],[103.58581,-7.54082]]]}},{"type":"Feature","properties":{"id":"ID-KA"},"geometry":{"type":"Polygon","coordinates":[[[107.28697,0.26368],[109.42381,-3.18438],[116.56493,-5.43007],[119.59714,1.47751],[118.42088,3.91562],[117.89538,4.16637],[117.48789,4.16597],[117.25801,4.35108],[116.14265,4.33888],[115.90217,4.37708],[115.59642,3.91666],[115.53713,3.14776],[115.11343,2.82879],[115.1721,2.49671],[114.80706,2.21665],[114.80706,1.92351],[114.57892,1.5],[114.03788,1.44787],[113.64677,1.23933],[113.01448,1.42832],[113.021,1.57819],[112.48648,1.56516],[112.2127,1.44135],[112.15679,1.17004],[111.94553,1.12016],[111.82846,0.99349],[111.66847,1.0378],[111.53491,0.95902],[111.22979,1.08326],[110.62374,0.873],[110.49182,0.88088],[110.35354,0.98869],[109.97503,1.3227],[109.95134,1.4164],[109.85692,1.43545],[109.66397,1.60425],[109.66397,1.79972],[109.57923,1.80624],[109.53794,1.91771],[109.62558,1.99182],[109.64506,2.08014],[109.6941,2.68827],[107.28697,0.26368]]]}},{"type":"Feature","properties":{"id":"ID-NU"},"geometry":{"type":"Polygon","coordinates":[[[114.41024,-8.36776],[115.13879,-9.59623],[122.91521,-11.65621],[125.68138,-9.85176],[125.08792,-9.46277],[125.06166,-9.36082],[124.99746,-9.28172],[124.98227,-9.19149],[125.05672,-9.17022],[125.09434,-9.19669],[125.17993,-9.16819],[125.18632,-9.03142],[125.11196,-8.96494],[125.0814,-9.01699],[124.95574,-9.06277],[124.95128,-8.9585],[124.94011,-8.85617],[124.48554,-9.12883],[124.46288,-9.30339],[124.38451,-9.36395],[124.35258,-9.43002],[124.3535,-9.48493],[124.28115,-9.50453],[124.28115,-9.42189],[124.21247,-9.36904],[124.14517,-9.42324],[124.10539,-9.41206],[124.03753,-9.33294],[125.62512,-8.11166],[116.36168,-7.57487],[114.42809,-8.07962],[114.41024,-8.36776]]]}},{"type":"Feature","properties":{"id":"ID-SL"},"geometry":{"type":"Polygon","coordinates":[[[116.36168,-7.57487],[125.62512,-8.11166],[123.97521,-1.42257],[127.84676,3.66842],[126.69413,6.02692],[125.13682,4.79327],[124.35423,1.62576],[119.59714,1.47751],[116.56493,-5.43007],[116.36168,-7.57487]]]}},{"type":"Feature","properties":{"id":"ID-ML"},"geometry":{"type":"Polygon","coordinates":[[[123.97521,-1.42257],[125.62512,-8.11166],[127.49908,-8.26585],[127.55165,-9.05052],[137.03241,-8.98668],[137.64953,-5.5675],[134.63744,-4.91309],[132.88512,-4.73519],[128.97399,-1.5324],[129.98748,0.26092],[128.97621,3.08804],[127.84676,3.66842],[123.97521,-1.42257]]]}},{"type":"Feature","properties":{"id":"PH-14"},"geometry":{"type":"Polygon","coordinates":[[[119.00684,4.70611],[119.01488,4.00674],[123.56322,6.55548],[124.02465,6.76553],[124.26429,6.76314],[124.26429,6.75087],[124.53071,6.75257],[124.57946,6.78121],[124.57946,6.86405],[124.6574,6.8828],[124.74838,6.74951],[124.77876,6.72155],[124.77584,6.63476],[124.873,6.63971],[124.93755,6.71558],[124.97943,6.72871],[124.91146,6.79178],[124.82665,6.80371],[124.83627,7.04808],[124.71911,7.19653],[124.69345,7.18333],[124.73636,7.09271],[124.69224,7.07091],[124.69448,6.94244],[124.54547,7.01886],[124.52977,7.03198],[124.50436,7.02857],[124.4575,7.05907],[124.45569,7.08011],[124.43724,7.09339],[124.51277,7.23476],[124.45003,7.24157],[124.37913,7.16868],[124.33639,7.16902],[124.41329,7.29862],[124.44368,7.30023],[124.48162,7.45881],[124.41707,7.50749],[124.41467,7.62626],[124.51148,7.71711],[124.58307,7.62014],[124.81121,7.60925],[124.76005,7.64932],[124.73413,7.7024],[124.73327,7.74501],[124.71834,7.7502],[124.66336,7.83192],[124.63663,7.84195],[124.60281,7.90385],[124.61791,7.96437],[124.38686,8.12907],[124.30171,8.06211],[124.21091,8.05124],[124.12353,8.02319],[124.11186,7.93071],[123.97865,7.89739],[123.98311,7.73378],[123.86707,7.73412],[123.83788,7.58849],[122.97546,6.86098],[122.03269,6.80985],[122.03046,6.69444],[122.06085,6.56144],[121.93056,6.56417],[121.93004,6.61993],[121.91596,6.67449],[121.86927,6.6762],[121.72302,7.05455],[119.70977,6.45723],[119.58617,5.27831],[119.00684,4.70611]]]}},{"type":"Feature","properties":{"id":"BA-BRC"},"geometry":{"type":"Polygon","coordinates":[[[18.51762,44.81691],[18.56414,44.73636],[18.63504,44.73606],[18.65916,44.70679],[18.72456,44.74959],[18.80902,44.75535],[18.82202,44.75864],[18.8188,44.76709],[18.84271,44.78326],[18.85816,44.77699],[18.89086,44.77589],[18.92163,44.76836],[18.96974,44.82915],[18.98851,44.83001],[19.01767,44.82113],[19.02123,44.82851],[18.98527,44.84643],[19.01994,44.85493],[18.86678,44.85233],[18.76347,44.90669],[18.76369,44.93707],[18.80661,44.93561],[18.78515,44.96742],[18.74143,44.95017],[18.63899,44.90975],[18.60671,44.8454],[18.56096,44.82929],[18.51762,44.81691]]]}},{"type":"Feature","properties":{"id":"RS-VO"},"geometry":{"type":"Polygon","coordinates":[[[18.81394,45.91329],[18.85783,45.85493],[18.90305,45.71863],[18.96691,45.66731],[18.88776,45.57253],[18.94562,45.53712],[19.07471,45.53086],[19.08364,45.48804],[18.99918,45.49333],[18.97446,45.37528],[19.10774,45.29547],[19.28208,45.23813],[19.41941,45.23475],[19.43589,45.17137],[19.19144,45.17863],[19.14063,45.12972],[19.07952,45.14668],[19.1011,45.01191],[19.05205,44.97692],[19.15573,44.95409],[19.06853,44.89915],[19.02871,44.92541],[18.98957,44.90645],[19.01994,44.85493],[19.19191,44.92202],[19.29903,44.9095],[19.47206,44.92774],[19.48596,44.90561],[19.74208,44.92579],[19.7565,44.89564],[19.68029,44.79548],[19.70209,44.76818],[19.99923,44.68867],[19.91203,44.63763],[19.94533,44.63274],[20.15167,44.71527],[20.08815,44.79548],[20.19733,44.91157],[20.32676,44.94876],[20.3786,45.10163],[20.44349,45.01918],[20.61721,44.87752],[20.62923,44.75721],[20.78166,44.65546],[21.10782,44.73015],[21.36933,44.82696],[21.35643,44.86364],[21.44013,44.87613],[21.48579,44.8704],[21.5607,44.89027],[21.54972,44.93014],[21.35855,45.01941],[21.4505,45.04294],[21.50835,45.13407],[21.48278,45.19557],[21.20443,45.26262],[21.17612,45.32566],[21.09894,45.30144],[20.87948,45.42743],[20.86026,45.47295],[20.77217,45.49788],[20.83321,45.53567],[20.76798,45.60969],[20.80361,45.65875],[20.82364,45.77738],[20.78446,45.78522],[20.77416,45.75601],[20.70069,45.7493],[20.66262,45.83757],[20.35862,45.99356],[20.26068,46.12332],[20.09713,46.17315],[20.03533,46.14509],[20.01816,46.17696],[19.93508,46.17553],[19.81491,46.1313],[19.66007,46.19005],[19.56113,46.16824],[19.52473,46.1171],[19.28826,45.99694],[19.14543,45.9998],[19.10388,46.04015],[19.0791,45.96458],[19.01284,45.96529],[18.99712,45.93537],[18.81394,45.91329]]]}},{"type":"Feature","properties":{"id":"PK-AJK"},"geometry":{"type":"Polygon","coordinates":[[[73.38987,34.37567],[73.40111,34.35335],[73.44162,34.32196],[73.45725,34.27303],[73.48248,34.28842],[73.47141,34.25487],[73.48651,34.2233],[73.49759,34.09858],[73.50952,34.02207],[73.54419,33.94293],[73.59518,33.88837],[73.55689,33.78028],[73.59629,33.75281],[73.60599,33.71041],[73.5805,33.68306],[73.56745,33.62065],[73.59732,33.60182],[73.6123,33.60361],[73.60548,33.59163],[73.63637,33.53338],[73.60479,33.49101],[73.62779,33.46323],[73.5696,33.34429],[73.6568,33.16572],[73.62401,33.08765],[73.74538,33.07068],[73.76529,32.94602],[73.84546,32.92469],[74.04424,32.93622],[74.17007,32.87093],[74.31452,32.82327],[74.32697,32.78359],[74.35538,32.7737],[74.39211,32.803],[74.40876,32.76829],[74.46335,32.77695],[74.44687,32.79506],[74.41143,32.89904],[74.30783,32.92858],[74.33976,32.95682],[74.31854,33.02891],[74.17548,33.075],[74.15374,33.13477],[74.01309,33.21556],[74.00794,33.25462],[74.1275,33.30571],[74.17983,33.3679],[74.18354,33.47626],[74.1372,33.56092],[74.03295,33.57662],[73.98296,33.64338],[73.98968,33.66155],[73.96232,33.72298],[74.00891,33.75437],[74.05763,33.82443],[74.14001,33.83002],[74.2783,33.89535],[74.26637,33.98635],[74.21625,34.02605],[74.07875,34.03523],[73.94373,34.01617],[73.89636,34.05635],[73.90678,34.10686],[74.01077,34.21677],[73.90517,34.35317],[73.8475,34.32935],[73.74862,34.34183],[73.75731,34.38226],[73.88732,34.48911],[73.90125,34.51843],[73.95584,34.56269],[73.93401,34.63386],[73.9706,34.68516],[74.12149,34.67528],[74.31667,34.78673],[74.58137,34.76389],[74.6663,34.703],[74.89194,34.66628],[74.7726,34.84142],[74.82994,35.09126],[74.13505,35.12945],[73.61045,34.58064],[73.46574,34.57428],[73.38987,34.37567]]]}},{"type":"Feature","properties":{"id":"ES-MD"},"geometry":{"type":"Polygon","coordinates":[[[-4.59056,40.21532],[-4.52327,40.19841],[-4.41169,40.26302],[-4.36346,40.32744],[-4.35504,40.21965],[-4.2929,40.22017],[-4.24106,40.27873],[-4.20398,40.26904],[-4.19343,40.30446],[-4.11901,40.23367],[-4.07661,40.26655],[-3.95001,40.19061],[-3.93344,40.20706],[-3.91559,40.19106],[-3.87087,40.191],[-3.82933,40.16457],[-3.79586,40.18136],[-3.76221,40.12632],[-3.72453,40.14843],[-3.692,40.1319],[-3.66831,40.14502],[-3.66102,40.12875],[-3.59965,40.10499],[-3.6199,40.07124],[-3.73818,39.9785],[-3.89096,39.91961],[-3.80908,39.88286],[-3.75037,39.93994],[-3.6823,39.9408],[-3.59132,40.01302],[-3.5224,40.02695],[-3.51734,40.04259],[-3.45708,40.04903],[-3.38404,40.03517],[-3.3613,40.08844],[-3.2583,40.04456],[-3.17676,40.08857],[-3.13084,40.0604],[-3.0408,40.08884],[-3.10346,40.15827],[-3.06707,40.15591],[-3.10329,40.28293],[-3.20131,40.2393],[-3.12526,40.40317],[-3.20337,40.45387],[-3.19599,40.51301],[-3.28989,40.53011],[-3.33417,40.58814],[-3.32216,40.64326],[-3.35083,40.63662],[-3.38911,40.693],[-3.41142,40.66944],[-3.47185,40.68493],[-3.44215,40.76481],[-3.50309,40.78976],[-3.40335,40.99492],[-3.43528,41.08349],[-3.48884,41.09228],[-3.5513234,41.1727026],[-3.58085,41.16521],[-3.79234,40.99596],[-3.90701,40.98067],[-3.98048,40.78405],[-4.06717,40.79158],[-4.17274,40.68037],[-4.1633,40.62124],[-4.25445,40.59955],[-4.28689,40.63857],[-4.32655,40.41009],[-4.42543,40.40696],[-4.45444,40.31998],[-4.50284,40.31644],[-4.59056,40.21532]]]}},{"type":"Feature","properties":{"id":"ES-EX"},"geometry":{"type":"Polygon","coordinates":[[[-7.54121,39.66717],[-7.49477,39.58794],[-7.2927,39.45847],[-7.3223,39.38055],[-7.23986,39.26675],[-7.23566,39.20132],[-7.12811,39.17101],[-7.14929,39.11287],[-7.10692,39.10275],[-7.04011,39.11919],[-6.97004,39.07619],[-6.95211,39.0243],[-7.051,38.907],[-7.03848,38.87221],[-7.26174,38.72107],[-7.265,38.61674],[-7.32529,38.44336],[-7.15581,38.27597],[-7.09389,38.17227],[-6.93418,38.21454],[-6.7947,38.16681],[-6.8201,38.10781],[-6.60741,38.08782],[-6.58939,38.01942],[-6.46751,38.01401],[-6.43798,38.06647],[-6.36074,38.03849],[-6.2677,37.95989],[-5.94669,37.99508],[-5.84163,38.20257],[-5.67478,38.08323],[-5.5196,38.2193],[-5.58654,38.42078],[-5.05508,38.73855],[-4.84359,38.94712],[-4.97474,39.04425],[-4.84497,39.04985],[-4.82574,39.1974],[-4.64859,39.16387],[-4.7533,39.32101],[-4.66438,39.44494],[-4.92118,39.35872],[-5.21232,39.59642],[-5.14228,39.79719],[-5.30777,39.75444],[-5.29712,39.88892],[-5.40493,39.87918],[-5.3366,40.11457],[-5.37197,40.27192],[-5.57436,40.1807],[-5.66825,40.28646],[-5.79305,40.2845],[-5.79477,40.35321],[-5.87751,40.32744],[-5.91819,40.27756],[-6.10221,40.35857],[-6.07097,40.39558],[-6.21551,40.49317],[-6.56398,40.32848],[-6.54871,40.28581],[-6.6826,40.24297],[-6.84688,40.25385],[-6.86085,40.26776],[-7.00426,40.23169],[-7.02544,40.18564],[-7.00589,40.12087],[-6.94233,40.10716],[-6.86737,40.01986],[-6.90982,39.86183],[-6.97492,39.81488],[-7.01613,39.66877],[-7.24707,39.66576],[-7.33507,39.64569],[-7.54121,39.66717]]]}},{"type":"Feature","properties":{"id":"ES-MC"},"geometry":{"type":"Polygon","coordinates":[[[-2.36068,38.03484],[-2.17838,37.88474],[-1.97702,37.87647],[-2.02491,37.69387],[-1.89273,37.5085],[-1.82124,37.44099],[-1.73695,37.44249],[-1.35177,37.23072],[-0.45044,37.47486],[-0.36811,37.94676],[-0.76897,37.84437],[-0.78024,37.84337],[-0.8014,37.85676],[-0.83358,37.86645],[-0.9201,37.9427],[-0.93203,37.96619],[-0.99658,38.04978],[-1.00627,38.05478],[-1.02516,38.07755],[-1.03717,38.13388],[-0.98876,38.19448],[-0.97177,38.31943],[-1.08472,38.34663],[-1.09365,38.42602],[-1.02713,38.47811],[-1.00301,38.57272],[-1.0267,38.65562],[-1.19304,38.76425],[-1.34393,38.67505],[-1.37947,38.7131],[-1.5065,38.5303],[-1.47148,38.37261],[-1.66099,38.3026],[-1.76811,38.38257],[-1.93805,38.27861],[-2.08122,38.30691],[-2.36068,38.03484]]]}},{"type":"Feature","properties":{"id":"ES-CM"},"geometry":{"type":"Polygon","coordinates":[[[-5.40493,39.87918],[-5.29712,39.88892],[-5.30777,39.75444],[-5.14228,39.79719],[-5.21232,39.59642],[-4.92118,39.35872],[-4.66438,39.44494],[-4.7533,39.32101],[-4.64859,39.16387],[-4.82574,39.1974],[-4.84497,39.04985],[-4.97474,39.04425],[-4.84359,38.94712],[-5.05508,38.73855],[-4.99534,38.68973],[-4.87106,38.6847],[-4.86557,38.61619],[-4.66386,38.54762],[-4.45667,38.40558],[-4.27076,38.34084],[-4.27059,38.40073],[-4.0143,38.37046],[-3.85637,38.37275],[-3.82169,38.42185],[-3.59493,38.39629],[-3.58334,38.44989],[-3.54352,38.4556],[-3.53279,38.41297],[-3.47125,38.39461],[-3.37992,38.43241],[-3.3728,38.48678],[-3.13127,38.43732],[-3.06638,38.47885],[-3.00132,38.408],[-2.99549,38.44323],[-2.95154,38.4765],[-2.88588,38.45426],[-2.761,38.5303],[-2.69748,38.49739],[-2.58144,38.51096],[-2.57217,38.41028],[-2.48222,38.39925],[-2.48428,38.29478],[-2.43484,38.28643],[-2.44222,38.18557],[-2.55638,38.08539],[-2.36068,38.03484],[-2.08122,38.30691],[-1.93805,38.27861],[-1.76811,38.38257],[-1.66099,38.3026],[-1.47148,38.37261],[-1.5065,38.5303],[-1.37947,38.7131],[-1.34393,38.67505],[-1.19304,38.76425],[-1.0267,38.65562],[-0.96593,38.65347],[-0.91615,38.69559],[-0.94164,38.72101],[-0.95993,38.77549],[-0.93023,38.78111],[-0.95649,38.94552],[-1.1388,38.92603],[-1.27166,39.03838],[-1.16077,39.3088],[-1.42169,39.35952],[-1.52023,39.4346],[-1.50203,39.64561],[-1.28402,39.67522],[-1.20849,39.9487],[-1.14518,39.9713],[-1.16523,40.01013],[-1.2502,39.99369],[-1.37895,40.02091],[-1.4186,40.09422],[-1.45671,40.14259],[-1.43835,40.19238],[-1.53396,40.18779],[-1.78081,40.37401],[-1.69223,40.58058],[-1.5604,40.56858],[-1.54804,40.74361],[-1.61018,40.93504],[-1.91848,41.14014],[-2.11452,41.16469],[-2.05101,41.10031],[-2.41561,41.05851],[-2.56959,41.16198],[-2.6059,41.23141],[-2.86056,41.26993],[-2.87601,41.32642],[-3.04561,41.27367],[-3.1826,41.30643],[-3.40078,41.24386],[-3.39323,41.2121],[-3.5513234,41.1727026],[-3.58085,41.16521],[-3.5513234,41.1727026],[-3.48884,41.09228],[-3.43528,41.08349],[-3.40335,40.99492],[-3.50309,40.78976],[-3.44215,40.76481],[-3.47185,40.68493],[-3.41142,40.66944],[-3.38911,40.693],[-3.35083,40.63662],[-3.32216,40.64326],[-3.33417,40.58814],[-3.28989,40.53011],[-3.19599,40.51301],[-3.20337,40.45387],[-3.12526,40.40317],[-3.20131,40.2393],[-3.10329,40.28293],[-3.06707,40.15591],[-3.10346,40.15827],[-3.0408,40.08884],[-3.13084,40.0604],[-3.17676,40.08857],[-3.2583,40.04456],[-3.3613,40.08844],[-3.38404,40.03517],[-3.45708,40.04903],[-3.51734,40.04259],[-3.5224,40.02695],[-3.59132,40.01302],[-3.6823,39.9408],[-3.75037,39.93994],[-3.80908,39.88286],[-3.89096,39.91961],[-3.73818,39.9785],[-3.6199,40.07124],[-3.59965,40.10499],[-3.66102,40.12875],[-3.66831,40.14502],[-3.692,40.1319],[-3.72453,40.14843],[-3.76221,40.12632],[-3.79586,40.18136],[-3.82933,40.16457],[-3.87087,40.191],[-3.91559,40.19106],[-3.93344,40.20706],[-3.95001,40.19061],[-4.07661,40.26655],[-4.11901,40.23367],[-4.19343,40.30446],[-4.20398,40.26904],[-4.24106,40.27873],[-4.2929,40.22017],[-4.35504,40.21965],[-4.36346,40.32744],[-4.41169,40.26302],[-4.52327,40.19841],[-4.59056,40.21532],[-4.66678,40.18621],[-4.703,40.28358],[-4.78042,40.26826],[-4.95861,40.12481],[-5.3366,40.11457],[-5.40493,39.87918]]]}},{"type":"Feature","properties":{"id":"ES-AN"},"geometry":{"type":"Polygon","coordinates":[[[-7.51759,37.56119],[-7.46878,37.47127],[-7.43974,37.38913],[-7.43227,37.25152],[-7.41854,37.23813],[-7.41133,37.20314],[-7.39769,37.16868],[-7.37282,36.7712],[-6.21126,35.90929],[-5.64962,35.93752],[-5.10878,36.05227],[-2.19467,35.59878],[-1.35177,37.23072],[-1.73695,37.44249],[-1.82124,37.44099],[-1.89273,37.5085],[-2.02491,37.69387],[-1.97702,37.87647],[-2.17838,37.88474],[-2.36068,38.03484],[-2.55638,38.08539],[-2.44222,38.18557],[-2.43484,38.28643],[-2.48428,38.29478],[-2.48222,38.39925],[-2.57217,38.41028],[-2.58144,38.51096],[-2.69748,38.49739],[-2.761,38.5303],[-2.88588,38.45426],[-2.95154,38.4765],[-2.99549,38.44323],[-3.00132,38.408],[-3.06638,38.47885],[-3.13127,38.43732],[-3.3728,38.48678],[-3.37992,38.43241],[-3.47125,38.39461],[-3.53279,38.41297],[-3.54352,38.4556],[-3.58334,38.44989],[-3.59493,38.39629],[-3.82169,38.42185],[-3.85637,38.37275],[-4.0143,38.37046],[-4.27059,38.40073],[-4.27076,38.34084],[-4.45667,38.40558],[-4.66386,38.54762],[-4.86557,38.61619],[-4.87106,38.6847],[-4.99534,38.68973],[-5.05508,38.73855],[-5.58654,38.42078],[-5.5196,38.2193],[-5.67478,38.08323],[-5.84163,38.20257],[-5.94669,37.99508],[-6.2677,37.95989],[-6.36074,38.03849],[-6.43798,38.06647],[-6.46751,38.01401],[-6.58939,38.01942],[-6.60741,38.08782],[-6.8201,38.10781],[-6.7947,38.16681],[-6.93418,38.21454],[-7.00375,38.01914],[-7.05966,38.01966],[-7.10366,38.04404],[-7.12648,38.00296],[-7.24544,37.98884],[-7.27314,37.90145],[-7.33441,37.81193],[-7.41981,37.75729],[-7.51759,37.56119]]]}},{"type":"Feature","properties":{"id":"NI-AN"},"geometry":{"type":"Polygon","coordinates":[[[-85.5059,13.34552],[-85.29167,13.15487],[-85.07331,13.18045],[-85.08362,13.1303],[-84.96894,13.08416],[-84.62562,13.2747],[-83.8195,13.26701],[-83.57299,13.01124],[-83.13355,13.06878],[-82.28946,14.65367],[-83.04763,15.03256],[-83.13724,15.00002],[-83.21405,14.99354],[-83.5124,15.01211],[-83.62101,14.89448],[-83.69281,14.87129],[-83.8801,14.76907],[-84.10755,14.75927],[-84.35285,14.69453],[-84.57635,14.65484],[-84.70381,14.68473],[-84.80587,14.82965],[-84.90082,14.80489],[-85.00465,14.72209],[-85.02147,14.6065],[-84.79797,14.00536],[-85.40943,13.58625],[-85.5059,13.34552]]]}},{"type":"Feature","properties":{"id":"NI-AS"},"geometry":{"type":"Polygon","coordinates":[[[-85.19554,12.83456],[-85.12756,12.77932],[-85.07572,12.79137],[-84.90097,12.59879],[-84.72553,12.00406],[-84.51868,11.8739],[-84.6009,11.85861],[-84.53636,11.66131],[-84.56863,11.50727],[-84.19922,11.17436],[-83.68276,11.01562],[-82.78663,12.4173],[-83.13355,13.06878],[-83.57299,13.01124],[-83.8195,13.26701],[-84.62562,13.2747],[-84.96894,13.08416],[-85.08362,13.1303],[-85.11305,13.11308],[-85.08851,13.07069],[-85.18043,12.99251],[-85.19554,12.83456]]]}},{"type":"Feature","properties":{"id":"IQ-KR"},"geometry":{"type":"Polygon","coordinates":[[[42.35212,37.10858],[42.36697,37.0627],[42.5473,36.81478],[42.7169,36.77093],[42.83483,36.70076],[42.98418,36.75305],[43.13884,36.75332],[43.2875,36.93054],[43.4335,36.88717],[43.43127,36.85153],[43.65829,36.82082],[43.65365,36.85476],[43.70687,36.84418],[43.70172,36.96827],[43.61452,36.98226],[43.65434,37.04956],[43.70163,37.0351],[43.76901,37.02996],[43.82429,37.00796],[43.97638,36.94838],[43.97157,36.82453],[44.30683,36.7375],[44.23164,36.6596],[44.1307,36.59747],[44.10427,36.61387],[44.07491,36.60147],[44.05689,36.6147],[44.00127,36.59995],[43.96737,36.5728],[43.95501,36.54019],[43.90351,36.54232],[43.89681,36.50466],[43.84454,36.50894],[43.85879,36.45608],[43.8054,36.45318],[43.81484,36.40705],[43.78652,36.37679],[43.72781,36.31678],[43.6473,36.27998],[43.63117,36.24095],[43.57915,36.2048],[43.69194,36.20993],[43.80952,36.1545],[43.76987,36.10098],[43.83733,36.08531],[43.68593,36.03743],[43.82995,35.90851],[44.00608,35.81586],[44.09036,35.73104],[44.17224,35.82574],[44.19336,35.81349],[44.21585,35.84509],[44.30459,35.81628],[44.46372,35.86693],[44.57084,35.86943],[44.62474,35.90337],[44.63916,35.82866],[44.66611,35.86665],[44.68036,35.7688],[44.7159,35.74107],[44.68276,35.71989],[44.79005,35.70135],[44.72362,35.61013],[44.74542,35.54912],[44.7286,35.48583],[44.81821,35.4664],[44.71607,35.36133],[44.74439,35.31764],[44.69512,35.27743],[44.66131,35.16623],[44.56123,35.13942],[44.68551,35.11766],[44.79675,35.00665],[44.91691,34.99709],[44.92893,34.90536],[45.03055,34.91296],[45.05784,34.84311],[45.22573,34.73512],[45.2132,34.66399],[45.25955,34.61187],[45.2544,34.56587],[45.27173,34.54467],[45.28564,34.57252],[45.34366,34.61922],[45.37199,34.64839],[45.43232,34.64955],[45.47412,34.72313],[45.46897,34.75339],[45.51811,34.807],[45.54184,34.86642],[45.61265,34.92323],[45.64304,35.04124],[45.72149,35.106],[45.7644,35.04601],[45.78904,34.91135],[45.86532,34.89858],[45.89477,34.95805],[45.87864,35.03441],[45.92173,35.0465],[45.92203,35.09538],[45.94156,35.0895],[45.96825,35.07412],[46.00241,35.06618],[46.06508,35.03699],[46.07747,35.0838],[46.11763,35.07551],[46.19116,35.11097],[46.15642,35.1268],[46.15897,35.1658],[46.19738,35.18536],[46.18978,35.22549],[46.11476,35.23545],[46.15474,35.2883],[46.13152,35.32548],[46.05358,35.38568],[45.98453,35.49848],[46.01518,35.52012],[45.97263,35.58389],[46.03028,35.57416],[46.01307,35.59756],[46.0165,35.61501],[45.99452,35.63574],[46.0117,35.65059],[46.01631,35.69139],[46.23736,35.71414],[46.34166,35.78363],[46.32921,35.82655],[46.1654,35.79832],[46.08325,35.8581],[45.94711,35.82218],[45.89784,35.83708],[45.81442,35.82107],[45.76145,35.79898],[45.6645,35.92872],[45.60018,35.96069],[45.55245,35.99943],[45.46594,36.00042],[45.38275,35.97156],[45.33885,35.99647],[45.37652,36.06222],[45.37312,36.09917],[45.32813,36.15409],[45.30038,36.27769],[45.26261,36.3001],[45.27394,36.35846],[45.23953,36.43257],[45.11811,36.40751],[45.00759,36.5402],[45.06985,36.62645],[45.06985,36.6814],[45.01537,36.75128],[44.84725,36.77622],[44.83479,36.81362],[44.90173,36.86096],[44.91199,36.91468],[44.89862,37.01897],[44.81611,37.04383],[44.75229,37.11958],[44.78319,37.1431],[44.76698,37.16162],[44.63179,37.19229],[44.51514,37.1029],[44.4154,37.05216],[44.39429,37.04949],[44.38292,37.05914],[44.35315,37.04955],[44.35937,37.02843],[44.30897,36.96347],[44.25975,36.98119],[44.18503,37.09551],[44.22239,37.15756],[44.27998,37.16501],[44.2613,37.25055],[44.12984,37.31952],[44.01638,37.32904],[43.90949,37.22453],[43.84878,37.22205],[43.82699,37.19477],[43.8052,37.22825],[43.7009,37.23692],[43.63085,37.21957],[43.56702,37.25675],[43.50122,37.24262],[43.33508,37.33105],[43.30535,37.30355],[43.12683,37.37656],[42.94967,37.3157],[42.78887,37.38615],[42.56725,37.14878],[42.35212,37.10858]]]}},{"type":"Feature","properties":{"id":"AE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[51.41644,24.39615],[51.58871,24.27256],[51.59617,24.12041],[52.56622,22.94341],[55.13599,22.63334],[55.21095,22.70827],[55.22634,23.10378],[55.41761,23.38259],[55.59287,23.6549],[55.50258,23.95645],[55.73301,24.05994],[55.8308,24.01633],[56.01799,24.07426],[55.95457,24.22301],[55.83367,24.20193],[55.77658,24.23476],[55.76558,24.23227],[55.75257,24.23466],[55.75382,24.2466],[55.75939,24.26114],[55.76781,24.26209],[55.79145,24.27914],[55.80747,24.31069],[55.83395,24.32776],[55.83271,24.41521],[55.76461,24.5287],[55.83271,24.68567],[55.83408,24.77858],[55.81348,24.80102],[55.81312,24.91072],[55.85191,24.96582],[55.91182,24.96567],[55.96195,25.0062],[56.05715,24.95727],[56.05106,24.87461],[55.97963,24.89492],[55.97836,24.87673],[56.03535,24.81161],[56.06128,24.74457],[56.13684,24.73699],[56.16185,24.76436],[56.20062,24.78565],[56.20568,24.85063],[56.25952,24.86011],[56.29231,24.88425],[56.34873,24.93205],[56.3227,24.97284],[56.86325,25.03856],[56.82555,25.7713],[56.2645,25.62438],[56.25939,25.61745],[56.2615,25.61053],[56.26703,25.60805],[56.26636,25.60643],[56.25365,25.60211],[56.20473,25.61119],[56.18363,25.65508],[56.15564,25.6625],[56.16717,25.72668],[56.13963,25.82765],[56.17871,25.9047],[56.16545,25.94376],[56.17991,25.95217],[56.19189,25.97475],[56.15498,26.06828],[56.08666,26.05038],[55.81777,26.18798],[54.86679,25.39571],[53.97892,24.64436],[52.82259,25.51697],[52.35509,25.00368],[52.02277,24.75635],[51.83108,24.71675],[51.58834,24.66608],[51.41644,24.39615]],[[56.20838,25.25668],[56.24465,25.27505],[56.26003,25.29417],[56.24357,25.31827],[56.26062,25.33108],[56.3005,25.31815],[56.3111,25.30107],[56.35172,25.30681],[56.34438,25.26653],[56.27628,25.23404],[56.24341,25.22867],[56.20872,25.24104],[56.20838,25.25668]]],[[[56.27086,25.26128],[56.28423,25.26344],[56.29379,25.2754],[56.28102,25.28486],[56.2716,25.27916],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"AM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[43.44984,41.0988],[43.47319,41.02251],[43.58683,40.98961],[43.67712,40.93084],[43.67712,40.84846],[43.74872,40.7365],[43.7425,40.66805],[43.63664,40.54159],[43.54791,40.47413],[43.60862,40.43267],[43.59928,40.34019],[43.71136,40.16673],[43.65221,40.14889],[43.65688,40.11199],[43.92307,40.01787],[44.1057,40.03555],[44.1778,40.02845],[44.26973,40.04866],[44.46635,39.97733],[44.61845,39.8281],[44.75779,39.7148],[44.88354,39.74432],[44.92869,39.72157],[45.06604,39.79277],[45.18554,39.67846],[45.17464,39.58614],[45.21784,39.58074],[45.23535,39.61373],[45.30385,39.61373],[45.29606,39.57654],[45.46992,39.49888],[45.70547,39.60174],[45.80804,39.56716],[45.83,39.46487],[45.79225,39.3695],[45.99774,39.28931],[46.02303,39.09978],[46.06973,39.0744],[46.14785,38.84206],[46.20601,38.85262],[46.34059,38.92076],[46.53497,38.86548],[46.51805,38.94982],[46.54296,39.07078],[46.44022,39.19636],[46.52584,39.18912],[46.54141,39.15895],[46.58032,39.21204],[46.63481,39.23013],[46.56476,39.24942],[46.50093,39.33736],[46.43244,39.35181],[46.37795,39.42039],[46.4013,39.45405],[46.53051,39.47809],[46.51027,39.52373],[46.57721,39.54414],[46.57098,39.56694],[46.52117,39.58734],[46.42465,39.57534],[46.40286,39.63651],[46.18493,39.60533],[45.96543,39.78859],[45.82533,39.82925],[45.7833,39.9475],[45.60895,39.97733],[45.59806,40.0131],[45.78642,40.03218],[45.83779,39.98925],[45.97944,40.181],[45.95609,40.27846],[45.65098,40.37696],[45.42994,40.53804],[45.45484,40.57707],[45.35366,40.65979],[45.4206,40.7424],[45.55914,40.78366],[45.60584,40.87436],[45.40814,40.97904],[45.44083,41.01663],[45.39725,41.02603],[45.35677,40.99784],[45.28859,41.03757],[45.26162,41.0228],[45.25897,41.0027],[45.1994,41.04518],[45.16493,41.05068],[45.1634,41.08082],[45.1313,41.09369],[45.12923,41.06059],[45.06784,41.05379],[45.08028,41.10917],[45.19942,41.13299],[45.1969,41.168],[45.11811,41.19967],[45.05201,41.19211],[45.02932,41.2101],[45.05497,41.2464],[45.0133,41.29747],[44.93493,41.25685],[44.81437,41.30371],[44.80053,41.25949],[44.81749,41.23488],[44.84358,41.23088],[44.89911,41.21366],[44.87887,41.20195],[44.82084,41.21513],[44.72814,41.20338],[44.61462,41.24018],[44.59322,41.1933],[44.46791,41.18204],[44.34417,41.2382],[44.34337,41.20312],[44.32139,41.2079],[44.18148,41.24644],[44.16591,41.19141],[43.84835,41.16329],[43.74717,41.1117],[43.67712,41.13398],[43.4717,41.12611],[43.44984,41.0988]],[[44.95383,41.07553],[44.97169,41.09176],[45.00864,41.09407],[45.03406,41.07931],[45.04517,41.06653],[45.03792,41.03938],[45.00864,41.03411],[44.9903,41.05657],[44.96031,41.06345],[44.95383,41.07553]],[[45.18382,41.0066],[45.20625,41.01484],[45.23487,41.00226],[45.23095,40.97828],[45.21324,40.9817],[45.21219,40.99001],[45.20518,40.99348],[45.19312,40.98998],[45.18382,41.0066]]],[[[45.47927,40.65023],[45.50279,40.58424],[45.56071,40.64765],[45.51825,40.67382],[45.47927,40.65023]]]]}},{"type":"Feature","properties":{"id":"AZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[44.75779,39.7148],[44.80977,39.65768],[44.81043,39.62677],[44.88916,39.59653],[44.96746,39.42998],[45.05932,39.36435],[45.08751,39.35052],[45.16168,39.21952],[45.30489,39.18333],[45.40148,39.09007],[45.40452,39.07224],[45.44811,39.04927],[45.44966,38.99243],[45.6131,38.964],[45.6155,38.94304],[45.65172,38.95199],[45.83883,38.90768],[45.90266,38.87739],[45.94624,38.89072],[46.00228,38.87376],[46.06766,38.87861],[46.14785,38.84206],[46.06973,39.0744],[46.02303,39.09978],[45.99774,39.28931],[45.79225,39.3695],[45.83,39.46487],[45.80804,39.56716],[45.70547,39.60174],[45.46992,39.49888],[45.29606,39.57654],[45.30385,39.61373],[45.23535,39.61373],[45.21784,39.58074],[45.17464,39.58614],[45.18554,39.67846],[45.06604,39.79277],[44.92869,39.72157],[44.88354,39.74432],[44.75779,39.7148]]],[[[44.95383,41.07553],[44.96031,41.06345],[44.9903,41.05657],[45.00864,41.03411],[45.03792,41.03938],[45.04517,41.06653],[45.03406,41.07931],[45.00864,41.09407],[44.97169,41.09176],[44.95383,41.07553]]],[[[45.0133,41.29747],[45.05497,41.2464],[45.02932,41.2101],[45.05201,41.19211],[45.11811,41.19967],[45.1969,41.168],[45.19942,41.13299],[45.08028,41.10917],[45.06784,41.05379],[45.12923,41.06059],[45.1313,41.09369],[45.1634,41.08082],[45.16493,41.05068],[45.1994,41.04518],[45.25897,41.0027],[45.26162,41.0228],[45.28859,41.03757],[45.35677,40.99784],[45.39725,41.02603],[45.44083,41.01663],[45.40814,40.97904],[45.60584,40.87436],[45.55914,40.78366],[45.4206,40.7424],[45.35366,40.65979],[45.45484,40.57707],[45.42994,40.53804],[45.65098,40.37696],[45.95609,40.27846],[45.97944,40.181],[45.83779,39.98925],[45.78642,40.03218],[45.59806,40.0131],[45.60895,39.97733],[45.7833,39.9475],[45.82533,39.82925],[45.96543,39.78859],[46.18493,39.60533],[46.40286,39.63651],[46.42465,39.57534],[46.52117,39.58734],[46.57098,39.56694],[46.57721,39.54414],[46.51027,39.52373],[46.53051,39.47809],[46.4013,39.45405],[46.37795,39.42039],[46.43244,39.35181],[46.50093,39.33736],[46.56476,39.24942],[46.63481,39.23013],[46.58032,39.21204],[46.54141,39.15895],[46.52584,39.18912],[46.44022,39.19636],[46.54296,39.07078],[46.51805,38.94982],[46.53497,38.86548],[46.75752,39.03231],[46.83822,39.13143],[46.92539,39.16644],[46.95539,39.13432],[47.05736,39.19301],[47.05927,39.24846],[47.31301,39.37492],[47.38978,39.45999],[47.50099,39.49615],[47.84774,39.66285],[47.98977,39.70999],[48.34264,39.42935],[48.37385,39.37584],[48.15984,39.30028],[48.13076,39.2498],[48.15067,39.20046],[48.30636,39.10408],[48.33884,39.03022],[48.28456,38.96314],[48.08475,38.94111],[48.07734,38.91616],[48.01409,38.90333],[48.02209,38.83422],[48.24773,38.71883],[48.3146,38.59958],[48.45084,38.61013],[48.58793,38.45076],[48.62217,38.40198],[48.70001,38.40564],[48.78979,38.45026],[48.84969,38.45015],[48.88288,38.43975],[49.20805,38.40869],[51.7708,40.29239],[48.80971,41.95365],[48.5867,41.84306],[48.55078,41.77917],[48.42301,41.65444],[48.40277,41.60441],[48.2878,41.56221],[48.22064,41.51472],[48.07587,41.49957],[47.87973,41.21798],[47.75831,41.19455],[47.62288,41.22969],[47.54504,41.20275],[47.49004,41.26366],[47.34579,41.27884],[47.10762,41.59044],[47.03757,41.55434],[46.99554,41.59743],[47.00955,41.63583],[46.8134,41.76252],[46.75269,41.8623],[46.58924,41.80547],[46.5332,41.87389],[46.42738,41.91323],[46.3984,41.84399],[46.30863,41.79133],[46.23962,41.75811],[46.20538,41.77205],[46.17891,41.72094],[46.19759,41.62327],[46.24429,41.59883],[46.26531,41.63339],[46.28182,41.60089],[46.3253,41.60912],[46.34039,41.5947],[46.34126,41.57454],[46.29794,41.5724],[46.33925,41.4963],[46.40307,41.48464],[46.4669,41.43331],[46.63658,41.37727],[46.72375,41.28609],[46.66148,41.20533],[46.63969,41.09515],[46.55096,41.1104],[46.48558,41.0576],[46.456,41.09984],[46.37661,41.10805],[46.27698,41.19011],[46.13221,41.19479],[45.95786,41.17956],[45.80842,41.2229],[45.69946,41.29545],[45.75705,41.35157],[45.71035,41.36208],[45.68389,41.3539],[45.45973,41.45898],[45.4006,41.42402],[45.31352,41.47168],[45.26285,41.46433],[45.1797,41.42231],[45.09867,41.34065],[45.0133,41.29747]],[[45.47927,40.65023],[45.51825,40.67382],[45.56071,40.64765],[45.50279,40.58424],[45.47927,40.65023]]],[[[45.18382,41.0066],[45.19312,40.98998],[45.20518,40.99348],[45.21219,40.99001],[45.21324,40.9817],[45.23095,40.97828],[45.23487,41.00226],[45.20625,41.01484],[45.18382,41.0066]]]]}},{"type":"Feature","properties":{"id":"CY"},"geometry":{"type":"MultiPolygon","coordinates":[[[[31.71872,35.1606],[32.10341,34.37973],[32.74412,34.43926],[32.75515,34.64985],[32.75972,34.68156],[32.79264,34.6768],[32.82556,34.7057],[32.85817,34.70531],[32.86096,34.68781],[32.89019,34.66837],[32.90873,34.66286],[32.91398,34.67343],[32.93043,34.67091],[32.92829,34.66808],[32.93465,34.66385],[32.93808,34.67073],[32.93967,34.67045],[32.94143,34.67394],[32.94769,34.67909],[32.95559,34.68396],[32.98984,34.68019],[32.98795,34.67221],[32.99014,34.65518],[32.97928,34.65357],[32.97031,34.65421],[32.96494,34.65873],[32.96151,34.65598],[32.95271,34.65103],[32.95471,34.64528],[32.95323,34.64075],[32.95891,34.62919],[32.96718,34.63446],[32.96915,34.63938],[33.0138,34.64424],[33.26744,34.49942],[33.83531,34.73974],[33.70575,34.97947],[33.70639,34.99303],[33.71412,35.0037],[33.69661,35.01607],[33.70553,35.0295],[33.67807,35.04046],[33.67378,35.05936],[33.68399,35.06498],[33.69412,35.06618],[33.70536,35.07559],[33.71704,35.07018],[33.70343,35.04819],[33.70584,35.03618],[33.73781,35.05589],[33.76403,35.0392],[33.79094,35.04875],[33.79694,35.06],[33.81939,35.07271],[33.82733,35.0705],[33.83909,35.06235],[33.84844,35.06407],[33.86149,35.07194],[33.87342,35.08276],[33.86754,35.09445],[33.86724,35.10084],[33.86844,35.10439],[33.87217,35.12391],[33.88398,35.12324],[33.88569,35.11611],[33.8932,35.11453],[33.91633,35.0869],[33.91217,35.08132],[33.90247,35.07686],[33.89277,35.06794],[33.87471,35.08114],[33.86235,35.07039],[33.84939,35.06337],[33.83883,35.06133],[33.82716,35.06934],[33.81995,35.07159],[33.79806,35.0595],[33.79201,35.0483],[33.8205,35.03814],[33.82656,35.03154],[33.8272,35.00869],[33.83295,35.00694],[33.83651,35.00321],[33.85385,35.00254],[33.85475,34.99157],[33.83445,34.97508],[33.8405,34.97114],[33.8602,34.97375],[33.90131,34.96478],[33.98684,34.76642],[35.48515,34.70851],[35.51152,36.10954],[32.82353,35.70297],[31.71872,35.1606]]],[[[33.7343,35.01178],[33.74144,35.01053],[33.7492,35.01319],[33.74983,35.02274],[33.74265,35.02329],[33.73781,35.02181],[33.7343,35.01178]]],[[[33.75682,34.99916],[33.76605,34.99543],[33.76738,34.99188],[33.7778,34.98981],[33.77843,34.988],[33.78149,34.98854],[33.78318,34.98699],[33.78571,34.98951],[33.78917,34.98854],[33.79191,34.98914],[33.78516,34.99582],[33.77553,34.99518],[33.77312,34.9976],[33.75994,35.00113],[33.75682,34.99916]]]]}},{"type":"Feature","properties":{"id":"DK"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-74.12379,76.56358],[-58.9307,74.79891],[-53.68108,62.9266],[-44.85369,58.6179],[-20.82201,70.31885],[-8.70622,81.30703],[-30.36949,84.23527],[-59.93819,82.31398],[-66.45595,80.82603],[-67.1326,80.75493],[-73.91222,78.42484],[-74.12379,76.56358]]],[[[-8.51774,62.35338],[-6.51083,60.95272],[-5.70102,62.77194],[-8.51774,62.35338]]],[[[7.28637,57.35913],[8.02459,55.09613],[8.45719,55.06747],[8.55769,54.91837],[8.63979,54.91069],[8.76387,54.8948],[8.81178,54.90518],[8.92795,54.90452],[9.04629,54.87249],[9.14275,54.87421],[9.20571,54.85841],[9.24631,54.84726],[9.23445,54.83432],[9.2474,54.8112],[9.32771,54.80602],[9.33849,54.80233],[9.36496,54.81749],[9.38532,54.83968],[9.41213,54.84254],[9.43155,54.82586],[9.4659,54.83131],[9.58937,54.88785],[9.62734,54.88057],[9.61187,54.85548],[9.73563,54.8247],[9.89314,54.84171],[10.16755,54.73883],[10.31111,54.65968],[11.00303,54.63689],[11.90309,54.38543],[12.85844,54.82438],[13.93395,54.84044],[15.36991,54.73263],[15.79951,55.54655],[14.89259,55.5623],[14.28399,55.1553],[12.84405,55.13257],[12.60345,55.42675],[12.88472,55.63369],[12.6372,55.91371],[12.65312,56.04345],[12.07466,56.29488],[12.16597,56.60205],[10.40861,58.38489],[7.28637,57.35913]]]]}},{"type":"Feature","properties":{"id":"ES"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-18.58553,28.95791],[-18.29528,27.07966],[-13.84006,27.86813],[-12.87179,29.54492],[-13.43249,29.9224],[-18.58553,28.95791]]],[[[-9.57169,43.18446],[-9.14112,41.86623],[-8.87157,41.86488],[-8.81794,41.90375],[-8.75712,41.92833],[-8.74606,41.9469],[-8.7478,41.96282],[-8.6681,41.99703],[-8.65832,42.02972],[-8.64626,42.03668],[-8.63791,42.04691],[-8.59493,42.05708],[-8.58086,42.05147],[-8.54563,42.0537],[-8.5252,42.06264],[-8.52837,42.07658],[-8.4444,42.08377],[-8.42512,42.07199],[-8.40143,42.08052],[-8.38323,42.07683],[-8.36353,42.09065],[-8.33912,42.08358],[-8.32703,42.08726],[-8.32161,42.10218],[-8.29809,42.106],[-8.27296,42.12432],[-8.25957,42.12108],[-8.25007,42.14018],[-8.2262,42.13069],[-8.19748,42.15436],[-8.18771,42.13722],[-8.19406,42.12141],[-8.18837,42.10812],[-8.18534,42.07171],[-8.17623,42.06477],[-8.11282,42.08339],[-8.08847,42.05767],[-8.08796,42.01398],[-8.15476,41.98509],[-8.21961,41.91064],[-8.19551,41.87459],[-8.16944,41.87944],[-8.16455,41.81753],[-8.0961,41.81024],[-8.01136,41.83453],[-7.9804,41.87337],[-7.92336,41.8758],[-7.90672,41.92649],[-7.88493,41.92597],[-7.88638,41.85568],[-7.84939,41.86201],[-7.84188,41.88065],[-7.69848,41.90977],[-7.65774,41.88308],[-7.58603,41.87944],[-7.61904,41.82961],[-7.51981,41.84073],[-7.49803,41.87095],[-7.44922,41.86436],[-7.44759,41.84451],[-7.42699,41.83293],[-7.42864,41.80589],[-7.37092,41.85031],[-7.32366,41.8406],[-7.18677,41.88793],[-7.18549,41.97515],[-7.14115,41.98855],[-7.08574,41.97401],[-7.07596,41.94977],[-7.01078,41.94977],[-6.98144,41.9728],[-6.95537,41.96553],[-6.94396,41.94403],[-6.82174,41.94493],[-6.81196,41.99097],[-6.76959,41.98734],[-6.75004,41.94129],[-6.61831,41.94036],[-6.58544,41.96674],[-6.5447,41.94371],[-6.57078,41.88358],[-6.51374,41.8758],[-6.56426,41.74219],[-6.54838,41.68475],[-6.49907,41.65823],[-6.44204,41.68258],[-6.29863,41.66432],[-6.19128,41.57638],[-6.28675,41.46459],[-6.3306,41.37677],[-6.38553,41.38655],[-6.38551,41.35274],[-6.55937,41.24417],[-6.65046,41.24725],[-6.68286,41.21641],[-6.69711,41.1858],[-6.77319,41.13049],[-6.75655,41.10187],[-6.8079,41.04523],[-6.80942,41.03629],[-6.84781,41.02692],[-6.88843,41.03027],[-6.913,41.03922],[-6.9357,41.02888],[-6.8527,40.93958],[-6.84292,40.89771],[-6.80707,40.88047],[-6.79892,40.84842],[-6.82337,40.84472],[-6.82826,40.74603],[-6.79567,40.65955],[-6.84703,40.56853],[-6.80218,40.55067],[-6.7973,40.51723],[-6.84901,40.45434],[-6.83313,40.40595],[-6.77953,40.36409],[-6.80415,40.3297],[-6.86565,40.2957],[-6.86085,40.26776],[-7.00426,40.23169],[-7.02544,40.18564],[-7.00589,40.12087],[-6.94233,40.10716],[-6.86737,40.01986],[-6.90982,39.86183],[-6.97492,39.81488],[-7.01613,39.66877],[-7.24707,39.66576],[-7.33507,39.64569],[-7.54121,39.66717],[-7.49477,39.58794],[-7.2927,39.45847],[-7.3223,39.38055],[-7.23986,39.26675],[-7.23566,39.20132],[-7.12811,39.17101],[-7.14929,39.11287],[-7.10692,39.10275],[-7.04011,39.11919],[-6.97004,39.07619],[-6.95211,39.0243],[-7.051,38.907],[-7.03848,38.87221],[-7.26174,38.72107],[-7.265,38.61674],[-7.32529,38.44336],[-7.15581,38.27597],[-7.09389,38.17227],[-6.93418,38.21454],[-7.00375,38.01914],[-7.05966,38.01966],[-7.10366,38.04404],[-7.12648,38.00296],[-7.24544,37.98884],[-7.27314,37.90145],[-7.33441,37.81193],[-7.41981,37.75729],[-7.51759,37.56119],[-7.46878,37.47127],[-7.43974,37.38913],[-7.43227,37.25152],[-7.41854,37.23813],[-7.41133,37.20314],[-7.39769,37.16868],[-7.37282,36.7712],[-6.21126,35.90929],[-5.64962,35.93752],[-5.10878,36.05227],[-2.19467,35.59878],[-1.35177,37.23072],[-0.45044,37.47486],[-0.36811,37.94676],[0.74156,38.66836],[1.8512,38.28539],[4.88397,39.94305],[2.8815,40.70665],[3.56345,42.43174],[3.11379,42.43646],[3.09059,42.42304],[3.02935,42.47312],[2.96518,42.46692],[2.94283,42.48174],[2.92107,42.4573],[2.88413,42.45938],[2.8736,42.46751],[2.86329,42.4638],[2.86417,42.45968],[2.86123,42.45367],[2.84335,42.45724],[2.77464,42.41046],[2.75497,42.42578],[2.72056,42.42298],[2.65311,42.38771],[2.67933,42.33637],[2.57934,42.35808],[2.55516,42.35351],[2.54382,42.33406],[2.48457,42.33933],[2.43508,42.37568],[2.43401,42.38941],[2.25551,42.43757],[2.20578,42.41633],[2.16599,42.42314],[2.12789,42.41291],[2.11621,42.38393],[2.06241,42.35906],[2.00488,42.35399],[1.96482,42.37787],[1.9574,42.42401],[1.94084,42.43039],[1.94061,42.43333],[1.94292,42.44316],[1.93663,42.45439],[1.88853,42.4501],[1.83037,42.48395],[1.72334,42.4973],[1.70571,42.48867],[1.66826,42.50779],[1.65674,42.47125],[1.58933,42.46275],[1.57953,42.44957],[1.55937,42.45808],[1.55073,42.43299],[1.5127,42.42959],[1.44529,42.43724],[1.43838,42.47848],[1.41648,42.48315],[1.46661,42.50949],[1.44759,42.54431],[1.41245,42.53539],[1.4234,42.55959],[1.44529,42.56722],[1.42512,42.58292],[1.44197,42.60217],[1.35562,42.71944],[1.15928,42.71407],[1.0804,42.78569],[0.98292,42.78754],[0.96166,42.80629],[0.93089,42.79154],[0.711,42.86372],[0.66121,42.84021],[0.65421,42.75872],[0.67873,42.69458],[0.40214,42.69779],[0.36251,42.72282],[0.29407,42.67431],[0.25336,42.7174],[0.17569,42.73424],[-0.02468,42.68513],[-0.10519,42.72761],[-0.16141,42.79535],[-0.17939,42.78974],[-0.3122,42.84788],[-0.38833,42.80132],[-0.41319,42.80776],[-0.44334,42.79939],[-0.50863,42.82713],[-0.52725,42.79565],[-0.55497,42.77846],[-0.67637,42.88303],[-0.69837,42.87945],[-0.72608,42.89318],[-0.73422,42.91228],[-0.72251,42.92025],[-0.75478,42.96916],[-0.81652,42.95166],[-0.97133,42.96239],[-1.00963,42.99279],[-1.08556,43.00232],[-1.18197,43.03338],[-1.22881,43.05534],[-1.25016,43.04079],[-1.30745,43.06918],[-1.29897,43.09281],[-1.28252,43.10891],[-1.26759,43.11907],[-1.32209,43.1127],[-1.34419,43.09665],[-1.35272,43.02658],[-1.44067,43.047],[-1.47555,43.08372],[-1.41562,43.12815],[-1.3758,43.24511],[-1.40942,43.27272],[-1.44301,43.26749],[-1.50585,43.2936],[-1.53463,43.29459],[-1.5646,43.28887],[-1.55808,43.27911],[-1.57021,43.25253],[-1.61341,43.25269],[-1.63052,43.28591],[-1.62124,43.30682],[-1.66839,43.31504],[-1.69407,43.31378],[-1.73074,43.29481],[-1.73744,43.33007],[-1.74967,43.3316],[-1.75334,43.34107],[-1.75854,43.34434],[-1.77218,43.34211],[-1.79005,43.35216],[-1.7816,43.36155],[-1.79224,43.3745],[-1.77289,43.38957],[-1.81005,43.59738],[-3.10233,43.59549],[-4.52988,43.62298],[-6.97771,43.78895],[-7.91565,44.00862],[-9.57169,43.18446]],[[-5.40134,36.14896],[-5.38545,36.15481],[-5.36494,36.15496],[-5.34536,36.15501],[-5.33822,36.15272],[-5.27801,36.14942],[-5.28217,36.09907],[-5.3004,36.07439],[-5.32837,36.05935],[-5.36503,36.06205],[-5.39074,36.10278],[-5.40134,36.14896]]],[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]],[[[-4.30191,35.17419],[-4.30112,35.17058],[-4.29436,35.17149],[-4.30191,35.17419]]],[[[-3.90602,35.21494],[-3.88926,35.20841],[-3.88617,35.21406],[-3.90288,35.22024],[-3.90602,35.21494]]],[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]],[[[-2.44896,35.18777],[-2.44887,35.17075],[-2.41312,35.17111],[-2.41265,35.1877],[-2.44896,35.18777]]],[[[1.95606,42.45785],[1.96125,42.45364],[1.98378,42.44697],[1.99838,42.44682],[2.01564,42.45171],[1.99216,42.46208],[1.98579,42.47486],[1.99766,42.4858],[1.98916,42.49351],[1.98022,42.49569],[1.97697,42.48568],[1.97227,42.48487],[1.97003,42.48081],[1.96215,42.47854],[1.95606,42.45785]]]]}},{"type":"Feature","properties":{"id":"FJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-180,-22.90585],[-176.74538,-22.89767],[-176.76826,-14.95183],[-178.60161,-14.95666],[-180,-14.96041],[-180,-22.90585]]],[[[174,-22.5],[180,-22.5],[180,-11.5],[174,-11.5],[174,-22.5]]]]}},{"type":"Feature","properties":{"id":"FR"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-178.60852,-12.49232],[-178.60161,-14.95666],[-176.76826,-14.95183],[-174.17905,-14.94502],[-174.18596,-12.48057],[-178.60852,-12.49232]]],[[[-156.4957,-12.32002],[-156.46451,-23.21255],[-156.44843,-28.52556],[-133.59543,-28.4709],[-133.61511,-21.93325],[-133.65593,-7.46952],[-149.6249,-7.51261],[-149.61166,-12.30171],[-156.4957,-12.32002]]],[[[-109.6462,9.84394],[-108.755,9.84085],[-108.75183,10.72712],[-109.64303,10.7302],[-109.6462,9.84394]]],[[[-63.35989,18.06012],[-63.33064,17.9615],[-63.13584,18.0541],[-63.11096,18.05368],[-63.09686,18.04608],[-63.07759,18.04943],[-63.0591,18.06744],[-63.03998,18.05596],[-63.03669,18.05786],[-63.02886,18.05482],[-63.02323,18.05757],[-62.93924,18.02904],[-63.07669,17.79659],[-62.76692,17.64353],[-62.54836,17.8636],[-62.75637,18.13489],[-62.86666,18.19278],[-63.35989,18.06012]]],[[[-62.17275,16.35721],[-61.78844,15.65993],[-61.07397,15.7179],[-60.71337,16.48911],[-61.44461,16.81958],[-61.83929,16.66647],[-62.17275,16.35721]]],[[[-61.51867,14.96709],[-61.26561,14.25664],[-60.5958,14.23076],[-60.78194,15.1733],[-61.51867,14.96709]]],[[[-56.70773,46.51478],[-55.8643,46.64935],[-56.25228,47.31192],[-56.67989,47.3339],[-56.70773,46.51478]]],[[[-54.6084,2.32856],[-54.16286,2.10779],[-53.78743,2.34412],[-52.96539,2.1881],[-52.6906,2.37298],[-52.31787,3.17896],[-51.85573,3.83427],[-51.82312,3.85825],[-51.79599,3.89336],[-51.76551,3.98036],[-51.65531,4.05811],[-51.61983,4.14596],[-51.55535,4.70281],[-53.83024,6.10624],[-54.01074,5.68785],[-54.00724,5.55072],[-54.14457,5.36582],[-54.29117,5.24771],[-54.35743,5.1477],[-54.44051,4.94713],[-54.47879,4.90454],[-54.46918,4.88795],[-54.46223,4.78027],[-54.46394,4.72938],[-54.42,4.71911],[-54.43511,4.63494],[-54.41554,4.61483],[-54.42051,4.56581],[-54.44794,4.52564],[-54.39056,4.28273],[-54.39022,4.18207],[-54.32584,4.14937],[-54.35709,4.05006],[-54.19367,3.84387],[-54.20302,3.80858],[-54.13444,3.80139],[-54.09101,3.72919],[-54.08286,3.6742],[-54.05128,3.63557],[-54.02981,3.63078],[-54.02441,3.64559],[-54.01033,3.65193],[-53.97892,3.60482],[-54.00904,3.46724],[-54.01617,3.4178],[-54.059,3.38422],[-54.0529,3.364],[-54.06681,3.32347],[-54.06852,3.30299],[-54.1286,3.28688],[-54.28534,2.67798],[-54.42864,2.42442],[-54.6084,2.32856]]],[[[-5.81385,48.52441],[-1.81005,43.59738],[-1.77289,43.38957],[-1.79224,43.3745],[-1.7816,43.36155],[-1.79005,43.35216],[-1.77218,43.34211],[-1.75854,43.34434],[-1.75334,43.34107],[-1.74967,43.3316],[-1.73744,43.33007],[-1.73074,43.29481],[-1.69407,43.31378],[-1.66839,43.31504],[-1.62124,43.30682],[-1.63052,43.28591],[-1.61341,43.25269],[-1.57021,43.25253],[-1.55808,43.27911],[-1.5646,43.28887],[-1.53463,43.29459],[-1.50585,43.2936],[-1.44301,43.26749],[-1.40942,43.27272],[-1.3758,43.24511],[-1.41562,43.12815],[-1.47555,43.08372],[-1.44067,43.047],[-1.35272,43.02658],[-1.34419,43.09665],[-1.32209,43.1127],[-1.26759,43.11907],[-1.28252,43.10891],[-1.29897,43.09281],[-1.30745,43.06918],[-1.25016,43.04079],[-1.22881,43.05534],[-1.18197,43.03338],[-1.08556,43.00232],[-1.00963,42.99279],[-0.97133,42.96239],[-0.81652,42.95166],[-0.75478,42.96916],[-0.72251,42.92025],[-0.73422,42.91228],[-0.72608,42.89318],[-0.69837,42.87945],[-0.67637,42.88303],[-0.55497,42.77846],[-0.52725,42.79565],[-0.50863,42.82713],[-0.44334,42.79939],[-0.41319,42.80776],[-0.38833,42.80132],[-0.3122,42.84788],[-0.17939,42.78974],[-0.16141,42.79535],[-0.10519,42.72761],[-0.02468,42.68513],[0.17569,42.73424],[0.25336,42.7174],[0.29407,42.67431],[0.36251,42.72282],[0.40214,42.69779],[0.67873,42.69458],[0.65421,42.75872],[0.66121,42.84021],[0.711,42.86372],[0.93089,42.79154],[0.96166,42.80629],[0.98292,42.78754],[1.0804,42.78569],[1.15928,42.71407],[1.35562,42.71944],[1.44197,42.60217],[1.47986,42.61346],[1.46718,42.63296],[1.48043,42.65203],[1.50867,42.64483],[1.55418,42.65669],[1.60085,42.62703],[1.63485,42.62957],[1.6625,42.61982],[1.68267,42.62533],[1.73452,42.61515],[1.72588,42.59098],[1.7858,42.57698],[1.73927,42.55523],[1.72334,42.4973],[1.83037,42.48395],[1.88853,42.4501],[1.93663,42.45439],[1.94292,42.44316],[1.94061,42.43333],[1.94084,42.43039],[1.9574,42.42401],[1.96482,42.37787],[2.00488,42.35399],[2.06241,42.35906],[2.11621,42.38393],[2.12789,42.41291],[2.16599,42.42314],[2.20578,42.41633],[2.25551,42.43757],[2.43401,42.38941],[2.43508,42.37568],[2.48457,42.33933],[2.54382,42.33406],[2.55516,42.35351],[2.57934,42.35808],[2.67933,42.33637],[2.65311,42.38771],[2.72056,42.42298],[2.75497,42.42578],[2.77464,42.41046],[2.84335,42.45724],[2.86123,42.45367],[2.86417,42.45968],[2.86329,42.4638],[2.8736,42.46751],[2.88413,42.45938],[2.92107,42.4573],[2.94283,42.48174],[2.96518,42.46692],[3.02935,42.47312],[3.09059,42.42304],[3.11379,42.43646],[3.56345,42.43174],[8.06341,41.16624],[9.72681,41.374],[9.60234,43.19214],[7.50102,43.51859],[7.41855,43.72479],[7.40903,43.7296],[7.41113,43.73156],[7.41291,43.73168],[7.41298,43.73311],[7.41233,43.73439],[7.42062,43.73977],[7.42299,43.74176],[7.42443,43.74087],[7.42809,43.74396],[7.43013,43.74895],[7.43624,43.75014],[7.43708,43.75197],[7.4389,43.75151],[7.4379,43.74963],[7.45448,43.7432],[7.53358,43.53609],[7.63035,43.57419],[7.5289,43.78887],[7.50423,43.84345],[7.49355,43.86551],[7.51162,43.88301],[7.56075,43.89932],[7.56858,43.94506],[7.65738,43.9766],[7.66848,43.99943],[7.66133,44.02644],[7.71375,44.06245],[7.71901,44.08555],[7.66878,44.12795],[7.68694,44.17487],[7.63245,44.17877],[7.62155,44.14881],[7.56597,44.15191],[7.36072,44.11302],[7.34547,44.14359],[7.27827,44.1462],[7.16929,44.20352],[7.00764,44.23736],[6.98221,44.28289],[6.89171,44.36637],[6.88784,44.42043],[6.94504,44.43112],[6.86233,44.49834],[6.85507,44.53072],[6.91675,44.56833],[6.96042,44.62129],[6.95133,44.66264],[6.99105,44.69205],[7.07484,44.68073],[7.00401,44.78782],[7.02217,44.82519],[6.93499,44.8664],[6.90774,44.84322],[6.75655,44.90352],[6.744,44.93629],[6.74791,45.01939],[6.66981,45.02324],[6.62803,45.11175],[6.7697,45.16044],[6.85144,45.13226],[6.96706,45.20841],[7.07074,45.21228],[7.13115,45.25386],[7.10572,45.32924],[7.18019,45.40071],[7.09297,45.46959],[7.00037,45.509],[6.98948,45.63869],[6.80785,45.71864],[6.80785,45.83265],[6.95315,45.85163],[7.04151,45.92435],[7.00946,45.9944],[6.93308,46.06713],[6.87868,46.03855],[6.89321,46.12548],[6.78487,46.14022],[6.80706,46.17471],[6.80929,46.2109],[6.85478,46.25887],[6.86249,46.28559],[6.77813,46.34242],[6.76903,46.35951],[6.7927,46.36761],[6.80611,46.38142],[6.80183,46.38836],[6.80309,46.3914],[6.80335,46.39285],[6.80501,46.39362],[6.82312,46.42661],[6.53358,46.45431],[6.25432,46.3632],[6.21981,46.31304],[6.24843,46.30199],[6.25137,46.29014],[6.23779,46.28163],[6.24984,46.26227],[6.26733,46.24752],[6.29474,46.26221],[6.30937,46.25003],[6.30868,46.24725],[6.31025,46.24381],[6.29477,46.22521],[6.27694,46.21566],[6.26795,46.21362],[6.25585,46.20999],[6.24821,46.20531],[6.23913,46.20511],[6.23544,46.20714],[6.22175,46.20045],[6.22222,46.19888],[6.21844,46.19837],[6.21603,46.19507],[6.21273,46.19409],[6.21114,46.1927],[6.20633,46.19229],[6.19807,46.18369],[6.19552,46.18401],[6.18707,46.17999],[6.18871,46.16644],[6.1812,46.16179],[6.14865,46.15001],[6.14498,46.14493],[6.14249,46.14668],[6.1367,46.14282],[6.13528,46.14121],[6.10035,46.14405],[6.09199,46.15191],[6.07533,46.14909],[6.05284,46.15245],[6.04587,46.14011],[6.036,46.13553],[6.01763,46.14274],[5.98433,46.14318],[5.97819,46.13303],[5.95574,46.12869],[5.9641,46.14412],[5.97508,46.15863],[5.98188,46.17392],[5.98846,46.17046],[5.99573,46.18587],[5.96515,46.19638],[5.97542,46.21525],[6.02461,46.23313],[6.03342,46.2383],[6.04602,46.23127],[6.05029,46.23518],[6.0633,46.24583],[6.07072,46.24085],[6.08767,46.24706],[6.10144,46.23759],[6.12457,46.25098],[6.12086,46.25501],[6.12247,46.25811],[6.11926,46.2634],[6.1013,46.28512],[6.11697,46.29547],[6.1198,46.31157],[6.13876,46.33844],[6.15738,46.3491],[6.16987,46.36759],[6.15985,46.37721],[6.15016,46.3778],[6.09926,46.40768],[6.06407,46.41676],[6.08668,46.4427],[6.07385,46.45964],[6.07295,46.46544],[6.15684,46.54522],[6.11084,46.57649],[6.27135,46.68251],[6.38494,46.73197],[6.45209,46.77502],[6.43216,46.80336],[6.46639,46.89152],[6.4324,46.92846],[6.50472,46.96572],[6.57295,46.98285],[6.62037,46.99281],[6.6469,47.00987],[6.662,47.02918],[6.69736,47.03801],[6.71891,47.05138],[6.69024,47.06638],[6.702,47.07108],[6.70831,47.08403],[6.72612,47.09236],[6.76788,47.1208],[6.85855,47.16445],[6.84568,47.16713],[6.87314,47.18597],[6.92293,47.22236],[6.92737,47.22839],[6.95275,47.24349],[6.95074,47.25913],[6.95159,47.26991],[6.94035,47.28636],[7.00871,47.30202],[7.00996,47.32427],[7.05172,47.32663],[7.05897,47.34292],[7.03125,47.36996],[6.87959,47.35335],[6.88542,47.37262],[6.93744,47.40714],[6.94078,47.43354],[6.97859,47.44884],[7.0024,47.45264],[6.98425,47.49432],[7.0231,47.50522],[7.07425,47.48863],[7.12781,47.50371],[7.16249,47.49025],[7.19583,47.49455],[7.17026,47.44312],[7.24669,47.4205],[7.33526,47.44186],[7.35603,47.43432],[7.40308,47.43638],[7.43088,47.45846],[7.4462,47.46264],[7.4583,47.47216],[7.42444,47.48519],[7.43356,47.49712],[7.47534,47.47932],[7.4913,47.48446],[7.5107,47.49873],[7.50906,47.50943],[7.49804,47.51798],[7.5229,47.51644],[7.53199,47.5284],[7.51904,47.53515],[7.50683,47.5278],[7.49731,47.53664],[7.50873,47.54546],[7.51723,47.54578],[7.52831,47.55347],[7.53634,47.55553],[7.55652,47.56779],[7.55689,47.57232],[7.56548,47.57617],[7.56684,47.57785],[7.58386,47.57536],[7.58945,47.59017],[7.59378,47.60295],[7.58851,47.60794],[7.57423,47.61628],[7.56807,47.63135],[7.5591,47.63849],[7.53384,47.65115],[7.52067,47.66437],[7.51915,47.68335],[7.51266,47.70197],[7.53722,47.71635],[7.54761,47.72912],[7.52921,47.77747],[7.55001,47.82289],[7.56365,47.8464],[7.5543,47.87784],[7.58151,47.89747],[7.58236,47.93003],[7.61704,47.96285],[7.61755,47.99468],[7.56966,48.03265],[7.57747,48.12233],[7.59987,48.14558],[7.60004,48.15749],[7.6648,48.22219],[7.69022,48.30018],[7.74561,48.32652],[7.72974,48.38538],[7.76493,48.45835],[7.76833,48.48945],[7.80647,48.5104],[7.80544,48.55229],[7.80003,48.58393],[7.84724,48.6468],[7.89002,48.66317],[7.96812,48.72491],[7.97023,48.7589],[8.01512,48.7602],[8.0326,48.79017],[8.06802,48.78957],[8.10379,48.81641],[8.12813,48.87985],[8.19989,48.95825],[8.20031,48.95856],[8.23348,48.96658],[8.22266,48.97627],[8.14189,48.97833],[8.06679,48.99908],[8.04834,49.01366],[7.97358,49.03269],[7.93641,49.05544],[7.86386,49.03499],[7.79557,49.06583],[7.75948,49.04562],[7.63618,49.05428],[7.62575,49.07654],[7.56416,49.08136],[7.53012,49.09818],[7.49172,49.13915],[7.49473,49.17],[7.44455,49.16765],[7.44052,49.18354],[7.3662,49.17308],[7.35995,49.14399],[7.3195,49.14231],[7.29514,49.11426],[7.23473,49.12971],[7.1593,49.1204],[7.1358,49.1282],[7.12504,49.14253],[7.10384,49.13787],[7.11205,49.1524],[7.10055,49.15602],[7.07859,49.15031],[7.09007,49.13094],[7.07162,49.1255],[7.06642,49.11415],[7.05548,49.11185],[7.04843,49.11422],[7.04409,49.12123],[7.04662,49.13724],[7.03178,49.15734],[7.0274,49.17042],[7.03459,49.19096],[7.02313,49.18902],[6.97273,49.2099],[6.95963,49.203],[6.94028,49.21641],[6.93831,49.2223],[6.92044,49.22353],[6.89289,49.20887],[6.85939,49.22376],[6.83559,49.21224],[6.85115,49.20106],[6.85016,49.19354],[6.86263,49.18289],[6.85954,49.17471],[6.84443,49.17314],[6.84688,49.15701],[6.83392,49.15128],[6.78265,49.16793],[6.73765,49.16375],[6.71137,49.18808],[6.73273,49.20593],[6.72157,49.22152],[6.69419,49.21529],[6.66583,49.28065],[6.6087,49.30738],[6.56596,49.35716],[6.59068,49.3528],[6.60166,49.36644],[6.533,49.40748],[6.55404,49.42464],[6.42957,49.47816],[6.40274,49.46546],[6.37965,49.4651],[6.36778,49.46937],[6.3687,49.4593],[6.28612,49.48534],[6.28078,49.50093],[6.2745,49.50433],[6.25346,49.50399],[6.2409,49.51408],[6.19543,49.50536],[6.17386,49.50934],[6.15366,49.50226],[6.16115,49.49297],[6.14321,49.48796],[6.12814,49.49365],[6.12346,49.4735],[6.10325,49.4707],[6.09845,49.46351],[6.10072,49.45268],[6.08373,49.45594],[6.07887,49.46399],[6.05553,49.46663],[6.04176,49.44801],[6.02693,49.44826],[6.02578,49.45147],[6.02845,49.45561],[5.97693,49.45513],[5.96725,49.49041],[5.94224,49.49608],[5.94128,49.50034],[5.86571,49.50015],[5.83389,49.52152],[5.83467,49.52717],[5.84466,49.53027],[5.83648,49.5425],[5.81664,49.53775],[5.80871,49.5425],[5.81838,49.54777],[5.79195,49.55228],[5.77435,49.56298],[5.7577,49.55915],[5.75627,49.54089],[5.64333,49.5507],[5.64345,49.54693],[5.60531,49.51016],[5.55885,49.53074],[5.46541,49.49825],[5.4475,49.51718],[5.46734,49.52648],[5.43713,49.5707],[5.3974,49.61596],[5.34837,49.62889],[5.33851,49.61599],[5.3137,49.61225],[5.30214,49.63055],[5.33039,49.6555],[5.31465,49.66846],[5.26232,49.69456],[5.16295,49.69437],[5.10158,49.75405],[4.96714,49.79872],[4.85464,49.78995],[4.86965,49.82271],[4.85134,49.86457],[4.88529,49.9236],[4.79041,49.9594],[4.8382,50.06738],[4.88602,50.15182],[4.83279,50.15331],[4.82359,50.16155],[4.82438,50.16878],[4.80634,50.15265],[4.763,50.13642],[4.75134,50.11192],[4.70154,50.09502],[4.68695,49.99685],[4.55091,49.96861],[4.5414,49.96911],[4.51098,49.94659],[4.43488,49.94122],[4.41822,49.94669],[4.35051,49.95315],[4.31963,49.97043],[4.20532,49.95803],[4.14239,49.98034],[4.16158,49.99157],[4.1318,50.01926],[4.16233,50.04944],[4.18965,50.04942],[4.23351,50.0697],[4.20209,50.10081],[4.19829,50.13496],[4.12985,50.13094],[4.1634,50.1892],[4.15524,50.21103],[4.21945,50.25539],[4.20651,50.27333],[4.17861,50.27443],[4.17407,50.28536],[4.15524,50.2833],[4.16808,50.25786],[4.13665,50.25609],[4.11962,50.30493],[4.10957,50.30234],[4.10172,50.31364],[4.07974,50.31],[4.07811,50.32072],[4.07104,50.32397],[4.0268,50.35793],[3.96812,50.3419],[3.96599,50.34939],[3.90888,50.32848],[3.89991,50.32708],[3.84365,50.35336],[3.82096,50.34622],[3.78092,50.3534],[3.73911,50.34809],[3.72857,50.31094],[3.70917,50.32217],[3.71009,50.30305],[3.68453,50.32771],[3.66976,50.34563],[3.65709,50.36873],[3.67262,50.38663],[3.67494,50.40239],[3.66153,50.45165],[3.64426,50.46275],[3.61314,50.49649],[3.58489,50.48939],[3.5683,50.50192],[3.54218,50.49571],[3.52991,50.49573],[3.49998,50.48663],[3.49622,50.49885],[3.52334,50.52393],[3.47385,50.53397],[3.4491,50.50753],[3.38516,50.49579],[3.32859,50.51097],[3.28575,50.52724],[3.2729,50.60718],[3.2405,50.65855],[3.24371,50.66959],[3.26525,50.67679],[3.25328,50.68987],[3.26141,50.69151],[3.261,50.70203],[3.25169,50.70637],[3.24547,50.71314],[3.23779,50.71146],[3.23152,50.71472],[3.22199,50.71015],[3.21332,50.71311],[3.20869,50.71102],[3.20478,50.71192],[3.21131,50.71735],[3.19041,50.72523],[3.20118,50.73561],[3.1877,50.74029],[3.18318,50.7503],[3.17994,50.75451],[3.17161,50.75866],[3.16476,50.76843],[3.15017,50.79031],[3.12612,50.78637],[3.11987,50.79188],[3.11144,50.79454],[3.10614,50.78303],[3.08552,50.7731],[3.05617,50.78021],[3.0402,50.77593],[3.03428,50.76949],[3.01922,50.77335],[3.00537,50.76588],[2.96778,50.75242],[2.95019,50.75138],[2.93944,50.74335],[2.94407,50.73183],[2.92965,50.72548],[2.92313,50.70309],[2.90873,50.702],[2.91036,50.6939],[2.90069,50.69263],[2.88504,50.70656],[2.87937,50.70298],[2.86985,50.7033],[2.86391,50.70865],[2.8483,50.72276],[2.81313,50.71604],[2.78988,50.7283],[2.77816,50.75101],[2.75662,50.76284],[2.76177,50.77115],[2.73344,50.78477],[2.71671,50.81358],[2.68036,50.81322],[2.67075,50.82201],[2.65676,50.81322],[2.6341,50.81301],[2.62006,50.84451],[2.598,50.84993],[2.61105,50.86529],[2.60564,50.90167],[2.59093,50.91751],[2.63074,50.94746],[2.60753,50.98323],[2.57457,51.00203],[2.55904,51.07014],[2.18458,51.52087],[1.17405,50.74239],[-2.02963,49.91866],[-2.09454,49.46288],[-1.83944,49.23037],[-2.00491,48.86706],[-2.5234,48.91595],[-2.56423,49.22209],[-2.9511,49.31141],[-5.81385,48.52441]],[[1.95606,42.45785],[1.96215,42.47854],[1.97003,42.48081],[1.97227,42.48487],[1.97697,42.48568],[1.98022,42.49569],[1.98916,42.49351],[1.99766,42.4858],[1.98579,42.47486],[1.99216,42.46208],[2.01564,42.45171],[1.99838,42.44682],[1.98378,42.44697],[1.96125,42.45364],[1.95606,42.45785]]],[[[39.10324,-21.48967],[40.40841,-23.17181],[43.72277,-16.09877],[41.06663,-17.08802],[39.10324,-21.48967]]],[[[44.82592,-12.46164],[44.83095,-13.17072],[45.42508,-13.16671],[45.42004,-12.45762],[44.82592,-12.46164]]],[[[46.52682,-10.83678],[47.29063,-12.45583],[48.86266,-10.8109],[46.52682,-10.83678]]],[[[48.6013,-46.16588],[70.19167,-50.55929],[78.57663,-37.28345],[48.6013,-46.16588]]],[[[54.31559,-15.68788],[54.3232,-16.10331],[54.73806,-16.0963],[54.73046,-15.68085],[54.31559,-15.68788]]],[[[55.04586,-20.69416],[55.06684,-21.59486],[56.6872,-21.56213],[56.66623,-20.66123],[55.04586,-20.69416]]],[[[157.46481,-18.93777],[158.4748,-21.86428],[166.93331,-23.49588],[173.07304,-22.54607],[162.93363,-17.28904],[157.46481,-18.93777]]]]}},{"type":"Feature","properties":{"id":"GB"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-133.61511,-21.93325],[-133.59543,-28.4709],[-122.0366,-24.55017],[-133.61511,-21.93325]]],[[[-81.81969,19.51758],[-81.52417,18.7521],[-79.33932,19.50496],[-79.63484,20.26689],[-81.81969,19.51758]]],[[[-72.94479,20.79216],[-71.13087,20.98822],[-70.63262,21.53631],[-72.41726,22.40371],[-72.94479,20.79216]]],[[[-65.23529,32.66274],[-65.22652,31.98296],[-64.37503,31.99084],[-64.3838,32.67056],[-65.23529,32.66274]]],[[[-65.02435,18.73231],[-64.86049,18.39954],[-64.64067,18.36478],[-64.646,18.10286],[-63.95092,18.07976],[-63.35989,18.06012],[-62.86666,18.19278],[-62.75637,18.13489],[-62.64209,18.3662],[-63.3414,18.89029],[-63.90607,18.93262],[-64.62855,18.98678],[-65.02435,18.73231]]],[[[-62.52079,16.69392],[-62.17275,16.35721],[-61.83929,16.66647],[-62.14123,17.02632],[-62.52079,16.69392]]],[[[-62.38818,-51.70652],[-59.03327,-53.28879],[-56.99965,-51.29617],[-62.10074,-50.6407],[-62.38818,-51.70652]]],[[[-42.54719,-53.2784],[-42.2676,-53.8363],[-26.56899,-59.99267],[-23.74554,-55.04457],[-42.54719,-53.2784]]],[[[-14.78497,57.60709],[-7.93366,55.84142],[-6.79943,55.54107],[-6.71944,55.27952],[-6.9734,55.19878],[-7.2471,55.06933],[-7.34367,55.04808],[-7.4033,55.00391],[-7.40004,54.94498],[-7.44404,54.9403],[-7.4473,54.87003],[-7.47626,54.83084],[-7.54508,54.79401],[-7.54671,54.74606],[-7.64449,54.75265],[-7.75041,54.7103],[-7.83352,54.73854],[-7.93293,54.66603],[-7.68965,54.61736],[-7.77445,54.5839],[-7.82827,54.55539],[-7.85084,54.53358],[-8.00714,54.54528],[-8.04216,54.49297],[-8.09924,54.48405],[-8.09297,54.47697],[-8.1782,54.46614],[-8.14172,54.45063],[-8.16206,54.44204],[-8.04555,54.36292],[-7.99118,54.34675],[-7.95324,54.30721],[-7.86552,54.29318],[-7.8596,54.21779],[-7.81397,54.20159],[-7.69501,54.20731],[-7.55812,54.12239],[-7.4799,54.12239],[-7.44567,54.1539],[-7.32834,54.11475],[-7.30553,54.11869],[-7.34005,54.14698],[-7.29157,54.17191],[-7.28017,54.16714],[-7.29687,54.1354],[-7.29493,54.12013],[-7.26316,54.13863],[-7.25012,54.20063],[-7.14908,54.22732],[-7.19145,54.31296],[-7.02034,54.4212],[-6.87775,54.34682],[-6.85179,54.29176],[-6.87692,54.28036],[-6.83383,54.26291],[-6.81583,54.22791],[-6.74575,54.18788],[-6.70175,54.20218],[-6.6382,54.17071],[-6.66264,54.0666],[-6.62842,54.03503],[-6.47849,54.06947],[-6.36605,54.07234],[-6.36279,54.11248],[-6.32694,54.09337],[-6.29003,54.11278],[-6.26218,54.09785],[-5.81146,53.88396],[-5.37267,53.63269],[-5.79914,52.03902],[-4.80826,51.25744],[-6.81839,49.7273],[1.17405,50.74239],[2.18458,51.52087],[2.65913,51.8564],[-1.16893,55.97338],[-0.3751,61.32236],[-14.78497,57.60709]]],[[[-14.60614,-7.38624],[-14.56953,-8.25719],[-13.48367,-36.6746],[-13.41694,-37.88844],[-9.58838,-40.85412],[-5.42129,-15.86551],[-13.99188,-7.95424],[-14.60614,-7.38624]]],[[[-5.40134,36.14896],[-5.39074,36.10278],[-5.36503,36.06205],[-5.32837,36.05935],[-5.3004,36.07439],[-5.28217,36.09907],[-5.27801,36.14942],[-5.33822,36.15272],[-5.34536,36.15501],[-5.36494,36.15496],[-5.38545,36.15481],[-5.40134,36.14896]]],[[[-3.06097,49.47231],[-2.9511,49.31141],[-2.56423,49.22209],[-2.5234,48.91595],[-2.00491,48.86706],[-1.83944,49.23037],[-2.09454,49.46288],[-2.02963,49.91866],[-2.39388,49.94612],[-3.06097,49.47231]]],[[[32.74412,34.43926],[33.26744,34.49942],[33.0138,34.64424],[32.96915,34.63938],[32.96718,34.63446],[32.95891,34.62919],[32.95323,34.64075],[32.95471,34.64528],[32.95271,34.65103],[32.96151,34.65598],[32.96494,34.65873],[32.97031,34.65421],[32.97928,34.65357],[32.99014,34.65518],[32.98795,34.67221],[32.98984,34.68019],[32.95559,34.68396],[32.94769,34.67909],[32.94143,34.67394],[32.93967,34.67045],[32.93808,34.67073],[32.93465,34.66385],[32.92829,34.66808],[32.93043,34.67091],[32.91398,34.67343],[32.90873,34.66286],[32.89019,34.66837],[32.86096,34.68781],[32.85817,34.70531],[32.82556,34.7057],[32.79264,34.6768],[32.75972,34.68156],[32.75515,34.64985],[32.74412,34.43926]]],[[[33.67378,35.05936],[33.67807,35.04046],[33.70553,35.0295],[33.69661,35.01607],[33.71412,35.0037],[33.70639,34.99303],[33.70575,34.97947],[33.83531,34.73974],[33.98684,34.76642],[33.90131,34.96478],[33.8602,34.97375],[33.8405,34.97114],[33.83445,34.97508],[33.85475,34.99157],[33.85385,35.00254],[33.83651,35.00321],[33.83295,35.00694],[33.8272,35.00869],[33.82656,35.03154],[33.8205,35.03814],[33.79201,35.0483],[33.79806,35.0595],[33.81995,35.07159],[33.82716,35.06934],[33.83883,35.06133],[33.84939,35.06337],[33.86235,35.07039],[33.87471,35.08114],[33.89277,35.06794],[33.90247,35.07686],[33.91217,35.08132],[33.91633,35.0869],[33.8932,35.11453],[33.88569,35.11611],[33.88398,35.12324],[33.87217,35.12391],[33.86844,35.10439],[33.86724,35.10084],[33.86754,35.09445],[33.87342,35.08276],[33.86149,35.07194],[33.84844,35.06407],[33.83909,35.06235],[33.82733,35.0705],[33.81939,35.07271],[33.79694,35.06],[33.79094,35.04875],[33.76403,35.0392],[33.73781,35.05589],[33.70584,35.03618],[33.70343,35.04819],[33.71704,35.07018],[33.70536,35.07559],[33.69412,35.06618],[33.68399,35.06498],[33.67378,35.05936]],[[33.7343,35.01178],[33.73781,35.02181],[33.74265,35.02329],[33.74983,35.02274],[33.7492,35.01319],[33.74144,35.01053],[33.7343,35.01178]],[[33.75682,34.99916],[33.75994,35.00113],[33.77312,34.9976],[33.77553,34.99518],[33.78516,34.99582],[33.79191,34.98914],[33.78917,34.98854],[33.78571,34.98951],[33.78318,34.98699],[33.78149,34.98854],[33.77843,34.988],[33.7778,34.98981],[33.76738,34.99188],[33.76605,34.99543],[33.75682,34.99916]]],[[[70.64391,-7.71751],[72.09053,-7.71938],[73.19616,-7.72081],[73.19718,-6.94577],[73.19979,-4.96078],[70.64754,-4.95745],[70.64391,-7.71751]]]]}},{"type":"Feature","properties":{"id":"IT"},"geometry":{"type":"MultiPolygon","coordinates":[[[[6.62803,45.11175],[6.66981,45.02324],[6.74791,45.01939],[6.744,44.93629],[6.75655,44.90352],[6.90774,44.84322],[6.93499,44.8664],[7.02217,44.82519],[7.00401,44.78782],[7.07484,44.68073],[6.99105,44.69205],[6.95133,44.66264],[6.96042,44.62129],[6.91675,44.56833],[6.85507,44.53072],[6.86233,44.49834],[6.94504,44.43112],[6.88784,44.42043],[6.89171,44.36637],[6.98221,44.28289],[7.00764,44.23736],[7.16929,44.20352],[7.27827,44.1462],[7.34547,44.14359],[7.36072,44.11302],[7.56597,44.15191],[7.62155,44.14881],[7.63245,44.17877],[7.68694,44.17487],[7.66878,44.12795],[7.71901,44.08555],[7.71375,44.06245],[7.66133,44.02644],[7.66848,43.99943],[7.65738,43.9766],[7.56858,43.94506],[7.56075,43.89932],[7.51162,43.88301],[7.49355,43.86551],[7.50423,43.84345],[7.5289,43.78887],[7.63035,43.57419],[9.60234,43.19214],[9.72681,41.374],[8.06341,41.16624],[8.28559,38.46209],[10.73363,38.54816],[12.02012,35.25036],[12.80065,35.1178],[13.4634,35.88474],[14.12059,36.67252],[15.31045,36.18238],[18.75365,39.82496],[18.83516,40.36999],[16.15283,42.18525],[13.12821,44.48877],[13.21833,45.43996],[13.45644,45.59464],[13.6076,45.64761],[13.7198,45.59352],[13.74587,45.59811],[13.78445,45.5825],[13.84625,45.58227],[13.86771,45.59898],[13.8695,45.60835],[13.9191,45.6322],[13.87933,45.65207],[13.83422,45.68703],[13.83332,45.70855],[13.78968,45.74144],[13.66986,45.79955],[13.59784,45.8072],[13.58858,45.83503],[13.57563,45.8425],[13.58644,45.88173],[13.59565,45.89446],[13.60857,45.89907],[13.61931,45.91782],[13.63815,45.93607],[13.6329,45.94894],[13.64375,45.98296],[13.63458,45.98947],[13.62074,45.98388],[13.58903,45.99009],[13.56759,45.96991],[13.52963,45.96588],[13.51429,45.97808],[13.50104,45.98078],[13.47581,46.00703],[13.50186,46.02031],[13.50998,46.04498],[13.49568,46.04839],[13.49867,46.0595],[13.52102,46.0633],[13.57669,46.09406],[13.64053,46.13587],[13.66815,46.1776],[13.64451,46.18966],[13.56682,46.18703],[13.56114,46.2054],[13.47587,46.22725],[13.4149,46.20846],[13.37671,46.29668],[13.45344,46.32636],[13.43418,46.35992],[13.47019,46.3621],[13.5763,46.40915],[13.5763,46.42613],[13.59777,46.44137],[13.68684,46.43881],[13.7148,46.5222],[13.49599,46.55634],[13.27627,46.56059],[13.09123,46.59661],[12.94445,46.60401],[12.73361,46.63797],[12.59992,46.6595],[12.47875,46.67888],[12.37952,46.72452],[12.31378,46.79718],[12.27591,46.88651],[12.2006,46.88854],[12.11675,47.01241],[12.23291,47.04487],[12.20764,47.09821],[11.74789,46.98484],[11.50726,47.00642],[11.40724,46.96689],[11.33355,46.99862],[11.12094,46.93552],[11.01811,46.76214],[10.72974,46.78972],[10.75753,46.82258],[10.66405,46.87614],[10.54783,46.84505],[10.46999,46.85498],[10.38659,46.67847],[10.40475,46.63671],[10.44686,46.64162],[10.49375,46.62049],[10.45299,46.53081],[10.25309,46.57432],[10.23674,46.63484],[10.10307,46.61003],[10.03715,46.44479],[10.165,46.41051],[10.10506,46.3372],[10.17862,46.25626],[10.14439,46.22992],[10.07055,46.21668],[9.95249,46.38045],[9.73086,46.35071],[9.71273,46.29266],[9.57015,46.2958],[9.46117,46.37481],[9.45936,46.50873],[9.40487,46.46621],[9.36128,46.5081],[9.28136,46.49685],[9.25502,46.43743],[9.29226,46.32717],[9.24503,46.23616],[9.08466,46.0849],[9.01618,46.04928],[8.99257,45.9698],[9.09065,45.89906],[9.06642,45.8761],[9.04378,45.84681],[9.04059,45.8464],[9.03505,45.83976],[9.03793,45.83548],[9.03279,45.82865],[9.03213,45.82081],[8.99608,45.82093],[8.99741,45.83489],[8.9621,45.83707],[8.95716,45.84327],[8.91132,45.83062],[8.91424,45.84207],[8.9336,45.86198],[8.94587,45.86712],[8.94212,45.86961],[8.93673,45.86734],[8.92141,45.9084],[8.89394,45.93091],[8.89609,45.95825],[8.87223,45.95926],[8.85121,45.97239],[8.8319,45.9879],[8.79362,45.99207],[8.78585,45.98973],[8.79541,46.01115],[8.82322,46.02897],[8.85617,46.0748],[8.80778,46.10085],[8.77884,46.09561],[8.75697,46.10395],[8.69713,46.10204],[8.59508,46.13809],[8.60383,46.15522],[8.45166,46.24801],[8.46317,46.43712],[8.42464,46.46367],[8.30648,46.41587],[8.31162,46.38044],[8.08814,46.26692],[8.16866,46.17817],[8.11383,46.11577],[8.02906,46.10331],[7.98881,45.99867],[7.9049,45.99945],[7.85949,45.91485],[7.56343,45.97421],[7.10685,45.85653],[7.04151,45.92435],[6.95315,45.85163],[6.80785,45.83265],[6.80785,45.71864],[6.98948,45.63869],[7.00037,45.509],[7.09297,45.46959],[7.18019,45.40071],[7.10572,45.32924],[7.13115,45.25386],[7.07074,45.21228],[6.96706,45.20841],[6.85144,45.13226],[6.7697,45.16044],[6.62803,45.11175]],[[12.40415,43.95485],[12.41414,43.95273],[12.42005,43.9578],[12.43662,43.95698],[12.44684,43.96597],[12.46205,43.97463],[12.47853,43.98052],[12.49406,43.98492],[12.50678,43.99113],[12.51463,43.99122],[12.5154,43.98508],[12.51064,43.98165],[12.51109,43.97201],[12.50622,43.97131],[12.50875,43.96198],[12.50655,43.95796],[12.51427,43.94897],[12.51553,43.94096],[12.50496,43.93017],[12.50269,43.92363],[12.49724,43.92248],[12.49247,43.91774],[12.49429,43.90973],[12.48771,43.89706],[12.45648,43.89369],[12.44184,43.90498],[12.41641,43.89991],[12.40935,43.9024],[12.41233,43.90956],[12.40733,43.92379],[12.41551,43.92984],[12.41165,43.93769],[12.40506,43.94325],[12.40415,43.95485]],[[12.44582,41.90194],[12.44815,41.90326],[12.44984,41.90545],[12.45091,41.90625],[12.45543,41.90738],[12.45561,41.90629],[12.45762,41.9058],[12.45755,41.9033],[12.45826,41.90281],[12.45834,41.90174],[12.4577,41.90115],[12.45691,41.90125],[12.45626,41.90172],[12.45435,41.90143],[12.45446,41.90028],[12.45181,41.90056],[12.44834,41.90095],[12.44582,41.90194]]],[[[8.95861,45.96485],[8.97604,45.96151],[8.97741,45.98317],[8.96668,45.98436],[8.95861,45.96485]]]]}},{"type":"Feature","properties":{"id":"KG"},"geometry":{"type":"Polygon","coordinates":[[[69.26938,39.8127],[69.37454,39.52364],[69.68677,39.59281],[69.87491,39.53882],[70.11111,39.58223],[70.2869,39.53141],[70.44757,39.60128],[70.64087,39.58792],[70.7854,39.38933],[71.06418,39.41586],[71.08752,39.50704],[71.49814,39.61397],[71.55856,39.57588],[71.5517,39.45722],[71.62688,39.44056],[71.76816,39.45456],[71.80164,39.40631],[71.7522,39.32031],[71.79202,39.27355],[71.90601,39.27674],[72.04059,39.36704],[72.09689,39.26823],[72.17242,39.2661],[72.23834,39.17248],[72.33173,39.33093],[72.62027,39.39696],[72.85934,39.35116],[73.18454,39.35536],[73.31912,39.38615],[73.45096,39.46677],[73.59831,39.46425],[73.87018,39.47879],[73.94683,39.60733],[73.92354,39.69565],[73.9051,39.75073],[73.83006,39.76136],[73.97049,40.04378],[74.25533,40.13191],[74.35063,40.09742],[74.69875,40.34668],[74.85996,40.32857],[74.78168,40.44886],[74.82013,40.52197],[75.08243,40.43945],[75.22834,40.45382],[75.5854,40.66874],[75.69663,40.28642],[75.91361,40.2948],[75.96168,40.38064],[76.33659,40.3482],[76.5261,40.46114],[76.75681,40.95354],[76.99302,41.0696],[77.28004,41.0033],[77.3693,41.0375],[77.52723,41.00227],[77.76206,41.01574],[77.81287,41.14307],[78.12873,41.23091],[78.15757,41.38565],[78.3732,41.39603],[79.92977,42.04113],[80.17842,42.03211],[80.17807,42.21166],[79.97364,42.42816],[79.52921,42.44778],[79.18807,42.69051],[79.19763,42.804],[78.91502,42.76839],[78.48469,42.89649],[75.82823,42.94848],[75.72174,42.79672],[75.54988,42.83116],[75.29565,42.85973],[75.2251,42.85507],[74.83217,43.00088],[74.75,42.99029],[74.70331,43.02519],[74.64615,43.05881],[74.57639,43.13268],[74.22489,43.24657],[73.96064,43.20392],[73.89816,43.12604],[73.55634,43.03071],[73.5102,42.9167],[73.50992,42.82356],[73.45355,42.42294],[71.88792,42.83578],[71.62405,42.76613],[71.53272,42.8014],[71.27002,42.77272],[71.22785,42.69248],[71.17807,42.67381],[71.15232,42.60486],[70.97717,42.50147],[70.85973,42.30188],[70.94483,42.26238],[71.13263,42.28356],[71.28719,42.18033],[70.69777,41.92554],[70.17682,41.5455],[70.48909,41.40335],[70.67586,41.47953],[70.78572,41.36419],[70.77885,41.24813],[70.86263,41.23833],[70.9615,41.16393],[71.02193,41.19494],[71.11896,41.14427],[71.25813,41.18796],[71.28049,41.0994],[71.3416,41.12656],[71.39671,41.10781],[71.45198,41.15668],[71.43814,41.19644],[71.46537,41.3036],[71.57227,41.29175],[71.6787,41.42111],[71.66913,41.44787],[71.60167,41.47617],[71.66759,41.49739],[71.70553,41.53736],[71.73471,41.55175],[71.7069,41.42355],[71.77419,41.44748],[71.84492,41.35838],[71.92491,41.2938],[71.85964,41.19081],[72.07262,41.11764],[72.10867,41.15455],[72.16433,41.16483],[72.17594,41.15522],[72.14515,41.13445],[72.1792,41.10621],[72.21061,41.05607],[72.17245,40.99272],[72.23476,41.00179],[72.324,41.03381],[72.34026,41.04539],[72.34757,41.06104],[72.36138,41.04384],[72.39415,41.02724],[72.45206,41.03018],[72.49826,40.98663],[72.49405,40.96952],[72.53474,40.96123],[72.54547,40.96538],[72.59362,40.90287],[72.57276,40.87679],[72.68157,40.84942],[72.84291,40.85512],[72.94312,40.80698],[73.01869,40.84681],[73.02972,40.83959],[73.1407,40.84309],[73.14208,40.79236],[73.06431,40.76546],[72.99133,40.76457],[72.93296,40.73089],[72.8722,40.71111],[72.85372,40.7116],[72.86072,40.69593],[72.84754,40.67229],[72.80137,40.67856],[72.74866,40.60873],[72.74894,40.59592],[72.76129,40.57253],[72.74713,40.57136],[72.74768,40.58051],[72.73807,40.58358],[72.69215,40.59642],[72.66713,40.59076],[72.66975,40.51366],[72.47795,40.5532],[72.40517,40.61917],[72.34406,40.60144],[72.41621,40.55515],[72.37861,40.51118],[72.41513,40.50856],[72.44195,40.47359],[72.40346,40.4007],[72.24368,40.46091],[72.17365,40.49017],[72.05623,40.38421],[71.96401,40.31907],[72.05451,40.27795],[72.0207,40.26734],[72.00353,40.2752],[71.97658,40.25673],[71.92714,40.25018],[71.83977,40.25306],[71.83462,40.20877],[71.78346,40.20326],[71.72887,40.14765],[71.7202,40.17415],[71.69583,40.18372],[71.70569,40.20391],[71.68386,40.26984],[71.61931,40.26775],[71.61725,40.20615],[71.51549,40.22986],[71.51215,40.26943],[71.4246,40.28619],[71.36663,40.31593],[71.13042,40.34106],[71.05901,40.28765],[70.95789,40.28761],[70.9818,40.22392],[70.89228,40.21892],[70.8843,40.17657],[70.84559,40.16451],[70.80413,40.1807],[70.79572,40.12599],[70.63934,40.10065],[70.62629,40.05087],[70.61779,39.99915],[70.59419,39.96824],[70.56037,39.95758],[70.53797,39.97363],[70.59059,39.99954],[70.60964,40.03734],[70.53308,40.033],[69.99514,40.23406],[69.88523,40.20182],[69.78378,40.17887],[69.69958,40.1315],[69.68593,40.12625],[69.68627,40.11529],[69.68224,40.11464],[69.67615,40.12238],[69.65057,40.12251],[69.5753,40.10302],[69.55555,40.12296],[69.5365,40.11674],[69.53855,40.0887],[69.5057,40.03277],[69.53916,39.93797],[69.49762,39.928],[69.42938,39.92777],[69.43134,39.98431],[69.35649,40.01994],[69.26938,39.8127]],[[70.52631,39.86989],[70.53651,39.89155],[70.74189,39.86319],[70.63105,39.77923],[70.59667,39.83542],[70.54998,39.85137],[70.52631,39.86989]],[[71.00236,40.18154],[71.0618,40.17756],[71.12428,40.01197],[71.19174,40.01012],[71.14282,39.95139],[71.21209,39.95324],[71.2047,39.94277],[71.23586,39.92395],[71.18445,39.91816],[71.11192,39.93461],[71.09407,39.90348],[71.04429,39.89367],[71.09029,39.94916],[71.11089,39.95843],[71.09063,39.99],[71.11668,39.99291],[71.11037,40.01984],[71.01035,40.05481],[71.00236,40.18154]],[[71.71511,39.96348],[71.79084,40.01105],[71.85873,39.9852],[71.84316,39.95582],[71.74673,39.93422],[71.71511,39.96348]]]}},{"type":"Feature","properties":{"id":"KI"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-175.33482,-1.40631],[-175.31804,-7.54825],[-174.18707,-7.54408],[-167.75329,-7.52784],[-156.50903,-7.4975],[-156.4957,-12.32002],[-149.61166,-12.30171],[-149.6249,-7.51261],[-149.65979,5.27712],[-161.06795,5.2462],[-161.05669,1.11722],[-158.62734,1.1296],[-158.62058,-1.35506],[-161.04969,-1.36251],[-175.33482,-1.40631]]],[[[169,-3.5],[178,-3.5],[178,3.9],[169,3.9],[169,-3.5]]]]}},{"type":"Feature","properties":{"id":"NL"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-70.34259,12.92535],[-70.24399,12.38063],[-69.4514,12.18025],[-68.99639,11.79035],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309],[-69.5195,12.75292],[-70.34259,12.92535]]],[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-62.93924,18.02904],[-63.02323,18.05757],[-63.02886,18.05482],[-63.03669,18.05786],[-63.03998,18.05596],[-63.0591,18.06744],[-63.07759,18.04943],[-63.09686,18.04608],[-63.11096,18.05368],[-63.13584,18.0541],[-63.33064,17.9615],[-63.29212,17.90532],[-63.58819,17.61311]]],[[[2.65913,51.8564],[3.36263,51.37112],[3.38696,51.33436],[3.35847,51.31572],[3.38289,51.27331],[3.41704,51.25933],[3.43488,51.24135],[3.52698,51.2458],[3.51502,51.28697],[3.58939,51.30064],[3.78999,51.25766],[3.78783,51.2151],[3.90125,51.20371],[3.97889,51.22537],[4.01957,51.24504],[4.05165,51.24171],[4.16721,51.29348],[4.24024,51.35371],[4.21923,51.37443],[4.33265,51.37687],[4.34086,51.35738],[4.39292,51.35547],[4.43777,51.36989],[4.38064,51.41965],[4.39747,51.43316],[4.38122,51.44905],[4.47736,51.4778],[4.5388,51.48184],[4.54675,51.47265],[4.52846,51.45002],[4.53521,51.4243],[4.57489,51.4324],[4.65442,51.42352],[4.72935,51.48424],[4.74578,51.48937],[4.77321,51.50529],[4.78803,51.50284],[4.84139,51.4799],[4.82409,51.44736],[4.82946,51.4213],[4.78314,51.43319],[4.76577,51.43046],[4.77229,51.41337],[4.78941,51.41102],[4.84988,51.41502],[4.90016,51.41404],[4.92152,51.39487],[5.00393,51.44406],[5.0106,51.47167],[5.03281,51.48679],[5.04774,51.47022],[5.07891,51.4715],[5.10456,51.43163],[5.07102,51.39469],[5.13105,51.34791],[5.13377,51.31592],[5.16222,51.31035],[5.2002,51.32243],[5.24244,51.30495],[5.22542,51.26888],[5.23814,51.26064],[5.26461,51.26693],[5.29716,51.26104],[5.33886,51.26314],[5.347,51.27502],[5.41672,51.26248],[5.4407,51.28169],[5.46519,51.2849],[5.48476,51.30053],[5.515,51.29462],[5.5569,51.26544],[5.5603,51.22249],[5.65145,51.19788],[5.65528,51.18736],[5.7082,51.18193],[5.74617,51.18928],[5.77735,51.17845],[5.77717,51.15122],[5.80498,51.16268],[5.81412,51.15908],[5.82417,51.16831],[5.85508,51.14445],[5.80798,51.11661],[5.8109,51.10861],[5.83226,51.10585],[5.82921,51.09328],[5.79903,51.09371],[5.79835,51.05834],[5.77258,51.06196],[5.75961,51.03113],[5.77631,51.0246],[5.76147,50.99452],[5.7352,50.97588],[5.71864,50.96092],[5.72875,50.95428],[5.74752,50.96202],[5.75927,50.95601],[5.74644,50.94723],[5.72679,50.92289],[5.72644,50.91167],[5.71626,50.90796],[5.69858,50.91046],[5.67924,50.88069],[5.668,50.88048],[5.64504,50.87107],[5.64009,50.84742],[5.65486,50.82074],[5.68799,50.81183],[5.70118,50.80764],[5.69332,50.79763],[5.70107,50.7827],[5.68091,50.75804],[5.69469,50.75529],[5.72216,50.76398],[5.73904,50.75674],[5.74356,50.7691],[5.76533,50.78159],[5.77513,50.78308],[5.80673,50.7558],[5.84548,50.76542],[5.84888,50.75448],[5.88734,50.77092],[5.89129,50.75125],[5.89132,50.75124],[5.95942,50.7622],[5.97545,50.75441],[6.02067,50.75421],[6.01866,50.76374],[6.0281,50.7743],[5.9957,50.78819],[5.99574,50.79036],[5.97407,50.79899],[5.98404,50.80988],[6.00462,50.80065],[6.0254,50.81651],[6.01698,50.84356],[6.05623,50.8572],[6.05702,50.85179],[6.07431,50.84674],[6.07693,50.86025],[6.08805,50.87223],[6.08679,50.87929],[6.07486,50.89307],[6.09297,50.92066],[6.05715,50.92164],[6.01758,50.93501],[6.01243,50.95502],[6.02697,50.98303],[5.96325,50.98172],[5.95407,50.98826],[5.90296,50.97356],[5.90493,51.00198],[5.87849,51.01969],[5.86735,51.05182],[5.9134,51.06736],[5.93708,51.03346],[5.95785,51.03491],[5.98248,51.07451],[6.01861,51.09387],[6.12007,51.14053],[6.17448,51.15733],[6.18178,51.18709],[6.16513,51.19472],[6.07889,51.17038],[6.07337,51.24289],[6.12599,51.27491],[6.16702,51.32599],[6.2265,51.36058],[6.2156,51.38731],[6.22632,51.40022],[6.20654,51.40049],[6.22135,51.44967],[6.21724,51.48568],[6.20736,51.5199],[6.17895,51.53939],[6.12401,51.59061],[6.09055,51.60564],[6.11759,51.65609],[6.02767,51.6742],[6.04091,51.71821],[5.94943,51.74754],[5.99441,51.76948],[5.97793,51.79805],[5.93304,51.82103],[5.97046,51.8337],[6.00574,51.8337],[6.07071,51.86207],[6.10337,51.84829],[6.16902,51.84094],[6.11551,51.89769],[6.15349,51.90439],[6.21443,51.86801],[6.29872,51.86801],[6.30593,51.84998],[6.40704,51.82771],[6.38815,51.87257],[6.47179,51.85395],[6.50231,51.86313],[6.58556,51.89386],[6.68386,51.91861],[6.72319,51.89518],[6.82357,51.96711],[6.83035,51.9905],[6.68106,52.04884],[6.74843,52.08899],[6.76117,52.11895],[6.83984,52.11728],[6.97189,52.20329],[6.9897,52.2271],[7.03729,52.22695],[7.06365,52.23789],[7.02703,52.27941],[7.07656,52.37717],[7.03417,52.40237],[6.99041,52.47235],[6.95417,52.43832],[6.76517,52.46207],[6.69719,52.48633],[6.70474,52.52123],[6.68106,52.55339],[6.76611,52.56189],[6.7056,52.62722],[6.78028,52.65389],[7.05047,52.634],[7.07253,52.81083],[7.21694,53.00742],[7.17898,53.13817],[7.22681,53.18165],[7.21679,53.20058],[7.19052,53.31866],[7.00198,53.32672],[6.91025,53.44221],[5.71287,54.07228],[2.65913,51.8564]],[[4.91493,51.4353],[4.91935,51.43634],[4.92227,51.44252],[4.91811,51.44621],[4.92287,51.44741],[4.92811,51.4437],[4.92566,51.44273],[4.92815,51.43856],[4.92879,51.44161],[4.93544,51.44634],[4.94025,51.44193],[4.93416,51.44185],[4.93471,51.43861],[4.94265,51.44003],[4.93986,51.43064],[4.92952,51.42984],[4.92652,51.43329],[4.91493,51.4353]],[[4.93295,51.44945],[4.95244,51.45207],[4.9524,51.45014],[4.93909,51.44632],[4.93295,51.44945]]]]}},{"type":"Feature","properties":{"id":"NO"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-9.46764,70.6182],[-7.10917,70.97072],[-8.64261,71.45615],[-9.46764,70.6182]]],[[[-0.3751,61.32236],[7.28637,57.35913],[10.40861,58.38489],[10.64958,58.89391],[11.08911,58.98745],[11.15367,59.07862],[11.34459,59.11672],[11.4601,58.99022],[11.45199,58.89604],[11.65732,58.90177],[11.8213,59.24985],[11.75228,59.48658],[11.69297,59.59442],[11.92112,59.69531],[11.87121,59.86039],[12.16787,59.88807],[12.23121,59.92758],[12.34228,59.96583],[12.39446,60.01589],[12.45111,60.04067],[12.52003,60.13846],[12.59133,60.50559],[12.2277,61.02442],[12.69115,61.06584],[12.86939,61.35427],[12.57707,61.56547],[12.40595,61.57226],[12.14746,61.7147],[12.29187,62.25699],[12.07085,62.6297],[12.19919,63.00104],[11.98529,63.27487],[12.21267,63.47673],[12.14928,63.59373],[12.29919,63.67246],[12.68405,63.9752],[12.92816,64.05958],[13.21208,64.09605],[13.98222,64.00953],[14.16051,64.18725],[14.11117,64.46674],[13.64276,64.58402],[14.50926,65.31786],[14.53778,66.12399],[15.05113,66.15572],[15.49318,66.28509],[15.37197,66.48217],[16.35589,67.06419],[16.39154,67.21653],[16.09922,67.4364],[16.12774,67.52106],[16.38441,67.52923],[16.7409,67.91037],[17.30416,68.11591],[17.90787,67.96537],[18.13836,68.20874],[18.1241,68.53721],[18.39503,68.58672],[18.63032,68.50849],[18.97255,68.52416],[19.93508,68.35911],[20.22027,68.48759],[19.95647,68.55546],[20.22027,68.67246],[20.33435,68.80174],[20.28444,68.93283],[20.0695,69.04469],[20.55258,69.06069],[20.72171,69.11874],[21.05775,69.0356],[21.11099,69.10291],[20.98641,69.18809],[21.00732,69.22755],[21.27827,69.31281],[21.63833,69.27485],[22.27276,68.89514],[22.38367,68.71561],[22.53321,68.74393],[23.13064,68.64684],[23.68017,68.70276],[23.781,68.84514],[24.02299,68.81601],[24.18432,68.73936],[24.74898,68.65143],[24.90023,68.55579],[24.93048,68.61102],[25.10189,68.63307],[25.12206,68.78684],[25.42455,68.90328],[25.61613,68.89602],[25.75729,68.99383],[25.69679,69.27039],[25.96904,69.68397],[26.40261,69.91377],[26.64461,69.96565],[27.05802,69.92069],[27.57226,70.06215],[27.95542,70.0965],[27.97558,69.99671],[28.32849,69.88605],[28.36883,69.81658],[29.12697,69.69193],[29.31664,69.47994],[28.8629,69.22395],[28.81248,69.11997],[28.91738,69.04774],[29.0444,69.0119],[29.26623,69.13794],[29.27631,69.2811],[29.97205,69.41623],[30.16363,69.65244],[30.52662,69.54699],[30.95011,69.54699],[30.84095,69.80584],[31.59909,70.16571],[29.30957,71.40318],[18.64087,70.84478],[-0.3751,61.32236]]],[[[2.85578,-54.70531],[3.87126,-54.71036],[3.87947,-54.15611],[2.86398,-54.15099],[2.85578,-54.70531]]],[[[5.93257,80.704],[16.30346,73.63706],[30.54194,75.9202],[36.27514,81.36349],[5.93257,80.704]]]]}},{"type":"Feature","properties":{"id":"NZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-180,-45.18423],[-173.00283,-45.20102],[-173.10761,-24.19665],[-180,-24.21376],[-180,-45.18423]]],[[[-174.18707,-7.54408],[-174.17993,-10.13616],[-167.75195,-10.12005],[-167.73854,-14.92809],[-171.14262,-14.93704],[-173.13438,-14.94228],[-173.11048,-23.23027],[-167.73129,-23.22266],[-156.46451,-23.21255],[-156.4957,-12.32002],[-156.50903,-7.4975],[-167.75329,-7.52784],[-174.18707,-7.54408]]],[[[164.49803,-50.68404],[169.00308,-53.19756],[179.49541,-50.04657],[179.49541,-36.79303],[169.6687,-29.09191],[169.35326,-30.60259],[164.49803,-50.68404]]]]}},{"type":"Feature","properties":{"id":"OM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[52.00311,19.00083],[52.78009,17.35124],[52.74267,17.29519],[52.81185,17.28568],[53.09917,16.67084],[53.32998,16.16312],[56.66759,17.24021],[61.45114,22.55394],[56.86325,25.03856],[56.3227,24.97284],[56.34873,24.93205],[56.29231,24.88425],[56.25952,24.86011],[56.20568,24.85063],[56.20062,24.78565],[56.16185,24.76436],[56.13684,24.73699],[56.06128,24.74457],[56.03535,24.81161],[55.97836,24.87673],[55.97963,24.89492],[56.05106,24.87461],[56.05715,24.95727],[55.96195,25.0062],[55.91182,24.96567],[55.85191,24.96582],[55.81312,24.91072],[55.81348,24.80102],[55.83408,24.77858],[55.83271,24.68567],[55.76461,24.5287],[55.83271,24.41521],[55.83395,24.32776],[55.80747,24.31069],[55.79145,24.27914],[55.76781,24.26209],[55.75939,24.26114],[55.75382,24.2466],[55.75257,24.23466],[55.76558,24.23227],[55.77658,24.23476],[55.83367,24.20193],[55.95457,24.22301],[56.01799,24.07426],[55.8308,24.01633],[55.73301,24.05994],[55.50258,23.95645],[55.59287,23.6549],[55.41761,23.38259],[55.22634,23.10378],[55.21095,22.70827],[55.66469,21.99658],[54.99756,20.00083],[52.00311,19.00083]]],[[[55.81777,26.18798],[56.08666,26.05038],[56.15498,26.06828],[56.19189,25.97475],[56.17991,25.95217],[56.16545,25.94376],[56.17871,25.9047],[56.13963,25.82765],[56.16717,25.72668],[56.15564,25.6625],[56.18363,25.65508],[56.20473,25.61119],[56.25365,25.60211],[56.26636,25.60643],[56.26703,25.60805],[56.2615,25.61053],[56.25939,25.61745],[56.2645,25.62438],[56.82555,25.7713],[56.70052,26.88164],[55.81777,26.18798]]],[[[56.20838,25.25668],[56.20872,25.24104],[56.24341,25.22867],[56.27628,25.23404],[56.34438,25.26653],[56.35172,25.30681],[56.3111,25.30107],[56.3005,25.31815],[56.26062,25.33108],[56.24357,25.31827],[56.26003,25.29417],[56.24465,25.27505],[56.20838,25.25668]],[[56.27086,25.26128],[56.2716,25.27916],[56.28102,25.28486],[56.29379,25.2754],[56.28423,25.26344],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"RU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-180,64.74703],[-172.76104,63.77445],[-168.9812,65.50181],[-168.65315,71.12923],[-180,71.90735],[-180,64.74703]]],[[[18.57853,55.25302],[19.64312,54.45423],[19.8038,54.44203],[20.63871,54.3706],[21.41123,54.32395],[22.79705,54.36264],[22.7253,54.41732],[22.70208,54.45312],[22.67788,54.532],[22.71293,54.56454],[22.68021,54.58486],[22.7522,54.63525],[22.74225,54.64339],[22.75467,54.6483],[22.73397,54.66604],[22.73631,54.72952],[22.87317,54.79492],[22.85083,54.88711],[22.76422,54.92521],[22.68723,54.9811],[22.65451,54.97037],[22.60075,55.01863],[22.58907,55.07085],[22.47688,55.04408],[22.31562,55.0655],[22.14267,55.05345],[22.11697,55.02131],[22.06087,55.02935],[22.02582,55.05078],[22.03984,55.07888],[21.99543,55.08691],[21.96505,55.07353],[21.91291,55.08215],[21.87807,55.09413],[21.85521,55.09493],[21.64954,55.1791],[21.57414,55.1991],[21.51095,55.18507],[21.46766,55.21115],[21.38892,55.29241],[21.35465,55.28427],[21.26425,55.24456],[20.95181,55.27994],[20.60454,55.40986],[18.57853,55.25302]]],[[[26.32936,60.00121],[26.90044,59.63819],[27.85643,59.58538],[28.04187,59.47017],[28.19061,59.39962],[28.20847,59.38382],[28.20302,59.37468],[28.21134,59.36941],[28.19598,59.35953],[28.18988,59.3457],[28.12808,59.29253],[28.00689,59.28351],[27.90911,59.24353],[27.88948,59.1856],[27.80794,59.12826],[27.76605,59.03295],[27.74476,59.02792],[27.74429,58.98351],[27.36366,58.78381],[27.55489,58.39525],[27.48541,58.22615],[27.62393,58.09462],[27.67282,57.92627],[27.81841,57.89244],[27.78526,57.83963],[27.56689,57.83356],[27.50171,57.78842],[27.52615,57.72843],[27.3746,57.66834],[27.40393,57.62125],[27.31919,57.57672],[27.34698,57.52242],[27.56832,57.53728],[27.52453,57.42826],[27.87265,57.29314],[27.64537,56.83188],[27.86922,56.88031],[28.04768,56.59004],[28.13526,56.57989],[28.10069,56.524],[28.19057,56.44637],[28.16599,56.37806],[28.24447,56.28224],[28.15217,56.16964],[28.30571,56.06035],[28.36888,56.05805],[28.37987,56.11399],[28.43068,56.09407],[28.5529,56.11705],[28.68337,56.10173],[28.63668,56.07262],[28.73418,55.97131],[29.08299,56.03427],[29.21717,55.98971],[29.44692,55.95978],[29.3604,55.75862],[29.51283,55.70294],[29.61446,55.77716],[29.80672,55.79569],[29.97975,55.87281],[30.10923,55.8306],[30.25428,55.87319],[30.30987,55.83592],[30.52448,55.79857],[30.48826,55.77502],[30.63344,55.73079],[30.67464,55.64176],[30.72957,55.66268],[30.7845,55.58514],[30.86003,55.63169],[30.93419,55.6185],[30.95204,55.50667],[30.90763,55.47642],[30.93144,55.3914],[30.8257,55.3313],[30.81946,55.27931],[30.87944,55.28223],[30.97369,55.17134],[31.02071,55.06167],[31.00972,55.02783],[30.94243,55.03964],[30.9081,55.02232],[30.95754,54.98609],[30.93144,54.9585],[30.81759,54.94064],[30.8264,54.90062],[30.75165,54.80699],[30.95479,54.74346],[30.97127,54.71967],[31.0262,54.70698],[30.98226,54.68872],[30.99187,54.67046],[31.19339,54.66947],[31.21399,54.63113],[31.08543,54.50361],[31.22945,54.46585],[31.27017,54.37755],[31.32442,54.34044],[31.30791,54.25315],[31.57002,54.14535],[31.89599,54.0837],[31.88744,54.03653],[31.85019,53.91801],[31.77028,53.80015],[31.89137,53.78099],[32.12621,53.81586],[32.36663,53.7166],[32.45717,53.74039],[32.50112,53.68594],[32.40499,53.6656],[32.47777,53.5548],[32.74968,53.45597],[32.73257,53.33494],[32.51725,53.28431],[32.40773,53.18856],[32.15368,53.07594],[31.82373,53.10042],[31.787,53.18033],[31.62496,53.22886],[31.56316,53.19432],[31.40523,53.21406],[31.36403,53.13504],[31.3915,53.09712],[31.33519,53.08805],[31.32283,53.04101],[31.24147,53.031],[31.35667,52.97854],[31.42982,52.89171],[31.592,52.79011],[31.57277,52.71613],[31.50406,52.69707],[31.63869,52.55361],[31.56316,52.51518],[31.61397,52.48843],[31.62084,52.33849],[31.57971,52.32146],[31.70735,52.26711],[31.6895,52.1973],[31.77877,52.18636],[31.7822,52.11406],[31.81722,52.09955],[31.85863,52.11251],[31.96141,52.08015],[31.92159,52.05144],[32.08813,52.03319],[32.23331,52.08085],[32.2777,52.10266],[32.34044,52.1434],[32.33083,52.23685],[32.38988,52.24946],[32.3528,52.32842],[32.49103,52.31729],[32.54751,52.32442],[32.71247,52.25428],[32.85405,52.27888],[32.89937,52.2461],[33.1636,52.35861],[33.51323,52.35779],[33.48027,52.31499],[33.55718,52.30324],[33.78789,52.37204],[34.05239,52.20132],[34.11199,52.14087],[34.09297,52.02302],[34.28352,51.89068],[34.41136,51.82793],[34.4399,51.74828],[34.42308,51.72367],[34.07765,51.67065],[34.17599,51.63253],[34.30562,51.5205],[34.22048,51.4187],[34.33446,51.363],[34.23009,51.26429],[34.31661,51.23936],[34.38802,51.2746],[34.49329,51.24343],[34.6613,51.25053],[34.6874,51.18],[34.82472,51.17483],[34.97304,51.2342],[35.14058,51.23162],[35.12685,51.16191],[35.20465,51.04576],[35.31774,51.08434],[35.40837,51.04119],[35.32598,50.94524],[35.39307,50.92145],[35.41367,50.80227],[35.47828,50.77283],[35.45888,50.68966],[35.49047,50.66099],[35.39091,50.65229],[35.47296,50.49017],[35.58003,50.45117],[35.61711,50.35707],[35.73659,50.35489],[35.82057,50.41781],[35.88932,50.4387],[35.91464,50.43498],[36.06893,50.45205],[36.16699,50.43421],[36.30101,50.29088],[36.4504,50.31828],[36.57949,50.27518],[36.5667,50.24451],[36.64824,50.21766],[36.69377,50.26982],[36.92882,50.35077],[37.12949,50.34578],[37.336,50.43586],[37.46664,50.44712],[37.47243,50.36277],[37.62486,50.29966],[37.62879,50.24481],[37.61113,50.21976],[37.63778,50.18107],[37.76498,50.07829],[37.90432,50.05075],[37.96394,49.9839],[38.04874,49.9205],[38.21388,49.97683],[38.17954,50.07873],[38.32524,50.08866],[38.35408,50.00664],[38.65688,49.97176],[38.68677,50.00904],[38.73311,49.90238],[38.90477,49.86787],[38.9391,49.79524],[39.1808,49.88911],[39.27968,49.75976],[39.44496,49.76067],[39.59142,49.73758],[39.65047,49.61761],[39.84548,49.56064],[40.13249,49.61672],[40.16683,49.56865],[40.03636,49.52321],[40.03087,49.45452],[40.11383,49.38656],[40.14953,49.37656],[40.19545,49.3429],[40.22129,49.25391],[40.01988,49.1761],[39.93437,49.05709],[39.6836,49.05121],[39.6683,48.99454],[39.71353,48.98959],[39.72649,48.9754],[39.74874,48.98675],[39.78368,48.91596],[39.98967,48.86901],[40.03349,48.92092],[40.08168,48.87443],[39.97182,48.79398],[39.80037,48.84054],[39.72587,48.73468],[39.71765,48.68673],[39.67226,48.59368],[39.8002,48.58819],[39.8517,48.56627],[39.86196,48.46633],[39.88794,48.44226],[39.94285,48.38282],[39.93221,48.36468],[39.94594,48.35225],[39.84136,48.33321],[39.84273,48.30947],[39.90041,48.3049],[39.91161,48.28987],[39.91294,48.26971],[39.92611,48.27165],[39.93646,48.2901],[39.94856,48.29452],[39.96594,48.30617],[39.97062,48.30734],[39.97325,48.31399],[39.99229,48.31693],[40.00752,48.22445],[39.94847,48.22811],[39.83724,48.06501],[39.88672,48.04314],[39.77462,48.03964],[39.82003,47.95946],[39.74819,47.82767],[39.39113,47.86408],[38.87838,47.87329],[38.79628,47.81109],[38.77229,47.68457],[38.67247,47.69997],[38.62827,47.66238],[38.45695,47.61773],[38.35052,47.61599],[38.35086,47.57664],[38.2864,47.53499],[38.30108,47.47417],[38.28954,47.39255],[38.22074,47.30542],[38.33074,47.30508],[38.32112,47.2585],[38.23482,47.23145],[38.22955,47.12069],[38.3384,46.98085],[35.23066,45.79231],[34.96015,45.75634],[34.79905,45.81009],[34.80118,45.89282],[34.56575,45.99728],[34.50256,45.9367],[34.44155,45.95995],[34.36177,46.05994],[34.204,46.06017],[34.07311,46.11769],[33.9971,46.10671],[33.82255,46.21028],[33.74047,46.18555],[33.646,46.23028],[33.61517,46.22615],[33.63854,46.14147],[33.61467,46.13561],[33.54017,46.0123],[32.14262,45.66011],[33.048,44.55373],[33.72184,44.24476],[36.40186,44.9675],[39.8664,43.20124],[40.0078,43.38551],[40.00853,43.40578],[40.01552,43.42025],[40.01007,43.42411],[40.03312,43.44262],[40.04445,43.47776],[40.10657,43.57344],[40.65957,43.56212],[41.64935,43.22331],[42.13085,43.20326],[42.40563,43.23226],[42.66667,43.13917],[42.75889,43.19651],[43.03322,43.08883],[43.0419,43.02413],[43.36538,42.90656],[43.81453,42.74297],[43.73119,42.62043],[43.95517,42.55396],[44.22978,42.64904],[44.54202,42.75699],[44.70002,42.74679],[44.80941,42.61277],[44.88754,42.74934],[45.08266,42.71749],[45.15318,42.70598],[45.36501,42.55268],[45.78692,42.48358],[45.61676,42.20768],[46.42738,41.91323],[46.5332,41.87389],[46.58924,41.80547],[46.75269,41.8623],[46.8134,41.76252],[47.00955,41.63583],[46.99554,41.59743],[47.03757,41.55434],[47.10762,41.59044],[47.34579,41.27884],[47.49004,41.26366],[47.54504,41.20275],[47.62288,41.22969],[47.75831,41.19455],[47.87973,41.21798],[48.07587,41.49957],[48.22064,41.51472],[48.2878,41.56221],[48.40277,41.60441],[48.42301,41.65444],[48.55078,41.77917],[48.5867,41.84306],[48.80971,41.95365],[47.51959,44.80109],[49.88945,46.04554],[49.32259,46.26944],[49.16518,46.38542],[48.54988,46.56267],[48.51142,46.69268],[49.01136,46.72716],[48.52326,47.4102],[48.45173,47.40818],[48.15348,47.74545],[47.64973,47.76559],[47.41689,47.83687],[47.38731,47.68176],[47.12107,47.83687],[47.11516,48.27188],[46.49011,48.43019],[46.78392,48.95352],[46.91104,48.99715],[47.01458,49.07085],[47.04416,49.17152],[46.98795,49.23531],[46.78398,49.34026],[46.9078,49.86707],[47.18319,49.93721],[47.34589,50.09308],[47.30448,50.30894],[47.58551,50.47867],[48.10044,50.09242],[48.24519,49.86099],[48.42564,49.82283],[48.68352,49.89546],[48.88572,50.00597],[48.61244,50.63291],[48.86936,50.61589],[49.12673,50.78639],[49.41959,50.85927],[49.39001,51.09396],[49.76866,51.11067],[49.97277,51.2405],[50.26859,51.28677],[50.59695,51.61859],[51.26254,51.68466],[51.28829,51.48886],[51.77431,51.49536],[51.8246,51.67916],[52.36119,51.74161],[52.55207,51.47389],[53.46165,51.49445],[53.69299,51.23466],[54.12248,51.11542],[54.20516,50.96923],[54.52308,50.83803],[54.41894,50.61214],[54.55797,50.52006],[54.71476,50.61214],[54.56685,51.01958],[54.72067,51.03261],[55.67774,50.54508],[56.11398,50.7471],[56.17906,50.93204],[57.17302,51.11253],[57.44221,50.88354],[57.74986,50.93017],[57.75578,51.13852],[58.13037,51.07182],[58.25946,51.14489],[58.61824,51.02455],[58.87974,50.70852],[59.48928,50.64216],[59.51886,50.49937],[59.81172,50.54451],[60.01288,50.8163],[60.17246,50.84041],[60.31914,50.67705],[60.81833,50.6629],[61.4431,50.80679],[61.56889,51.23679],[61.6813,51.25716],[61.55114,51.32746],[61.50677,51.40687],[60.95655,51.48615],[60.92401,51.61124],[60.5424,51.61675],[60.36787,51.66815],[60.51921,51.7929],[60.07392,51.87225],[59.99908,51.99397],[60.19925,51.99173],[60.51303,52.1575],[60.72581,52.15538],[60.78201,52.22067],[61.06853,52.3454],[60.98021,52.50068],[60.84709,52.52228],[60.84118,52.63912],[60.71693,52.66245],[60.71989,52.75923],[61.05842,52.92217],[61.24877,53.03584],[62.0422,52.96105],[62.12799,52.99133],[62.14574,53.09626],[61.67381,53.24138],[61.18629,53.2882],[61.15436,53.40809],[61.27864,53.51765],[61.37957,53.45887],[61.57185,53.50112],[61.55811,53.58048],[60.90626,53.62937],[61.22574,53.80268],[61.14283,53.90063],[60.99796,53.93699],[61.26863,53.92797],[61.3706,54.08464],[61.47603,54.08048],[61.56941,53.95703],[61.65318,54.02445],[62.03913,53.94768],[62.0046,54.03903],[62.37075,54.04386],[62.45931,53.90737],[62.56876,53.94047],[62.58651,54.05871],[63.0622,54.10651],[63.79486,54.27645],[63.91224,54.20013],[64.02715,54.22679],[63.97686,54.29763],[64.83032,54.37855],[64.97216,54.4212],[65.11033,54.33028],[65.24663,54.35721],[65.20174,54.55216],[67.70187,54.87818],[67.90099,54.9784],[68.22235,54.96263],[68.26661,55.09226],[68.19206,55.18823],[68.63159,55.21237],[68.73287,55.35472],[68.91654,55.32836],[68.93337,55.42706],[69.34224,55.36344],[69.72198,55.34906],[70.18615,55.14356],[70.46012,55.27598],[70.80482,55.28302],[70.99056,55.08622],[70.98197,54.88895],[71.09149,54.71004],[71.28822,54.65675],[71.18385,54.57803],[71.2017,54.32493],[71.01631,54.3045],[71.13098,54.12201],[71.76132,54.13911],[71.76784,54.23112],[72.17948,54.1389],[72.24575,54.37435],[72.43415,53.92685],[72.71026,54.1161],[73.28086,53.9383],[73.74778,54.07194],[73.69491,53.85698],[73.36807,53.78807],[73.23898,53.54887],[73.43879,53.43612],[73.99806,53.64097],[74.38945,53.45964],[74.71115,53.84402],[75.07405,53.80831],[75.43398,53.98652],[75.3668,54.07439],[76.93622,54.46385],[76.82266,54.1798],[76.44076,54.16017],[76.54243,53.99329],[77.90383,53.29807],[79.11255,52.01171],[80.07316,50.74427],[80.06183,50.85039],[80.47691,50.96815],[80.44819,51.20855],[80.80318,51.28262],[81.16999,51.15662],[81.06091,50.94833],[81.41248,50.97524],[81.47581,50.75177],[81.92796,50.79237],[82.57581,50.75258],[82.77168,50.91255],[82.99381,50.8974],[83.14607,51.00796],[83.413,51.00079],[83.8442,50.87375],[84.08386,50.64249],[84.29706,50.25115],[84.99198,50.06793],[85.24047,49.60239],[86.22413,49.49756],[86.6732,49.80874],[86.79056,49.74787],[86.60591,49.5968],[86.84881,49.51852],[87.03071,49.25142],[87.31465,49.23603],[87.28386,49.11626],[87.478,49.07403],[87.48983,49.13794],[87.81333,49.17354],[87.98977,49.18147],[88.15543,49.30314],[88.17223,49.46934],[88.42449,49.48821],[88.82499,49.44808],[89.70687,49.72535],[89.59711,49.90851],[91.86048,50.73734],[92.07173,50.69585],[92.44714,50.78762],[93.01109,50.79001],[92.99595,50.63183],[94.30823,50.57498],[94.39258,50.22193],[94.49477,50.17832],[94.6121,50.04239],[94.97166,50.04725],[95.02465,49.96941],[95.74757,49.97915],[95.80056,50.04239],[96.97388,49.88413],[97.24639,49.74737],[97.56811,49.84265],[97.56432,49.92801],[97.76871,49.99861],[97.85197,49.91339],[98.29481,50.33561],[98.31373,50.4996],[98.06393,50.61262],[97.9693,50.78044],[98.01472,50.86652],[97.83305,51.00248],[98.05257,51.46696],[98.22053,51.46579],[98.33222,51.71832],[98.74142,51.8637],[98.87768,52.14563],[99.27888,51.96876],[99.75578,51.90108],[99.89203,51.74903],[100.61116,51.73028],[101.39085,51.45753],[101.5044,51.50467],[102.14032,51.35566],[102.32194,50.67982],[102.71178,50.38873],[103.70343,50.13952],[105.32528,50.4648],[106.05562,50.40582],[106.07865,50.33474],[106.47156,50.31909],[106.49628,50.32436],[106.51122,50.34408],[106.80326,50.30177],[107.00007,50.1977],[107.1174,50.04239],[107.36407,49.97612],[107.77379,49.94578],[107.96116,49.93191],[107.95387,49.66659],[108.27937,49.53167],[108.53969,49.32325],[109.18017,49.34709],[109.51325,49.22859],[110.24373,49.16676],[110.39891,49.25083],[110.64493,49.1816],[113.02647,49.60772],[113.20216,49.83356],[114.325,50.28098],[114.9703,50.19254],[115.26068,49.97367],[115.73602,49.87688],[116.22402,50.04477],[116.62502,49.92919],[116.71193,49.83813],[117.07142,49.68482],[117.27597,49.62544],[117.48208,49.62324],[117.82343,49.52696],[118.61623,49.93809],[119.11003,50.00276],[119.27996,50.13348],[119.38598,50.35162],[119.13553,50.37412],[120.10963,51.671],[120.65907,51.93544],[120.77337,52.20805],[120.61346,52.32447],[120.71673,52.54099],[120.46454,52.63811],[120.04049,52.58773],[120.0451,52.7359],[120.85633,53.28499],[121.48681,53.33169],[122.35063,53.49565],[122.85966,53.47395],[123.26989,53.54843],[123.86158,53.49391],[124.46078,53.21881],[125.17522,53.20225],[125.6131,53.07229],[125.96099,52.76995],[126.08562,52.79923],[126.03378,52.58052],[126.56524,52.12042],[126.44606,51.98254],[126.68349,51.70607],[126.90369,51.3238],[126.93135,51.0841],[127.14586,50.91152],[127.28165,50.72075],[127.36335,50.58306],[127.28765,50.46585],[127.36009,50.43787],[127.37384,50.28393],[127.60515,50.23503],[127.49299,50.01251],[127.53516,49.84306],[127.83476,49.5748],[128.72896,49.58676],[129.11153,49.36813],[129.23232,49.40353],[129.35317,49.3481],[129.40398,49.44194],[129.50685,49.42398],[129.67598,49.29596],[129.85416,49.11067],[130.2355,48.86741],[130.43232,48.90844],[130.61358,48.88862],[130.66946,48.88251],[130.52147,48.61745],[130.84462,48.30942],[130.65103,48.10052],[130.90915,47.90623],[130.95985,47.6957],[131.09871,47.6852],[131.2635,47.73325],[131.90448,47.68011],[132.57309,47.71741],[132.66989,47.96491],[134.49516,48.42884],[134.76619,48.36286],[134.67098,48.1564],[134.57221,48.006],[134.7671,47.72051],[134.50252,47.44666],[134.31644,47.43737],[134.20074,47.34301],[134.14375,47.26222],[134.24777,47.12224],[134.11388,47.06591],[134.02255,46.77937],[134.01466,46.66663],[133.91647,46.59638],[133.84104,46.46681],[133.94977,46.40117],[133.86154,46.34526],[133.91441,46.26273],[133.67065,46.14416],[133.73897,46.0637],[133.67569,45.9759],[133.67013,45.94136],[133.60645,45.9379],[133.61228,45.90171],[133.57915,45.8655],[133.52251,45.89849],[133.48457,45.86203],[133.43616,45.71049],[133.47976,45.67212],[133.41083,45.57723],[133.19652,45.51284],[133.09279,45.25693],[133.12293,45.1332],[132.96373,45.0212],[132.83978,45.05916],[131.99417,45.2567],[131.86903,45.33636],[131.76532,45.22609],[131.66852,45.2196],[131.68466,45.12374],[131.48415,44.99513],[130.95639,44.85154],[131.10603,44.70673],[131.30365,44.04262],[131.25484,44.03131],[131.23583,43.96085],[131.26176,43.94011],[131.20525,43.82164],[131.22962,43.6552],[131.19492,43.53047],[131.30739,43.47335],[131.30324,43.39498],[131.19031,43.21385],[131.20414,43.13654],[131.10274,43.04734],[131.135,42.94114],[131.02668,42.91246],[131.02438,42.86518],[130.66524,42.84753],[130.44361,42.76205],[130.40213,42.70788],[130.56576,42.68925],[130.62107,42.58413],[130.55143,42.52158],[130.56835,42.43281],[130.60805,42.4317],[130.64181,42.41422],[130.66367,42.38024],[130.65022,42.32281],[131.95041,41.5445],[140.9182,45.92937],[145.82343,44.571],[145.23667,43.76813],[145.76215,43.50342],[145.97944,43.07828],[168.4495,54.38739],[174.61724,61.60651],[180,62.16094],[180,71.74907],[163.13492,69.79589],[158.59861,77.40868],[116.38911,75.59041],[96.72358,81.47278],[58.40327,82.08215],[35.20015,80.12611],[62.13862,79.22076],[48.75604,70.23627],[46.97559,68.59708],[38.45276,68.81889],[31.59909,70.16571],[30.84095,69.80584],[30.95011,69.54699],[30.52662,69.54699],[30.16363,69.65244],[29.97205,69.41623],[29.27631,69.2811],[29.26623,69.13794],[29.0444,69.0119],[28.91738,69.04774],[28.45957,68.91417],[28.78224,68.86696],[28.43941,68.53366],[28.62982,68.19816],[29.34179,68.06655],[29.66955,67.79872],[30.02041,67.67523],[29.91155,67.51507],[28.9839,66.94139],[29.30622,66.66539],[29.91155,66.13863],[30.12517,65.7552],[29.61914,65.23791],[29.8813,65.22101],[29.84096,65.1109],[29.61914,65.05993],[29.68972,64.80789],[30.05271,64.79072],[30.12329,64.64862],[29.99267,64.58058],[30.06279,64.35782],[30.4762,64.25728],[30.55687,64.09036],[30.25437,63.83364],[29.98213,63.75795],[30.49637,63.46666],[31.23244,63.22239],[31.29294,63.09035],[31.58535,62.91642],[31.38369,62.66284],[31.1634,62.45585],[30.42354,62.02281],[29.64454,61.52023],[29.30666,61.33001],[29.01829,61.17448],[28.82228,61.12119],[28.54042,60.95561],[27.87282,60.60441],[27.77755,60.51759],[27.71177,60.3893],[27.44953,60.22766],[26.32936,60.00121]]]]}},{"type":"Feature","properties":{"id":"TJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[67.3546,39.22959],[67.67833,39.14479],[67.68915,39.00775],[68.09704,39.02589],[68.19743,38.85985],[68.06948,38.82115],[68.12877,38.73677],[68.05598,38.71641],[68.0807,38.64136],[68.05873,38.56087],[68.11677,38.46985],[68.06098,38.39629],[68.0809,38.38768],[68.13549,38.41082],[68.40637,38.19407],[68.37272,38.08768],[68.30389,38.02213],[68.2759,37.91115],[68.14819,37.93445],[67.81566,37.43107],[67.8474,37.31594],[67.78329,37.1834],[67.79113,37.08311],[67.87917,37.0591],[68.02194,36.91923],[68.18542,37.02074],[68.27605,37.00977],[68.29253,37.10621],[68.41201,37.10402],[68.41888,37.13906],[68.6194,37.19915],[68.69699,37.30396],[68.8278,37.24221],[68.82093,37.32839],[68.91189,37.26704],[68.92719,37.28074],[68.88168,37.33368],[68.99156,37.31406],[69.03274,37.25174],[69.25249,37.09393],[69.31789,37.11693],[69.37145,37.16606],[69.39874,37.16756],[69.45522,37.23497],[69.37007,37.40548],[69.45367,37.48957],[69.51888,37.5844],[69.82618,37.57233],[69.85476,37.60668],[69.93362,37.61378],[69.9487,37.59148],[69.9545,37.56662],[70.15015,37.52519],[70.28243,37.66706],[70.27694,37.81258],[70.18478,37.84639],[70.17465,37.93478],[70.26958,37.93885],[70.29653,37.98689],[70.32889,37.99853],[70.36683,38.04231],[70.48999,38.12004],[70.52973,38.20277],[70.54673,38.24541],[70.60389,38.28202],[70.61239,38.34862],[70.64908,38.34885],[70.69461,38.37056],[70.67603,38.38852],[70.67438,38.40597],[70.69367,38.41832],[70.72485,38.4131],[70.75455,38.4252],[70.77132,38.45548],[70.78581,38.45502],[70.78702,38.45031],[70.79766,38.44944],[70.80521,38.44447],[70.81697,38.44507],[70.82538,38.45394],[70.84376,38.44688],[70.88719,38.46826],[70.92451,38.43039],[70.98936,38.49011],[71.0315,38.45231],[71.05609,38.39959],[71.09484,38.42414],[71.10592,38.42077],[71.10957,38.40671],[71.1451,38.40106],[71.21415,38.32872],[71.26041,38.31135],[71.33216,38.30549],[71.33272,38.27427],[71.37362,38.25691],[71.36461,38.1963],[71.37564,38.1602],[71.34997,38.1494],[71.32942,38.11146],[71.30354,38.04359],[71.28354,38.0417],[71.29427,38.0178],[71.27642,38.00603],[71.26848,37.99156],[71.27457,37.96653],[71.2505,37.92724],[71.2809,37.91995],[71.296,37.93403],[71.32871,37.88564],[71.51565,37.95349],[71.59257,37.92294],[71.58614,37.89551],[71.59832,37.87404],[71.58468,37.84774],[71.59369,37.81432],[71.59255,37.79956],[71.55481,37.78509],[71.54324,37.77104],[71.53052,37.76521],[71.55241,37.73515],[71.54236,37.69407],[71.53138,37.67835],[71.5267,37.63313],[71.51957,37.62035],[71.51168,37.61484],[71.4952,37.53926],[71.50434,37.52701],[71.50616,37.50733],[71.52648,37.47983],[71.49612,37.4279],[71.4973,37.40715],[71.47945,37.40664],[71.47451,37.38625],[71.48906,37.38055],[71.4949,37.36961],[71.48701,37.33312],[71.49821,37.31975],[71.50725,37.31563],[71.48454,37.26017],[71.48339,37.23937],[71.47386,37.2269],[71.4555,37.21418],[71.4494,37.18137],[71.44127,37.11856],[71.43097,37.05855],[71.45578,37.03094],[71.46923,36.99925],[71.46683,36.95222],[71.51502,36.89128],[71.57195,36.74943],[71.67034,36.67268],[71.83977,36.67888],[72.04215,36.82],[72.33673,36.98596],[72.54095,37.00007],[72.66381,37.02014],[72.80862,37.22513],[73.06884,37.31729],[73.29633,37.46495],[73.77197,37.4417],[73.76647,37.33913],[73.61129,37.27469],[73.64974,37.23643],[73.82552,37.22659],[73.8564,37.26158],[74.20308,37.34208],[74.23339,37.41116],[74.56161,37.37734],[74.68383,37.3948],[74.8294,37.3435],[74.88887,37.23275],[75.12328,37.31839],[75.13907,37.42124],[75.06011,37.52779],[74.94338,37.55501],[74.8912,37.67576],[75.00935,37.77486],[74.92416,37.83428],[74.9063,38.03033],[74.82665,38.07359],[74.80331,38.19889],[74.69894,38.22155],[74.69619,38.42947],[74.51217,38.47034],[74.17022,38.65504],[73.97933,38.52945],[73.79806,38.61106],[73.80656,38.66449],[73.7033,38.84782],[73.7445,38.93867],[73.82964,38.91517],[73.81728,39.04007],[73.75823,39.023],[73.60638,39.24534],[73.54572,39.27567],[73.55396,39.3543],[73.5004,39.38402],[73.59241,39.40843],[73.59831,39.46425],[73.45096,39.46677],[73.31912,39.38615],[73.18454,39.35536],[72.85934,39.35116],[72.62027,39.39696],[72.33173,39.33093],[72.23834,39.17248],[72.17242,39.2661],[72.09689,39.26823],[72.04059,39.36704],[71.90601,39.27674],[71.79202,39.27355],[71.7522,39.32031],[71.80164,39.40631],[71.76816,39.45456],[71.62688,39.44056],[71.5517,39.45722],[71.55856,39.57588],[71.49814,39.61397],[71.08752,39.50704],[71.06418,39.41586],[70.7854,39.38933],[70.64087,39.58792],[70.44757,39.60128],[70.2869,39.53141],[70.11111,39.58223],[69.87491,39.53882],[69.68677,39.59281],[69.37454,39.52364],[69.26938,39.8127],[69.35649,40.01994],[69.43134,39.98431],[69.42938,39.92777],[69.49762,39.928],[69.53916,39.93797],[69.5057,40.03277],[69.53855,40.0887],[69.5365,40.11674],[69.55555,40.12296],[69.5753,40.10302],[69.65057,40.12251],[69.67615,40.12238],[69.68224,40.11464],[69.68627,40.11529],[69.68593,40.12625],[69.69958,40.1315],[69.78378,40.17887],[69.88523,40.20182],[69.99514,40.23406],[70.53308,40.033],[70.60964,40.03734],[70.59059,39.99954],[70.53797,39.97363],[70.56037,39.95758],[70.59419,39.96824],[70.61779,39.99915],[70.62629,40.05087],[70.63934,40.10065],[70.79572,40.12599],[70.80413,40.1807],[70.84559,40.16451],[70.8843,40.17657],[70.89228,40.21892],[70.8607,40.217],[70.63144,40.17638],[70.57514,40.26538],[70.5808,40.33803],[70.37511,40.38605],[70.32626,40.45174],[70.49871,40.52503],[70.8043,40.72176],[70.45251,41.04438],[70.38028,41.02014],[70.36655,40.90296],[69.69434,40.62615],[69.59441,40.70181],[69.53021,40.77621],[69.36235,40.79041],[69.35583,40.72644],[69.32834,40.70233],[69.34759,40.61486],[69.3455,40.57988],[69.2643,40.57506],[69.21063,40.54469],[69.27066,40.49274],[69.28525,40.41894],[69.30774,40.36102],[69.33794,40.34819],[69.32833,40.29794],[69.30808,40.2821],[69.24817,40.30357],[69.25592,40.26354],[69.30104,40.24502],[69.30448,40.18774],[69.20305,40.21427],[69.16511,40.21244],[69.03345,40.22895],[68.85832,40.20885],[68.84357,40.18604],[68.79793,40.17179],[68.77956,40.20365],[68.53219,40.14187],[68.52771,40.11676],[68.62796,40.07789],[69.01523,40.15771],[69.01935,40.11466],[68.96579,40.06949],[68.89526,40.04982],[68.84977,40.05731],[68.93886,39.92026],[68.88805,39.86679],[68.69716,39.84927],[68.63536,39.85731],[68.61972,39.68905],[68.52807,39.53304],[68.12053,39.56317],[67.70992,39.66156],[67.62889,39.60234],[67.44899,39.57799],[67.46547,39.53564],[67.3915,39.52364],[67.46274,39.46813],[67.46961,39.31663],[67.36522,39.31287],[67.3546,39.22959]]],[[[70.52631,39.86989],[70.54998,39.85137],[70.59667,39.83542],[70.63105,39.77923],[70.74189,39.86319],[70.53651,39.89155],[70.52631,39.86989]]],[[[70.54223,40.98787],[70.57501,40.98941],[70.6721,40.90555],[70.68112,40.90612],[70.6158,40.97661],[70.56077,41.00642],[70.54223,40.98787]]]]}},{"type":"Feature","properties":{"id":"US"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-179.55295,50.81807],[-133.92876,54.62289],[-130.61931,54.70835],[-130.65082,54.7709],[-130.53591,54.80215],[-130.27203,54.97174],[-130.18765,55.07744],[-130.08035,55.21556],[-129.97513,55.28029],[-130.15373,55.74895],[-130.00857,55.91344],[-130.00093,56.00325],[-130.10173,56.12178],[-130.33965,56.10849],[-130.77769,56.36185],[-131.8271,56.62247],[-133.38523,58.42773],[-133.84645,58.73543],[-134.27175,58.8634],[-134.49912,59.12103],[-134.7047,59.2458],[-135.00267,59.28745],[-135.03069,59.56208],[-135.48007,59.79937],[-136.31566,59.59083],[-136.22381,59.55526],[-136.33727,59.44466],[-136.47323,59.46617],[-136.52365,59.16752],[-136.82619,59.16198],[-137.4925,58.89415],[-137.60623,59.24465],[-138.62145,59.76431],[-138.71149,59.90728],[-139.05365,59.99655],[-139.20603,60.08896],[-139.05831,60.35205],[-139.68991,60.33693],[-139.98024,60.18027],[-140.45648,60.30919],[-140.5227,60.22077],[-141.00116,60.30648],[-140.97259,70.50112],[-140.97259,70.50112],[-156.4893,71.58054],[-168.65315,71.12923],[-168.9812,65.50181],[-172.76104,63.77445],[-179.55295,57.62081],[-179.55295,50.81807]]],[[[-179.2458,29.18869],[-179.23433,18.15359],[-154.13058,18.17333],[-154.14205,29.20682],[-176.83456,29.19028],[-177.8563,29.18961],[-179.2458,29.18869]]],[[[-177.43928,1.65656],[-177.43039,-1.43294],[-175.33482,-1.40631],[-175.33167,1.67574],[-177.43928,1.65656]]],[[[-174.18596,-12.48057],[-171.14953,-12.4725],[-171.14262,-14.93704],[-167.73854,-14.92809],[-167.75195,-10.12005],[-174.17993,-10.13616],[-174.18596,-12.48057]]],[[[-169.97341,16.32808],[-169.02347,16.32808],[-169.02347,17.13856],[-169.97341,17.13856],[-169.97341,16.32808]]],[[[-163.24994,7.12322],[-163.24478,5.24198],[-161.06795,5.2462],[-161.0731,7.1291],[-163.24994,7.12322]]],[[[-161.05669,1.11722],[-161.04969,-1.36251],[-158.62058,-1.35506],[-158.62734,1.1296],[-161.05669,1.11722]]],[[[-125.69978,42.00605],[-121.26019,33.77126],[-118.48109,32.5991],[-117.12426,32.53431],[-116.87852,32.55531],[-116.58627,32.57969],[-115.88053,32.63624],[-115.49377,32.66517],[-114.71871,32.71894],[-114.76736,32.64094],[-114.80584,32.62028],[-114.81141,32.55543],[-114.79524,32.55731],[-114.82011,32.49609],[-114.63109,32.43959],[-112.34627,31.73488],[-111.07523,31.33232],[-109.56227,31.33402],[-109.05235,31.3333],[-108.20979,31.33316],[-108.20899,31.78534],[-106.529,31.784],[-106.52266,31.77509],[-106.51251,31.76922],[-106.50962,31.76155],[-106.50111,31.75714],[-106.48815,31.74769],[-106.47206,31.7509],[-106.46726,31.75998],[-106.45244,31.76523],[-106.43419,31.75478],[-106.41773,31.75196],[-106.38003,31.73151],[-106.3718,31.71165],[-106.34864,31.69663],[-106.33419,31.66303],[-106.30319,31.62214],[-106.30122,31.60989],[-106.27924,31.56061],[-106.24612,31.54193],[-106.23711,31.51262],[-106.21204,31.46981],[-106.08158,31.39907],[-106.00363,31.39181],[-105.78426,31.19518],[-104.87514,30.53003],[-104.67567,30.1469],[-104.5171,29.64671],[-104.3969,29.57105],[-104.39363,29.55396],[-104.37752,29.54255],[-103.15787,28.93865],[-102.60596,29.8192],[-101.47277,29.7744],[-101.05686,29.44738],[-101.01128,29.36947],[-100.96725,29.3477],[-100.94579,29.34523],[-100.94053,29.33399],[-100.87982,29.296],[-100.79696,29.24688],[-100.67294,29.09744],[-100.63689,28.90812],[-100.59809,28.88197],[-100.52313,28.75598],[-100.5075,28.74066],[-100.51222,28.70679],[-100.50029,28.66117],[-100.35255,28.48679],[-100.29247,28.27883],[-99.73972,27.69568],[-99.55409,27.61314],[-99.51478,27.55836],[-99.52955,27.49747],[-99.50208,27.50021],[-99.48045,27.49016],[-99.482,27.47128],[-99.49744,27.43746],[-99.53573,27.30926],[-99.43313,27.2096],[-99.44515,27.04032],[-99.26044,26.80936],[-99.08477,26.39849],[-99.03053,26.41249],[-99.00546,26.3925],[-98.82116,26.35465],[-98.66477,26.23984],[-98.60298,26.25462],[-98.49002,26.21335],[-98.44505,26.20627],[-98.34154,26.15058],[-98.31167,26.10781],[-98.28429,26.1055],[-98.24603,26.07191],[-97.97017,26.05232],[-97.95155,26.0625],[-97.86337,26.05948],[-97.64528,26.01544],[-97.51773,25.88671],[-97.50821,25.88911],[-97.49765,25.89934],[-97.49743,25.8866],[-97.45941,25.87841],[-97.4304,25.84516],[-97.37246,25.84373],[-97.35946,25.92189],[-97.27535,25.94592],[-97.13927,25.96583],[-96.92418,25.97377],[-93.77551,29.43998],[-88.93054,28.25639],[-88.37952,30.00457],[-87.51915,30.07055],[-83.18732,24.36791],[-82.02215,24.23074],[-80.16442,23.44484],[-79.36558,27.02964],[-81.34929,30.71298],[-80.49837,32.0326],[-77.99552,33.38485],[-74.86753,35.41538],[-75.79776,36.55091],[-75.16879,38.02735],[-74.98718,38.4507],[-73.81773,39.66512],[-71.6391,40.94332],[-71.10101,41.43444],[-69.97282,40.56828],[-69.42513,41.52748],[-70.04999,42.81005],[-67.16117,44.20069],[-66.93432,44.82597],[-66.96824,44.83078],[-66.98249,44.87071],[-66.96824,44.90965],[-67.0216,44.95333],[-67.15904,45.16312],[-67.23321,45.16882],[-67.26508,45.1902],[-67.28456,45.19153],[-67.28924,45.18799],[-67.29359,45.17737],[-67.29168,45.17229],[-67.30074,45.16588],[-67.29623,45.14751],[-67.34351,45.1246],[-67.40215,45.16097],[-67.48789,45.28207],[-67.42394,45.37969],[-67.50578,45.48971],[-67.42144,45.50584],[-67.42863,45.56872],[-67.42017,45.57415],[-67.45536,45.60851],[-67.6049,45.60725],[-67.80705,45.69528],[-67.80653,45.80022],[-67.75955,45.82748],[-67.80961,45.87531],[-67.75196,45.91814],[-67.78111,45.9392],[-67.79027,47.06731],[-67.86495,47.09981],[-67.93155,47.15995],[-67.96382,47.20172],[-68.14012,47.29972],[-68.16578,47.32422],[-68.23565,47.35464],[-68.32998,47.36028],[-68.37203,47.35126],[-68.38431,47.32567],[-68.37598,47.28668],[-68.44216,47.28307],[-68.50009,47.30088],[-68.55228,47.28243],[-68.58687,47.28272],[-68.60069,47.25063],[-68.62206,47.24142],[-68.69424,47.24148],[-68.79784,47.21665],[-68.89222,47.1807],[-69.05039,47.2456],[-69.05073,47.30076],[-69.05148,47.42012],[-69.22485,47.4596],[-69.99966,46.69543],[-70.05812,46.41768],[-70.18547,46.35357],[-70.29078,46.18832],[-70.23855,46.1453],[-70.31025,45.96424],[-70.24694,45.95138],[-70.25976,45.89675],[-70.41523,45.79497],[-70.38934,45.73215],[-70.59168,45.64987],[-70.72651,45.49771],[-70.62518,45.42286],[-70.65383,45.37592],[-70.78372,45.43269],[-70.80715,45.4143],[-70.81726,45.2219],[-70.89864,45.2398],[-70.9406,45.34341],[-71.08364,45.30623],[-71.14568,45.24128],[-71.19723,45.25438],[-71.22338,45.25184],[-71.29371,45.29996],[-71.37133,45.24624],[-71.44252,45.2361],[-71.40364,45.21382],[-71.42778,45.12624],[-71.48735,45.07784],[-71.50067,45.01357],[-72.08662,45.00571],[-72.52504,45.00826],[-72.69824,45.01566],[-73.35073,45.01056],[-74.32699,44.99029],[-74.66689,45.00646],[-74.8447,45.00606],[-74.99101,44.98051],[-75.01363,44.95608],[-75.2193,44.87821],[-75.41441,44.76614],[-75.76813,44.51537],[-75.8217,44.43176],[-75.95947,44.34463],[-76.00018,44.34896],[-76.16285,44.28262],[-76.1664,44.23051],[-76.244,44.19643],[-76.31222,44.19894],[-76.35324,44.13493],[-76.43859,44.09393],[-76.79706,43.63099],[-79.25796,43.54052],[-79.06921,43.26183],[-79.05512,43.25375],[-79.05178,43.17029],[-79.04652,43.16396],[-79.0427,43.13934],[-79.06881,43.12029],[-79.05782,43.11153],[-79.06276,43.09706],[-79.07636,43.07797],[-79.01055,43.06659],[-78.99941,43.05612],[-79.02424,43.01983],[-79.02074,42.98444],[-78.91213,42.93838],[-78.89518,42.84543],[-79.78216,42.57325],[-80.55605,42.3348],[-82.71775,41.66281],[-83.08506,41.89693],[-83.14962,42.04089],[-83.12724,42.2376],[-83.07871,42.31244],[-82.97944,42.33438],[-82.83152,42.37811],[-82.64242,42.55594],[-82.59645,42.5468],[-82.51617,42.61668],[-82.51063,42.66025],[-82.46613,42.76615],[-82.4826,42.8068],[-82.45331,42.93139],[-82.4253,42.95423],[-82.4146,42.97626],[-82.42469,42.992],[-81.54485,44.86396],[-83.59589,45.82131],[-83.43746,45.99749],[-83.57017,46.105],[-83.82299,46.12002],[-83.90453,46.05922],[-83.94231,46.05681],[-84.1096,46.23987],[-84.09756,46.25512],[-84.11615,46.2681],[-84.11254,46.32329],[-84.14875,46.40366],[-84.11196,46.50248],[-84.12953,46.53233],[-84.17723,46.52753],[-84.1945,46.54061],[-84.2264,46.53337],[-84.26351,46.49508],[-84.29893,46.49127],[-84.34174,46.50683],[-84.4402,46.49657],[-84.47607,46.45225],[-84.55635,46.45974],[-85.04547,46.88317],[-88.37033,48.30586],[-89.48837,48.01412],[-89.57972,48.00023],[-89.77248,48.02607],[-89.89974,47.98109],[-90.07418,48.11043],[-90.56312,48.09488],[-90.56444,48.12184],[-90.75045,48.09143],[-90.87588,48.2484],[-91.08016,48.18096],[-91.25025,48.08522],[-91.43248,48.04912],[-91.45829,48.07454],[-91.58025,48.04339],[-91.55649,48.10611],[-91.70451,48.11805],[-91.71231,48.18936],[-91.98929,48.25409],[-92.05339,48.35958],[-92.26662,48.35651],[-92.30939,48.31251],[-92.27167,48.25046],[-92.37185,48.22259],[-92.48147,48.36609],[-92.45588,48.40624],[-92.50712,48.44921],[-92.65606,48.43471],[-92.71323,48.46081],[-92.69927,48.49573],[-92.62747,48.50278],[-92.62836,48.52564],[-92.94973,48.60866],[-93.22713,48.64334],[-93.33946,48.62787],[-93.3712,48.60599],[-93.39758,48.60364],[-93.40693,48.60948],[-93.46377,48.58567],[-93.45374,48.54834],[-93.66382,48.51845],[-93.80279,48.51892],[-93.82292,48.62313],[-93.84515,48.63011],[-94.2464,48.65422],[-94.26485,48.70188],[-94.4174,48.71049],[-94.44258,48.69223],[-94.53826,48.70216],[-94.54885,48.71543],[-94.58903,48.71803],[-94.68691,48.77498],[-94.70477,48.82975],[-94.68751,48.84286],[-94.82487,49.29483],[-94.8159,49.32299],[-94.85381,49.32492],[-94.95766,49.37046],[-95.05825,49.35311],[-95.15357,49.37852],[-95.15355,48.9996],[-97.24024,48.99952],[-101.36198,48.99935],[-104.05004,48.99925],[-110.0051,48.99901],[-114.0683,48.99885],[-116.04938,48.99999],[-117.03266,49.00056],[-123.3218,49.00227],[-122.98526,48.79206],[-123.26565,48.6959],[-123.15614,48.22053],[-125.03842,48.53282],[-125.2772,46.2631],[-125.69978,42.00605]]],[[[-75.27909,18.17213],[-74.76465,18.06252],[-74.7289,18.71009],[-75.24866,18.6531],[-75.27909,18.17213]]],[[[-68.20301,17.83927],[-65.27974,17.56928],[-64.35558,17.48384],[-64.646,18.10286],[-64.64067,18.36478],[-64.86049,18.39954],[-65.02435,18.73231],[-67.99519,18.97186],[-68.20301,17.83927]]],[[[143.82485,13.92273],[144.61642,12.82462],[146.25931,13.85876],[146.6755,21.00809],[144.18594,21.03576],[143.82485,13.92273]]],[[[166.27257,19.60026],[166.27567,19.02484],[166.94111,19.02804],[166.93801,19.60345],[166.27257,19.60026]]],[[[171.97544,51.06331],[180,51.0171],[180,53.34113],[172.01045,53.385],[171.97544,51.06331]]]]}},{"type":"Feature","properties":{"id":"UZ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[55.99849,44.99862],[56.00314,41.32584],[57.03423,41.25435],[57.12628,41.33429],[56.96218,41.80383],[57.03633,41.92043],[57.6296,42.16519],[57.84932,42.18555],[57.92897,42.24047],[57.90975,42.4374],[57.99214,42.50021],[58.3492,42.43335],[58.40688,42.29535],[58.51674,42.30348],[58.28453,42.55662],[58.14321,42.62159],[58.27504,42.69632],[58.57995,42.64103],[58.6266,42.79314],[59.00636,42.52436],[59.17528,42.53044],[59.2955,42.37064],[59.46212,42.29178],[59.73867,42.29965],[59.89128,42.298],[60.01831,42.2069],[59.96419,42.1428],[60.04659,42.08982],[60.0356,42.01028],[59.90226,41.99968],[60.32764,41.76772],[60.31082,41.74749],[60.08504,41.80997],[60.04869,41.74967],[60.18117,41.60082],[60.04199,41.44478],[60.5078,41.21694],[61.03261,41.25691],[61.22212,41.14946],[61.33199,41.14946],[61.39732,41.19873],[61.4446,41.29407],[61.86675,41.12488],[62.08717,40.58579],[62.34273,40.43206],[62.43337,39.98528],[63.69392,39.27266],[63.70778,39.22349],[64.19086,38.95561],[64.32576,38.98691],[65.55873,38.29052],[65.83913,38.25733],[66.24858,38.14184],[66.41042,38.02403],[66.51603,38.03376],[66.70761,37.9515],[66.53676,37.80084],[66.52496,37.56009],[66.59088,37.44869],[66.52303,37.39827],[66.55743,37.35409],[66.64699,37.32958],[66.95598,37.40162],[67.08232,37.35469],[67.13039,37.27168],[67.2224,37.24545],[67.2581,37.17216],[67.51868,37.26102],[67.78329,37.1834],[67.8474,37.31594],[67.81566,37.43107],[68.14819,37.93445],[68.2759,37.91115],[68.30389,38.02213],[68.37272,38.08768],[68.40637,38.19407],[68.13549,38.41082],[68.0809,38.38768],[68.06098,38.39629],[68.11677,38.46985],[68.05873,38.56087],[68.0807,38.64136],[68.05598,38.71641],[68.12877,38.73677],[68.06948,38.82115],[68.19743,38.85985],[68.09704,39.02589],[67.68915,39.00775],[67.67833,39.14479],[67.3546,39.22959],[67.36522,39.31287],[67.46961,39.31663],[67.46274,39.46813],[67.3915,39.52364],[67.46547,39.53564],[67.44899,39.57799],[67.62889,39.60234],[67.70992,39.66156],[68.12053,39.56317],[68.52807,39.53304],[68.61972,39.68905],[68.63536,39.85731],[68.69716,39.84927],[68.88805,39.86679],[68.93886,39.92026],[68.84977,40.05731],[68.89526,40.04982],[68.96579,40.06949],[69.01935,40.11466],[69.01523,40.15771],[68.62796,40.07789],[68.52771,40.11676],[68.53219,40.14187],[68.77956,40.20365],[68.79793,40.17179],[68.84357,40.18604],[68.85832,40.20885],[69.03345,40.22895],[69.16511,40.21244],[69.20305,40.21427],[69.30448,40.18774],[69.30104,40.24502],[69.25592,40.26354],[69.24817,40.30357],[69.30808,40.2821],[69.32833,40.29794],[69.33794,40.34819],[69.30774,40.36102],[69.28525,40.41894],[69.27066,40.49274],[69.21063,40.54469],[69.2643,40.57506],[69.3455,40.57988],[69.34759,40.61486],[69.32834,40.70233],[69.35583,40.72644],[69.36235,40.79041],[69.53021,40.77621],[69.59441,40.70181],[69.69434,40.62615],[70.36655,40.90296],[70.38028,41.02014],[70.45251,41.04438],[70.8043,40.72176],[70.49871,40.52503],[70.32626,40.45174],[70.37511,40.38605],[70.5808,40.33803],[70.57514,40.26538],[70.63144,40.17638],[70.8607,40.217],[70.89228,40.21892],[70.9818,40.22392],[70.95789,40.28761],[71.05901,40.28765],[71.13042,40.34106],[71.36663,40.31593],[71.4246,40.28619],[71.51215,40.26943],[71.51549,40.22986],[71.61725,40.20615],[71.61931,40.26775],[71.68386,40.26984],[71.70569,40.20391],[71.69583,40.18372],[71.7202,40.17415],[71.72887,40.14765],[71.78346,40.20326],[71.83462,40.20877],[71.83977,40.25306],[71.92714,40.25018],[71.97658,40.25673],[72.00353,40.2752],[72.0207,40.26734],[72.05451,40.27795],[71.96401,40.31907],[72.05623,40.38421],[72.17365,40.49017],[72.24368,40.46091],[72.40346,40.4007],[72.44195,40.47359],[72.41513,40.50856],[72.37861,40.51118],[72.41621,40.55515],[72.34406,40.60144],[72.40517,40.61917],[72.47795,40.5532],[72.66975,40.51366],[72.66713,40.59076],[72.69215,40.59642],[72.73807,40.58358],[72.74768,40.58051],[72.74713,40.57136],[72.76129,40.57253],[72.74894,40.59592],[72.74866,40.60873],[72.80137,40.67856],[72.84754,40.67229],[72.86072,40.69593],[72.85372,40.7116],[72.8722,40.71111],[72.93296,40.73089],[72.99133,40.76457],[73.06431,40.76546],[73.14208,40.79236],[73.1407,40.84309],[73.02972,40.83959],[73.01869,40.84681],[72.94312,40.80698],[72.84291,40.85512],[72.68157,40.84942],[72.57276,40.87679],[72.59362,40.90287],[72.54547,40.96538],[72.53474,40.96123],[72.49405,40.96952],[72.49826,40.98663],[72.45206,41.03018],[72.39415,41.02724],[72.36138,41.04384],[72.34757,41.06104],[72.34026,41.04539],[72.324,41.03381],[72.23476,41.00179],[72.17245,40.99272],[72.21061,41.05607],[72.1792,41.10621],[72.14515,41.13445],[72.17594,41.15522],[72.16433,41.16483],[72.10867,41.15455],[72.07262,41.11764],[71.85964,41.19081],[71.92491,41.2938],[71.84492,41.35838],[71.77419,41.44748],[71.7069,41.42355],[71.73471,41.55175],[71.70553,41.53736],[71.66759,41.49739],[71.60167,41.47617],[71.66913,41.44787],[71.6787,41.42111],[71.57227,41.29175],[71.46537,41.3036],[71.43814,41.19644],[71.45198,41.15668],[71.39671,41.10781],[71.3416,41.12656],[71.28049,41.0994],[71.25813,41.18796],[71.11896,41.14427],[71.02193,41.19494],[70.9615,41.16393],[70.86263,41.23833],[70.77885,41.24813],[70.78572,41.36419],[70.67586,41.47953],[70.48909,41.40335],[70.17682,41.5455],[70.69777,41.92554],[71.28719,42.18033],[71.13263,42.28356],[70.94483,42.26238],[69.49545,41.545],[69.45751,41.56863],[69.39485,41.51518],[69.45081,41.46246],[69.37468,41.46555],[69.35554,41.47211],[69.29778,41.43673],[69.25059,41.46693],[69.23332,41.45847],[69.22671,41.46298],[69.20439,41.45391],[69.18528,41.45175],[69.17701,41.43769],[69.15137,41.43078],[69.17275,41.40185],[69.12717,41.38949],[69.11481,41.39213],[69.0961,41.35812],[69.08031,41.35787],[69.08803,41.3694],[69.07902,41.37751],[69.0519,41.3683],[69.03242,41.30347],[69.01525,41.28606],[69.03121,41.26761],[69.02778,41.23483],[68.91586,41.17994],[68.7145,41.05812],[68.75278,40.97795],[68.64875,40.94373],[68.62221,41.03019],[68.49983,40.99669],[68.5794,40.92129],[68.56155,40.80627],[68.64875,40.66293],[68.62506,40.63089],[68.6618,40.59961],[68.48722,40.5721],[67.9676,40.82809],[68.0821,40.97873],[68.07523,41.02524],[68.12278,41.04181],[68.08273,41.08148],[67.98511,41.02794],[67.9644,41.14611],[66.69129,41.1311],[66.09482,42.93426],[64.53885,43.56941],[62.01711,43.51008],[61.01475,44.41383],[58.58792,45.59067],[55.99849,44.99862]],[[70.54223,40.98787],[70.56077,41.00642],[70.6158,40.97661],[70.68112,40.90612],[70.6721,40.90555],[70.57501,40.98941],[70.54223,40.98787]]],[[[71.00236,40.18154],[71.01035,40.05481],[71.11037,40.01984],[71.11668,39.99291],[71.09063,39.99],[71.11089,39.95843],[71.09029,39.94916],[71.04429,39.89367],[71.09407,39.90348],[71.11192,39.93461],[71.18445,39.91816],[71.23586,39.92395],[71.2047,39.94277],[71.21209,39.95324],[71.14282,39.95139],[71.19174,40.01012],[71.12428,40.01197],[71.0618,40.17756],[71.00236,40.18154]]],[[[71.71511,39.96348],[71.74673,39.93422],[71.84316,39.95582],[71.85873,39.9852],[71.79084,40.01105],[71.71511,39.96348]]]]}},{"type":"Feature","properties":{"id":"ZA"},"geometry":{"type":"Polygon","coordinates":[[[15.70388,-29.23989],[18.18188,-34.40654],[38.61808,-47.85644],[33.10054,-26.92273],[32.89816,-26.8579],[32.74646,-26.86726],[32.34741,-26.86425],[32.21847,-26.834],[32.1861,-26.86436],[32.13315,-26.84345],[32.09664,-26.80721],[32.00893,-26.8096],[31.97463,-27.11057],[31.97639,-27.31747],[31.49344,-27.31518],[31.15014,-27.20204],[30.95899,-27.00048],[30.97516,-26.91481],[30.90771,-26.85968],[30.88826,-26.79622],[30.81101,-26.84722],[30.7854,-26.66924],[30.80815,-26.45504],[30.95819,-26.26303],[31.13073,-25.91558],[31.31237,-25.7431],[31.4175,-25.71886],[31.86881,-25.99973],[31.97537,-25.95271],[31.92987,-25.84037],[32.00631,-25.65044],[31.97896,-25.45776],[32.01676,-25.38117],[32.03196,-25.10785],[31.9835,-24.29983],[31.9091,-24.1812],[31.88095,-23.95268],[31.76937,-23.88772],[31.69589,-23.72155],[31.69109,-23.62596],[31.5596,-23.48025],[31.56234,-23.19622],[31.30743,-22.42308],[31.16344,-22.32599],[31.08932,-22.34884],[30.86696,-22.28907],[30.6294,-22.32599],[30.48686,-22.31368],[30.382,-22.34944],[30.28351,-22.35587],[30.2265,-22.2961],[30.13147,-22.30841],[29.92242,-22.19408],[29.76848,-22.14128],[29.64609,-22.12917],[29.37364,-22.1957],[29.21955,-22.17771],[29.18974,-22.18599],[29.15415,-22.21589],[29.10851,-22.21241],[29.01781,-22.22308],[28.97292,-22.3715],[28.92511,-22.4572],[28.83172,-22.45426],[28.83207,-22.48801],[28.72366,-22.51073],[28.65414,-22.55116],[28.56565,-22.55988],[28.51226,-22.58825],[28.47454,-22.57003],[28.34874,-22.5694],[28.171,-22.70216],[28.16139,-22.75766],[28.04562,-22.8394],[28.04752,-22.90243],[27.94647,-22.96202],[27.93539,-23.04941],[27.74154,-23.2137],[27.6066,-23.21894],[27.53156,-23.3785],[27.33768,-23.40917],[26.99151,-23.65663],[26.85916,-24.24273],[26.51842,-24.47871],[26.46346,-24.60358],[26.41027,-24.64608],[26.39224,-24.63827],[26.09364,-24.70613],[25.8515,-24.75727],[25.85125,-24.77917],[25.88996,-24.88129],[25.72895,-25.2568],[25.70268,-25.28862],[25.66474,-25.46698],[25.5832,-25.63719],[25.34296,-25.76851],[25.12298,-25.7641],[25.10946,-25.74992],[25.10221,-25.7486],[25.10427,-25.74099],[25.08873,-25.74168],[25.08178,-25.73078],[25.00368,-25.7348],[24.89673,-25.81225],[24.68387,-25.82353],[24.57092,-25.77361],[24.43393,-25.73542],[24.339,-25.77608],[24.18287,-25.62916],[23.97903,-25.64462],[23.7557,-25.46722],[23.70489,-25.45679],[23.47588,-25.29971],[23.03497,-25.29971],[22.86012,-25.50572],[22.70808,-25.99186],[22.57278,-26.20134],[22.41921,-26.23078],[22.23083,-26.38295],[22.05368,-26.63303],[21.90703,-26.66808],[21.83291,-26.65959],[21.78348,-26.68212],[21.7854,-26.79199],[21.69322,-26.86152],[21.37869,-26.82083],[21.13353,-26.86661],[20.87031,-26.80047],[20.68596,-26.9039],[20.63275,-26.78181],[20.61754,-26.4692],[20.86081,-26.14892],[20.64795,-25.47827],[20.29826,-24.94869],[20.03678,-24.81004],[20.02809,-24.78725],[19.99817,-24.76768],[19.99882,-28.42622],[18.99742,-28.87414],[17.41779,-28.70624],[17.36801,-28.32009],[17.11051,-28.04774],[16.89937,-28.06092],[16.87705,-28.17507],[16.77955,-28.28563],[16.76685,-28.45027],[16.59922,-28.53246],[16.46592,-28.57126],[16.45332,-28.63117],[15.70388,-29.23989]],[[27.00177,-29.65352],[27.06799,-29.60599],[27.30257,-29.52238],[27.30471,-29.49594],[27.33464,-29.48161],[27.4365,-29.33336],[27.47294,-29.32004],[27.45311,-29.30039],[27.45607,-29.29672],[27.46624,-29.29403],[27.49405,-29.28755],[27.54761,-29.25184],[27.5158,-29.2261],[27.61516,-29.1574],[27.65945,-29.05166],[27.73489,-28.94457],[27.76442,-28.93485],[27.75458,-28.89839],[27.8907,-28.91612],[27.88933,-28.88156],[27.9392,-28.84864],[27.98675,-28.8787],[28.02503,-28.85991],[28.1317,-28.7293],[28.2348,-28.69471],[28.34893,-28.6957],[28.40612,-28.6215],[28.65091,-28.57025],[28.68043,-28.58744],[28.76255,-28.68893],[28.91876,-28.77126],[28.96751,-28.88977],[29.33023,-29.10177],[29.40524,-29.21246],[29.44883,-29.3772],[29.30809,-29.4931],[29.28545,-29.58456],[29.12553,-29.76266],[29.16548,-29.91706],[28.9338,-30.05072],[28.80222,-30.10579],[28.68627,-30.12885],[28.66719,-30.14067],[28.399,-30.1592],[28.24653,-30.27211],[28.25752,-30.38827],[28.12073,-30.68072],[27.74382,-30.60589],[27.71734,-30.57146],[27.69467,-30.55862],[27.69069,-30.54093],[27.67134,-30.5342],[27.6521,-30.51707],[27.62137,-30.50509],[27.60572,-30.44941],[27.56781,-30.44562],[27.56901,-30.42504],[27.44041,-30.32176],[27.38108,-30.33456],[27.36649,-30.27246],[27.37293,-30.19401],[27.41084,-30.15143],[27.40479,-30.14275],[27.32555,-30.14785],[27.29603,-30.05473],[27.22719,-30.00718],[27.0967,-29.72998],[27.02443,-29.66679],[27.00177,-29.65352]]]}},{"type":"Feature","properties":{"id":"UM"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-177.8563,29.18961],[-177.84531,27.68616],[-176.81808,27.68129],[-176.83456,29.19028],[-177.8563,29.18961]]],[[[-177.43928,1.65656],[-177.43039,-1.43294],[-175.33482,-1.40631],[-175.33167,1.67574],[-177.43928,1.65656]]],[[[-169.97341,16.32808],[-169.02347,16.32808],[-169.02347,17.13856],[-169.97341,17.13856],[-169.97341,16.32808]]],[[[-163.24994,7.12322],[-163.24478,5.24198],[-161.06795,5.2462],[-161.0731,7.1291],[-163.24994,7.12322]]],[[[-161.05669,1.11722],[-161.04969,-1.36251],[-158.62058,-1.35506],[-158.62734,1.1296],[-161.05669,1.11722]]],[[[-75.27909,18.17213],[-74.76465,18.06252],[-74.7289,18.71009],[-75.24866,18.6531],[-75.27909,18.17213]]],[[[166.27257,19.60026],[166.27567,19.02484],[166.94111,19.02804],[166.93801,19.60345],[166.27257,19.60026]]]]}},{"type":"Feature","properties":{"id":"PS"},"geometry":{"type":"MultiPolygon","coordinates":[[[[34.052,31.46619],[34.21853,31.32363],[34.23572,31.2966],[34.24012,31.29591],[34.26742,31.21998],[34.29417,31.24194],[34.36523,31.28963],[34.37381,31.30598],[34.36505,31.36404],[34.40077,31.40926],[34.5108,31.50026],[34.54925,31.51504],[34.56797,31.54197],[34.48681,31.59711],[34.29262,31.70393],[34.052,31.46619]]],[[[34.87833,31.39321],[34.88932,31.37093],[34.92571,31.34337],[35.02459,31.35979],[35.13033,31.3551],[35.22921,31.37445],[35.39675,31.49572],[35.47672,31.49578],[35.55941,31.76535],[35.52758,31.9131],[35.54375,31.96587],[35.52012,32.04076],[35.57111,32.21877],[35.55807,32.38674],[35.42078,32.41562],[35.41048,32.43706],[35.41598,32.45593],[35.42034,32.46009],[35.40224,32.50136],[35.35212,32.52047],[35.30685,32.51024],[35.29306,32.50947],[35.25049,32.52453],[35.2244,32.55289],[35.15937,32.50466],[35.10882,32.4757],[35.10024,32.47856],[35.09236,32.47614],[35.08564,32.46948],[35.07059,32.4585],[35.05423,32.41754],[35.05311,32.4024],[35.0421,32.38242],[35.05142,32.3667],[35.04243,32.35008],[35.01772,32.33863],[35.01119,32.28684],[35.02939,32.2671],[35.01841,32.23981],[34.98885,32.20758],[34.95703,32.19522],[34.96009,32.17503],[34.99039,32.14626],[34.98507,32.12606],[34.99437,32.10962],[34.9863,32.09551],[35.00261,32.027],[34.98682,31.96935],[35.00578,31.92889],[35.03973,31.92222],[35.03978,31.89276],[35.03489,31.85919],[34.99712,31.85569],[34.97566,31.83396],[35.01978,31.82944],[35.05617,31.85685],[35.07677,31.85627],[35.10835,31.82528],[35.13874,31.81412],[35.18603,31.80901],[35.18169,31.82542],[35.19461,31.82687],[35.21469,31.81835],[35.216,31.83894],[35.21128,31.863],[35.20381,31.86716],[35.20673,31.88151],[35.20791,31.8821],[35.20945,31.8815],[35.21016,31.88237],[35.21276,31.88153],[35.2136,31.88241],[35.22014,31.88264],[35.22294,31.87889],[35.22567,31.86745],[35.22817,31.8638],[35.2245,31.85386],[35.2303,31.84136],[35.24701,31.84624],[35.25753,31.8387],[35.251,31.83085],[35.26469,31.82597],[35.25573,31.81362],[35.26049,31.79103],[35.25233,31.76648],[35.263,31.74829],[35.25207,31.73904],[35.24981,31.72543],[35.2438,31.7201],[35.24315,31.71244],[35.23884,31.70953],[35.22454,31.71904],[35.21937,31.71578],[35.20573,31.72358],[35.18023,31.72067],[35.16478,31.73242],[35.15474,31.73352],[35.15079,31.73665],[35.13807,31.72847],[35.13537,31.7346],[35.12723,31.73017],[35.11895,31.71454],[35.10782,31.71594],[35.08226,31.69107],[35.00638,31.65177],[34.95231,31.5944],[34.9415,31.55601],[34.9445,31.5067],[34.93128,31.47362],[34.89978,31.43657],[34.87833,31.39321]]]]}},{"type":"Feature","properties":{"id":"TF"},"geometry":{"type":"MultiPolygon","coordinates":[[[[39.10324,-21.48967],[40.40841,-23.17181],[43.72277,-16.09877],[41.06663,-17.08802],[39.10324,-21.48967]]],[[[46.52682,-10.83678],[47.29063,-12.45583],[48.86266,-10.8109],[46.52682,-10.83678]]],[[[48.6013,-46.16588],[70.19167,-50.55929],[78.57663,-37.28345],[48.6013,-46.16588]]],[[[54.31559,-15.68788],[54.3232,-16.10331],[54.73806,-16.0963],[54.73046,-15.68085],[54.31559,-15.68788]]]]}},{"type":"Feature","properties":{"id":"EA"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-5.38491,35.92591],[-5.37338,35.88417],[-5.35844,35.87375],[-5.34379,35.8711],[-5.27056,35.88794],[-5.27635,35.91222],[-5.38491,35.92591]]],[[[-2.97035,35.28852],[-2.96507,35.28801],[-2.96826,35.28296],[-2.96516,35.27967],[-2.95431,35.2728],[-2.95065,35.26576],[-2.93893,35.26737],[-2.92674,35.27313],[-2.92181,35.28599],[-2.92224,35.3401],[-2.96038,35.31609],[-2.96648,35.30475],[-2.96978,35.29459],[-2.97035,35.28852]]]]}},{"type":"Feature","properties":{"id":"MA"},"geometry":{"type":"Polygon","coordinates":[[[-17.27295,21.93519],[-17.21511,21.34226],[-17.02707,21.34022],[-16.9978,21.36239],[-16.44269,21.39745],[-14.78487,21.36587],[-14.47329,21.63839],[-14.48112,22.00886],[-14.1291,22.41636],[-14.10361,22.75501],[-13.75627,23.77231],[-13.00628,24.01923],[-12.92147,24.39502],[-12.12281,25.13682],[-12.06001,26.04442],[-11.62052,26.05229],[-11.38635,26.611],[-11.23622,26.72023],[-11.35695,26.8505],[-10.68417,26.90984],[-9.81998,26.71379],[-9.56957,26.90042],[-9.08698,26.98639],[-8.71787,26.9898],[-8.77527,27.66663],[-8.66879,27.6666],[-8.6715,28.71194],[-7.61585,29.36252],[-6.95824,29.50924],[-6.78351,29.44634],[-6.69965,29.51623],[-5.75616,29.61407],[-5.72121,29.52322],[-5.58831,29.48103],[-5.21671,29.95253],[-4.6058,30.28343],[-4.31774,30.53229],[-3.64735,30.67539],[-3.65418,30.85566],[-3.54944,31.0503],[-3.77103,31.14984],[-3.77647,31.31912],[-3.66386,31.39202],[-3.66314,31.6339],[-2.82784,31.79459],[-2.93873,32.06557],[-2.46166,32.16603],[-1.22829,32.07832],[-1.15735,32.12096],[-1.24453,32.1917],[-1.24998,32.32993],[-0.99426,32.51526],[-1.37794,32.73628],[-1.54244,32.95499],[-1.46249,33.0499],[-1.67067,33.27084],[-1.59508,33.59929],[-1.73494,33.71721],[-1.65412,34.09474],[-1.71009,34.31111],[-1.78244,34.3926],[-1.69788,34.48056],[-1.84569,34.61907],[-1.7391,34.74323],[-1.88999,34.81042],[-1.89325,34.84198],[-1.97367,34.88223],[-1.97273,34.93456],[-2.04734,34.93218],[-2.08465,34.9525],[-2.21305,35.04679],[-2.21248,35.08532],[-2.19467,35.59878],[-5.10878,36.05227],[-5.64962,35.93752],[-6.21126,35.90929],[-12.87179,29.54492],[-13.84006,27.86813],[-17.27295,21.93519]],[[-5.38491,35.92591],[-5.27635,35.91222],[-5.27056,35.88794],[-5.34379,35.8711],[-5.35844,35.87375],[-5.37338,35.88417],[-5.38491,35.92591]],[[-4.30191,35.17419],[-4.29436,35.17149],[-4.30112,35.17058],[-4.30191,35.17419]],[[-3.90602,35.21494],[-3.90288,35.22024],[-3.88617,35.21406],[-3.88926,35.20841],[-3.90602,35.21494]],[[-2.97035,35.28852],[-2.96978,35.29459],[-2.96648,35.30475],[-2.96038,35.31609],[-2.92224,35.3401],[-2.92181,35.28599],[-2.92674,35.27313],[-2.93893,35.26737],[-2.95065,35.26576],[-2.95431,35.2728],[-2.96516,35.27967],[-2.96826,35.28296],[-2.96507,35.28801],[-2.97035,35.28852]],[[-2.44896,35.18777],[-2.41265,35.1877],[-2.41312,35.17111],[-2.44887,35.17075],[-2.44896,35.18777]]]}},{"type":"Feature","properties":{"id":"AU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[71.83423,-53.46357],[74.60136,-53.32773],[73.06787,-52.24306],[71.83423,-53.46357]]],[[[96.53724,-12.46709],[97.24513,-12.47233],[97.25212,-11.57036],[96.54423,-11.5651],[96.53724,-12.46709]]],[[[105.29647,-10.80676],[105.9699,-10.81333],[105.97626,-10.18257],[105.30283,-10.176],[105.29647,-10.80676]]],[[[111.88472,-23.52365],[115.28483,-36.59878],[129.11535,-33.13244],[140.96881,-39.24482],[158.81148,-55.541],[159.49376,-54.48258],[149.33352,-39.17352],[150.19141,-37.59274],[155.32243,-27.36405],[159.35793,-33.24807],[165.46901,-28.32101],[169.35326,-30.60259],[169.6687,-29.09191],[166.75333,-27.25416],[158.4748,-21.86428],[157.46481,-18.93777],[158.60851,-15.7108],[155.22803,-12.9001],[144.30183,-9.48146],[142.81927,-9.31709],[142.5723,-9.35994],[142.31447,-9.24611],[142.23304,-9.19253],[142.1462,-9.19923],[142.0953,-9.23534],[142.0601,-9.56571],[140.88922,-9.34945],[139.41724,-8.97063],[137.03241,-8.98668],[127.55165,-9.05052],[125.68138,-9.85176],[122.91521,-11.65621],[111.88472,-23.52365]]]]}},{"type":"Feature","properties":{"id":"CH"},"geometry":{"type":"Polygon","coordinates":[[[5.95574,46.12869],[5.97819,46.13303],[5.98433,46.14318],[6.01763,46.14274],[6.036,46.13553],[6.04587,46.14011],[6.05284,46.15245],[6.07533,46.14909],[6.09199,46.15191],[6.10035,46.14405],[6.13528,46.14121],[6.1367,46.14282],[6.14249,46.14668],[6.14498,46.14493],[6.14865,46.15001],[6.1812,46.16179],[6.18871,46.16644],[6.18707,46.17999],[6.19552,46.18401],[6.19807,46.18369],[6.20633,46.19229],[6.21114,46.1927],[6.21273,46.19409],[6.21603,46.19507],[6.21844,46.19837],[6.22222,46.19888],[6.22175,46.20045],[6.23544,46.20714],[6.23913,46.20511],[6.24821,46.20531],[6.25585,46.20999],[6.26795,46.21362],[6.27694,46.21566],[6.29477,46.22521],[6.31025,46.24381],[6.30868,46.24725],[6.30937,46.25003],[6.29474,46.26221],[6.26733,46.24752],[6.24984,46.26227],[6.23779,46.28163],[6.25137,46.29014],[6.24843,46.30199],[6.21981,46.31304],[6.25432,46.3632],[6.53358,46.45431],[6.82312,46.42661],[6.80501,46.39362],[6.80335,46.39285],[6.80309,46.3914],[6.80183,46.38836],[6.80611,46.38142],[6.7927,46.36761],[6.76903,46.35951],[6.77813,46.34242],[6.86249,46.28559],[6.85478,46.25887],[6.80929,46.2109],[6.80706,46.17471],[6.78487,46.14022],[6.89321,46.12548],[6.87868,46.03855],[6.93308,46.06713],[7.00946,45.9944],[7.04151,45.92435],[7.10685,45.85653],[7.56343,45.97421],[7.85949,45.91485],[7.9049,45.99945],[7.98881,45.99867],[8.02906,46.10331],[8.11383,46.11577],[8.16866,46.17817],[8.08814,46.26692],[8.31162,46.38044],[8.30648,46.41587],[8.42464,46.46367],[8.46317,46.43712],[8.45166,46.24801],[8.60383,46.15522],[8.59508,46.13809],[8.69713,46.10204],[8.75697,46.10395],[8.77884,46.09561],[8.80778,46.10085],[8.85617,46.0748],[8.82322,46.02897],[8.79541,46.01115],[8.78585,45.98973],[8.79362,45.99207],[8.8319,45.9879],[8.85121,45.97239],[8.87223,45.95926],[8.89609,45.95825],[8.89394,45.93091],[8.92141,45.9084],[8.93673,45.86734],[8.94212,45.86961],[8.94587,45.86712],[8.9336,45.86198],[8.91424,45.84207],[8.91132,45.83062],[8.95716,45.84327],[8.9621,45.83707],[8.99741,45.83489],[8.99608,45.82093],[9.03213,45.82081],[9.03279,45.82865],[9.03793,45.83548],[9.03505,45.83976],[9.04059,45.8464],[9.04378,45.84681],[9.06642,45.8761],[9.09065,45.89906],[8.99257,45.9698],[9.01618,46.04928],[9.08466,46.0849],[9.24503,46.23616],[9.29226,46.32717],[9.25502,46.43743],[9.28136,46.49685],[9.36128,46.5081],[9.40487,46.46621],[9.45936,46.50873],[9.46117,46.37481],[9.57015,46.2958],[9.71273,46.29266],[9.73086,46.35071],[9.95249,46.38045],[10.07055,46.21668],[10.14439,46.22992],[10.17862,46.25626],[10.10506,46.3372],[10.165,46.41051],[10.03715,46.44479],[10.10307,46.61003],[10.23674,46.63484],[10.25309,46.57432],[10.45299,46.53081],[10.49375,46.62049],[10.44686,46.64162],[10.40475,46.63671],[10.38659,46.67847],[10.46999,46.85498],[10.48376,46.93891],[10.36933,47.00212],[10.30031,46.92093],[10.24128,46.93147],[10.22675,46.86942],[10.10715,46.84296],[9.98058,46.91434],[9.88266,46.93343],[9.87935,47.01337],[9.60717,47.06091],[9.55721,47.04762],[9.54041,47.06495],[9.47548,47.05257],[9.47139,47.06402],[9.51362,47.08505],[9.52089,47.10019],[9.51044,47.13727],[9.48876,47.16643],[9.4891,47.19346],[9.50318,47.22153],[9.52406,47.24959],[9.53116,47.27029],[9.54773,47.2809],[9.55659,47.29822],[9.58513,47.31334],[9.59978,47.34671],[9.62476,47.36639],[9.65427,47.36824],[9.66243,47.37136],[9.6711,47.37824],[9.67445,47.38429],[9.67334,47.39191],[9.6629,47.39591],[9.65136,47.40504],[9.65043,47.41937],[9.6446,47.43233],[9.64483,47.43842],[9.65863,47.44847],[9.65728,47.45383],[9.6423,47.45599],[9.62475,47.45685],[9.62158,47.45858],[9.60841,47.47178],[9.60484,47.46358],[9.60205,47.46165],[9.59482,47.46305],[9.58208,47.48344],[9.56312,47.49495],[9.55125,47.53629],[9.25619,47.65939],[9.18203,47.65598],[9.17593,47.65399],[9.1755,47.65584],[9.1705,47.65513],[9.15181,47.66904],[9.13845,47.66389],[9.09891,47.67801],[9.02093,47.6868],[8.94093,47.65596],[8.89946,47.64769],[8.87625,47.65441],[8.87383,47.67045],[8.85065,47.68209],[8.86637,47.70532],[8.82002,47.71458],[8.80663,47.73821],[8.77309,47.72059],[8.76965,47.7075],[8.79966,47.70222],[8.79511,47.67462],[8.75856,47.68969],[8.72809,47.69282],[8.72617,47.69651],[8.73671,47.7169],[8.70942,47.73034],[8.72241,47.7446],[8.74572,47.74908],[8.71778,47.76571],[8.68985,47.75686],[8.68086,47.78648],[8.65292,47.80066],[8.64425,47.76398],[8.62408,47.7626],[8.61657,47.79998],[8.56415,47.80633],[8.56814,47.78001],[8.48868,47.77215],[8.45706,47.74981],[8.44807,47.72426],[8.40478,47.69791],[8.4211,47.68407],[8.40473,47.67499],[8.41346,47.66676],[8.42264,47.66667],[8.44711,47.65379],[8.4667,47.65747],[8.46605,47.64103],[8.49656,47.64709],[8.5322,47.64687],[8.52801,47.66059],[8.56141,47.67088],[8.57683,47.66158],[8.60109,47.67267],[8.60628,47.67223],[8.60697,47.6653],[8.62884,47.65098],[8.62049,47.63757],[8.60412,47.63735],[8.61471,47.64514],[8.60701,47.65271],[8.59545,47.64298],[8.60547,47.61328],[8.57586,47.59537],[8.56173,47.60063],[8.57006,47.61429],[8.55756,47.62394],[8.51686,47.63476],[8.50805,47.61958],[8.45578,47.60121],[8.46637,47.58389],[8.48949,47.588],[8.49431,47.58107],[8.43235,47.56617],[8.39597,47.57753],[8.38388,47.56576],[8.35533,47.57014],[8.32773,47.57101],[8.30277,47.58607],[8.29524,47.5919],[8.29789,47.6059],[8.2821,47.612],[8.26403,47.60923],[8.26013,47.61527],[8.23809,47.61204],[8.22614,47.60436],[8.22011,47.6181],[8.21305,47.62048],[8.20193,47.62048],[8.1643,47.5936],[8.14947,47.59558],[8.13823,47.59147],[8.13739,47.58411],[8.11323,47.58393],[8.10409,47.57858],[8.1007,47.56474],[8.08585,47.55669],[8.06993,47.5641],[8.06014,47.56362],[8.04677,47.55502],[8.01984,47.55017],[8.00113,47.55616],[7.97581,47.55493],[7.95682,47.55789],[7.94614,47.5436],[7.93264,47.54704],[7.92058,47.5469],[7.91251,47.55031],[7.9068,47.56037],[7.91161,47.56686],[7.90673,47.57674],[7.89835,47.58408],[7.88664,47.58854],[7.86269,47.58808],[7.84167,47.58196],[7.83346,47.58663],[7.81901,47.58798],[7.81127,47.56946],[7.80098,47.56335],[7.79486,47.55691],[7.75261,47.54599],[7.71961,47.54219],[7.69642,47.53297],[7.68101,47.53232],[7.6656,47.53752],[7.66174,47.54554],[7.65083,47.54662],[7.63421,47.56193],[7.63599,47.56382],[7.67655,47.56435],[7.68904,47.57133],[7.67115,47.5871],[7.69385,47.60099],[7.68229,47.59905],[7.67317,47.59146],[7.64599,47.59695],[7.64213,47.5944],[7.64309,47.59151],[7.61929,47.57683],[7.60459,47.57869],[7.60523,47.58519],[7.58945,47.59017],[7.58386,47.57536],[7.56684,47.57785],[7.56548,47.57617],[7.55689,47.57232],[7.55652,47.56779],[7.53634,47.55553],[7.52831,47.55347],[7.51723,47.54578],[7.50873,47.54546],[7.49731,47.53664],[7.50683,47.5278],[7.51904,47.53515],[7.53199,47.5284],[7.5229,47.51644],[7.49804,47.51798],[7.50906,47.50943],[7.5107,47.49873],[7.4913,47.48446],[7.47534,47.47932],[7.43356,47.49712],[7.42444,47.48519],[7.4583,47.47216],[7.4462,47.46264],[7.43088,47.45846],[7.40308,47.43638],[7.35603,47.43432],[7.33526,47.44186],[7.24669,47.4205],[7.17026,47.44312],[7.19583,47.49455],[7.16249,47.49025],[7.12781,47.50371],[7.07425,47.48863],[7.0231,47.50522],[6.98425,47.49432],[7.0024,47.45264],[6.97859,47.44884],[6.94078,47.43354],[6.93744,47.40714],[6.88542,47.37262],[6.87959,47.35335],[7.03125,47.36996],[7.05897,47.34292],[7.05172,47.32663],[7.00996,47.32427],[7.00871,47.30202],[6.94035,47.28636],[6.95159,47.26991],[6.95074,47.25913],[6.95275,47.24349],[6.92737,47.22839],[6.92293,47.22236],[6.87314,47.18597],[6.84568,47.16713],[6.85855,47.16445],[6.76788,47.1208],[6.72612,47.09236],[6.70831,47.08403],[6.702,47.07108],[6.69024,47.06638],[6.71891,47.05138],[6.69736,47.03801],[6.662,47.02918],[6.6469,47.00987],[6.62037,46.99281],[6.57295,46.98285],[6.50472,46.96572],[6.4324,46.92846],[6.46639,46.89152],[6.43216,46.80336],[6.45209,46.77502],[6.38494,46.73197],[6.27135,46.68251],[6.11084,46.57649],[6.15684,46.54522],[6.07295,46.46544],[6.07385,46.45964],[6.08668,46.4427],[6.06407,46.41676],[6.09926,46.40768],[6.15016,46.3778],[6.15985,46.37721],[6.16987,46.36759],[6.15738,46.3491],[6.13876,46.33844],[6.1198,46.31157],[6.11697,46.29547],[6.1013,46.28512],[6.11926,46.2634],[6.12247,46.25811],[6.12086,46.25501],[6.12457,46.25098],[6.10144,46.23759],[6.08767,46.24706],[6.07072,46.24085],[6.0633,46.24583],[6.05029,46.23518],[6.04602,46.23127],[6.03342,46.2383],[6.02461,46.23313],[5.97542,46.21525],[5.96515,46.19638],[5.99573,46.18587],[5.98846,46.17046],[5.98188,46.17392],[5.97508,46.15863],[5.9641,46.14412],[5.95574,46.12869]],[[8.65769,47.68928],[8.67508,47.6979],[8.66416,47.71367],[8.70237,47.71453],[8.71773,47.69088],[8.70847,47.68904],[8.68985,47.69552],[8.66837,47.68437],[8.65769,47.68928]],[[8.95861,45.96485],[8.96668,45.98436],[8.97741,45.98317],[8.97604,45.96151],[8.95861,45.96485]]]}},{"type":"Feature","properties":{"id":"DE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[5.71287,54.07228],[6.91025,53.44221],[7.00198,53.32672],[7.19052,53.31866],[7.21679,53.20058],[7.22681,53.18165],[7.17898,53.13817],[7.21694,53.00742],[7.07253,52.81083],[7.05047,52.634],[6.78028,52.65389],[6.7056,52.62722],[6.76611,52.56189],[6.68106,52.55339],[6.70474,52.52123],[6.69719,52.48633],[6.76517,52.46207],[6.95417,52.43832],[6.99041,52.47235],[7.03417,52.40237],[7.07656,52.37717],[7.02703,52.27941],[7.06365,52.23789],[7.03729,52.22695],[6.9897,52.2271],[6.97189,52.20329],[6.83984,52.11728],[6.76117,52.11895],[6.74843,52.08899],[6.68106,52.04884],[6.83035,51.9905],[6.82357,51.96711],[6.72319,51.89518],[6.68386,51.91861],[6.58556,51.89386],[6.50231,51.86313],[6.47179,51.85395],[6.38815,51.87257],[6.40704,51.82771],[6.30593,51.84998],[6.29872,51.86801],[6.21443,51.86801],[6.15349,51.90439],[6.11551,51.89769],[6.16902,51.84094],[6.10337,51.84829],[6.07071,51.86207],[6.00574,51.8337],[5.97046,51.8337],[5.93304,51.82103],[5.97793,51.79805],[5.99441,51.76948],[5.94943,51.74754],[6.04091,51.71821],[6.02767,51.6742],[6.11759,51.65609],[6.09055,51.60564],[6.12401,51.59061],[6.17895,51.53939],[6.20736,51.5199],[6.21724,51.48568],[6.22135,51.44967],[6.20654,51.40049],[6.22632,51.40022],[6.2156,51.38731],[6.2265,51.36058],[6.16702,51.32599],[6.12599,51.27491],[6.07337,51.24289],[6.07889,51.17038],[6.16513,51.19472],[6.18178,51.18709],[6.17448,51.15733],[6.12007,51.14053],[6.01861,51.09387],[5.98248,51.07451],[5.95785,51.03491],[5.93708,51.03346],[5.9134,51.06736],[5.86735,51.05182],[5.87849,51.01969],[5.90493,51.00198],[5.90296,50.97356],[5.95407,50.98826],[5.96325,50.98172],[6.02697,50.98303],[6.01243,50.95502],[6.01758,50.93501],[6.05715,50.92164],[6.09297,50.92066],[6.07486,50.89307],[6.08679,50.87929],[6.08805,50.87223],[6.07693,50.86025],[6.07431,50.84674],[6.05702,50.85179],[6.05623,50.8572],[6.01698,50.84356],[6.0254,50.81651],[6.00462,50.80065],[5.98404,50.80988],[5.97407,50.79899],[5.99574,50.79036],[5.9957,50.78819],[6.0281,50.7743],[6.01866,50.76374],[6.02067,50.75421],[6.03889,50.74618],[6.0326,50.72647],[6.0406,50.71848],[6.04428,50.72861],[6.11707,50.72231],[6.17852,50.6245],[6.26957,50.62444],[6.2476,50.60392],[6.24888,50.59869],[6.24005,50.58732],[6.22581,50.5907],[6.20281,50.56952],[6.17739,50.55875],[6.17802,50.54179],[6.19735,50.53576],[6.19579,50.5313],[6.18716,50.52653],[6.19193,50.5212],[6.20599,50.52089],[6.22335,50.49578],[6.26637,50.50272],[6.30809,50.50058],[6.3465,50.48833],[6.34005,50.46083],[6.37219,50.45397],[6.36852,50.40776],[6.34406,50.37994],[6.3688,50.35898],[6.40785,50.33557],[6.40641,50.32425],[6.35701,50.31139],[6.32488,50.32333],[6.29949,50.30887],[6.28797,50.27458],[6.208,50.25179],[6.16853,50.2234],[6.18364,50.20815],[6.18739,50.1822],[6.14588,50.17106],[6.14132,50.14971],[6.15298,50.14126],[6.1379,50.12964],[6.12055,50.09171],[6.11274,50.05916],[6.13458,50.04141],[6.13044,50.02929],[6.14666,50.02207],[6.13794,50.01466],[6.13273,50.02019],[6.1295,50.01849],[6.13806,50.01056],[6.14948,50.00908],[6.14147,49.99563],[6.1701,49.98518],[6.16466,49.97086],[6.17872,49.9537],[6.18554,49.95622],[6.18045,49.96611],[6.19089,49.96991],[6.19856,49.95053],[6.22094,49.94955],[6.22608,49.929],[6.21882,49.92403],[6.22926,49.92096],[6.23496,49.89972],[6.26146,49.88203],[6.28874,49.87592],[6.29692,49.86685],[6.30963,49.87021],[6.32303,49.85133],[6.32098,49.83728],[6.33585,49.83785],[6.34267,49.84974],[6.36576,49.85032],[6.40022,49.82029],[6.42521,49.81591],[6.42905,49.81091],[6.44131,49.81443],[6.45425,49.81164],[6.47111,49.82263],[6.48718,49.81267],[6.50647,49.80916],[6.51215,49.80124],[6.52121,49.81338],[6.53122,49.80666],[6.52169,49.79787],[6.50534,49.78952],[6.51669,49.78336],[6.51056,49.77515],[6.51828,49.76855],[6.51646,49.75961],[6.50174,49.75292],[6.50193,49.73291],[6.51805,49.72425],[6.51397,49.72058],[6.50261,49.72718],[6.49535,49.72645],[6.49694,49.72205],[6.5042,49.71808],[6.50647,49.71353],[6.49785,49.71118],[6.48014,49.69767],[6.46048,49.69092],[6.44654,49.67799],[6.42937,49.66857],[6.42726,49.66078],[6.43768,49.66021],[6.4413,49.65722],[6.41861,49.61723],[6.39822,49.60081],[6.385,49.59946],[6.37464,49.58886],[6.38342,49.5799],[6.38024,49.57593],[6.36676,49.57813],[6.35825,49.57053],[6.38228,49.55855],[6.38072,49.55171],[6.35666,49.52931],[6.36788,49.50377],[6.36907,49.48931],[6.36778,49.46937],[6.37965,49.4651],[6.40274,49.46546],[6.42957,49.47816],[6.55404,49.42464],[6.533,49.40748],[6.60166,49.36644],[6.59068,49.3528],[6.56596,49.35716],[6.6087,49.30738],[6.66583,49.28065],[6.69419,49.21529],[6.72157,49.22152],[6.73273,49.20593],[6.71137,49.18808],[6.73765,49.16375],[6.78265,49.16793],[6.83392,49.15128],[6.84688,49.15701],[6.84443,49.17314],[6.85954,49.17471],[6.86263,49.18289],[6.85016,49.19354],[6.85115,49.20106],[6.83559,49.21224],[6.85939,49.22376],[6.89289,49.20887],[6.92044,49.22353],[6.93831,49.2223],[6.94028,49.21641],[6.95963,49.203],[6.97273,49.2099],[7.02313,49.18902],[7.03459,49.19096],[7.0274,49.17042],[7.03178,49.15734],[7.04662,49.13724],[7.04409,49.12123],[7.04843,49.11422],[7.05548,49.11185],[7.06642,49.11415],[7.07162,49.1255],[7.09007,49.13094],[7.07859,49.15031],[7.10055,49.15602],[7.11205,49.1524],[7.10384,49.13787],[7.12504,49.14253],[7.1358,49.1282],[7.1593,49.1204],[7.23473,49.12971],[7.29514,49.11426],[7.3195,49.14231],[7.35995,49.14399],[7.3662,49.17308],[7.44052,49.18354],[7.44455,49.16765],[7.49473,49.17],[7.49172,49.13915],[7.53012,49.09818],[7.56416,49.08136],[7.62575,49.07654],[7.63618,49.05428],[7.75948,49.04562],[7.79557,49.06583],[7.86386,49.03499],[7.93641,49.05544],[7.97358,49.03269],[8.04834,49.01366],[8.06679,48.99908],[8.14189,48.97833],[8.22266,48.97627],[8.23348,48.96658],[8.20031,48.95856],[8.19989,48.95825],[8.12813,48.87985],[8.10379,48.81641],[8.06802,48.78957],[8.0326,48.79017],[8.01512,48.7602],[7.97023,48.7589],[7.96812,48.72491],[7.89002,48.66317],[7.84724,48.6468],[7.80003,48.58393],[7.80544,48.55229],[7.80647,48.5104],[7.76833,48.48945],[7.76493,48.45835],[7.72974,48.38538],[7.74561,48.32652],[7.69022,48.30018],[7.6648,48.22219],[7.60004,48.15749],[7.59987,48.14558],[7.57747,48.12233],[7.56966,48.03265],[7.61755,47.99468],[7.61704,47.96285],[7.58236,47.93003],[7.58151,47.89747],[7.5543,47.87784],[7.56365,47.8464],[7.55001,47.82289],[7.52921,47.77747],[7.54761,47.72912],[7.53722,47.71635],[7.51266,47.70197],[7.51915,47.68335],[7.52067,47.66437],[7.53384,47.65115],[7.5591,47.63849],[7.56807,47.63135],[7.57423,47.61628],[7.59378,47.60295],[7.58945,47.59017],[7.60523,47.58519],[7.60459,47.57869],[7.61929,47.57683],[7.64309,47.59151],[7.64213,47.5944],[7.64599,47.59695],[7.67317,47.59146],[7.68229,47.59905],[7.69385,47.60099],[7.67115,47.5871],[7.68904,47.57133],[7.67655,47.56435],[7.63599,47.56382],[7.63421,47.56193],[7.65083,47.54662],[7.66174,47.54554],[7.6656,47.53752],[7.68101,47.53232],[7.69642,47.53297],[7.71961,47.54219],[7.75261,47.54599],[7.79486,47.55691],[7.80098,47.56335],[7.81127,47.56946],[7.81901,47.58798],[7.83346,47.58663],[7.84167,47.58196],[7.86269,47.58808],[7.88664,47.58854],[7.89835,47.58408],[7.90673,47.57674],[7.91161,47.56686],[7.9068,47.56037],[7.91251,47.55031],[7.92058,47.5469],[7.93264,47.54704],[7.94614,47.5436],[7.95682,47.55789],[7.97581,47.55493],[8.00113,47.55616],[8.01984,47.55017],[8.04677,47.55502],[8.06014,47.56362],[8.06993,47.5641],[8.08585,47.55669],[8.1007,47.56474],[8.10409,47.57858],[8.11323,47.58393],[8.13739,47.58411],[8.13823,47.59147],[8.14947,47.59558],[8.1643,47.5936],[8.20193,47.62048],[8.21305,47.62048],[8.22011,47.6181],[8.22614,47.60436],[8.23809,47.61204],[8.26013,47.61527],[8.26403,47.60923],[8.2821,47.612],[8.29789,47.6059],[8.29524,47.5919],[8.30277,47.58607],[8.32773,47.57101],[8.35533,47.57014],[8.38388,47.56576],[8.39597,47.57753],[8.43235,47.56617],[8.49431,47.58107],[8.48949,47.588],[8.46637,47.58389],[8.45578,47.60121],[8.50805,47.61958],[8.51686,47.63476],[8.55756,47.62394],[8.57006,47.61429],[8.56173,47.60063],[8.57586,47.59537],[8.60547,47.61328],[8.59545,47.64298],[8.60701,47.65271],[8.61471,47.64514],[8.60412,47.63735],[8.62049,47.63757],[8.62884,47.65098],[8.60697,47.6653],[8.60628,47.67223],[8.60109,47.67267],[8.57683,47.66158],[8.56141,47.67088],[8.52801,47.66059],[8.5322,47.64687],[8.49656,47.64709],[8.46605,47.64103],[8.4667,47.65747],[8.44711,47.65379],[8.42264,47.66667],[8.41346,47.66676],[8.40473,47.67499],[8.4211,47.68407],[8.40478,47.69791],[8.44807,47.72426],[8.45706,47.74981],[8.48868,47.77215],[8.56814,47.78001],[8.56415,47.80633],[8.61657,47.79998],[8.62408,47.7626],[8.64425,47.76398],[8.65292,47.80066],[8.68086,47.78648],[8.68985,47.75686],[8.71778,47.76571],[8.74572,47.74908],[8.72241,47.7446],[8.70942,47.73034],[8.73671,47.7169],[8.72617,47.69651],[8.72809,47.69282],[8.75856,47.68969],[8.79511,47.67462],[8.79966,47.70222],[8.76965,47.7075],[8.77309,47.72059],[8.80663,47.73821],[8.82002,47.71458],[8.86637,47.70532],[8.85065,47.68209],[8.87383,47.67045],[8.87625,47.65441],[8.89946,47.64769],[8.94093,47.65596],[9.02093,47.6868],[9.09891,47.67801],[9.13845,47.66389],[9.15181,47.66904],[9.1705,47.65513],[9.1755,47.65584],[9.17593,47.65399],[9.18203,47.65598],[9.25619,47.65939],[9.55125,47.53629],[9.72736,47.53457],[9.76748,47.5934],[9.80254,47.59419],[9.82591,47.58158],[9.8189,47.54688],[9.87499,47.52953],[9.87733,47.54688],[9.92407,47.53111],[9.96029,47.53899],[10.00003,47.48216],[10.03859,47.48927],[10.07131,47.45531],[10.09001,47.46005],[10.1052,47.4316],[10.06897,47.40709],[10.09819,47.35724],[10.11805,47.37228],[10.16362,47.36674],[10.17648,47.38889],[10.2127,47.38019],[10.22774,47.38904],[10.23757,47.37609],[10.19998,47.32832],[10.2147,47.31014],[10.17648,47.29149],[10.17531,47.27167],[10.23257,47.27088],[10.33424,47.30813],[10.39851,47.37623],[10.4324,47.38494],[10.4359,47.41183],[10.47446,47.43318],[10.46278,47.47901],[10.44291,47.48453],[10.4324,47.50111],[10.44992,47.5524],[10.43473,47.58394],[10.47329,47.58552],[10.48849,47.54057],[10.56912,47.53584],[10.60337,47.56755],[10.63456,47.5591],[10.68832,47.55752],[10.6965,47.54253],[10.7596,47.53228],[10.77596,47.51729],[10.88814,47.53701],[10.91268,47.51334],[10.86945,47.5015],[10.87061,47.4786],[10.90918,47.48571],[10.93839,47.48018],[10.92437,47.46991],[10.98513,47.42882],[10.97111,47.41617],[10.97111,47.39561],[11.11835,47.39719],[11.12536,47.41222],[11.20482,47.43198],[11.25157,47.43277],[11.22002,47.3964],[11.27844,47.39956],[11.29597,47.42566],[11.33804,47.44937],[11.4175,47.44621],[11.38128,47.47465],[11.4362,47.51413],[11.52618,47.50939],[11.58578,47.52281],[11.58811,47.55515],[11.60681,47.57881],[11.63934,47.59202],[11.84052,47.58354],[11.85572,47.60166],[12.0088,47.62451],[12.02282,47.61033],[12.05788,47.61742],[12.13734,47.60639],[12.17824,47.61506],[12.18145,47.61019],[12.17737,47.60121],[12.18568,47.6049],[12.20398,47.60667],[12.20801,47.61082],[12.19895,47.64085],[12.18507,47.65984],[12.18347,47.66663],[12.16769,47.68167],[12.16217,47.70105],[12.18303,47.70065],[12.22571,47.71776],[12.2542,47.7433],[12.26238,47.73544],[12.24017,47.69534],[12.26004,47.67725],[12.27991,47.68827],[12.336,47.69534],[12.37222,47.68433],[12.43883,47.6977],[12.44117,47.6741],[12.50076,47.62293],[12.53816,47.63553],[12.57438,47.63238],[12.6071,47.6741],[12.7357,47.6787],[12.77777,47.66689],[12.76492,47.64485],[12.82101,47.61493],[12.77427,47.58025],[12.80699,47.54477],[12.84672,47.54556],[12.85256,47.52741],[12.9624,47.47452],[12.98344,47.48716],[12.9998,47.46267],[13.04537,47.49426],[13.03252,47.53373],[13.05355,47.56291],[13.04537,47.58183],[13.06641,47.58577],[13.06407,47.60075],[13.09562,47.63304],[13.07692,47.68814],[13.01382,47.72116],[12.98578,47.7078],[12.92969,47.71094],[12.91333,47.7178],[12.90274,47.72513],[12.91711,47.74026],[12.9353,47.74788],[12.94371,47.76281],[12.93202,47.77302],[12.96311,47.79957],[12.98543,47.82896],[13.00588,47.84374],[12.94163,47.92927],[12.93886,47.94046],[12.93642,47.94436],[12.93419,47.94063],[12.92668,47.93879],[12.91985,47.94069],[12.9211,47.95135],[12.91683,47.95647],[12.87476,47.96195],[12.8549,48.01122],[12.76141,48.07373],[12.74973,48.10885],[12.7617,48.12796],[12.78595,48.12445],[12.80676,48.14979],[12.82673,48.15245],[12.8362,48.15876],[12.836,48.1647],[12.84475,48.16556],[12.87126,48.20318],[12.95306,48.20629],[13.02083,48.25689],[13.0851,48.27711],[13.126,48.27867],[13.18093,48.29577],[13.26039,48.29422],[13.30897,48.31575],[13.40709,48.37292],[13.43929,48.43386],[13.42527,48.45711],[13.45727,48.51092],[13.43695,48.55776],[13.45214,48.56472],[13.46967,48.55157],[13.50663,48.57506],[13.50131,48.58091],[13.51291,48.59023],[13.57535,48.55912],[13.59705,48.57013],[13.62508,48.55501],[13.65186,48.55092],[13.66113,48.53558],[13.72802,48.51208],[13.74816,48.53058],[13.7513,48.5624],[13.76921,48.55324],[13.80519,48.58026],[13.80038,48.59487],[13.82609,48.62345],[13.81901,48.6761],[13.81283,48.68426],[13.81791,48.69832],[13.79337,48.71375],[13.81863,48.73257],[13.82266,48.75544],[13.84023,48.76988],[13.8096,48.77877],[13.78977,48.83319],[13.76994,48.83537],[13.73854,48.88538],[13.67739,48.87886],[13.61624,48.9462],[13.58319,48.96899],[13.50552,48.97441],[13.50221,48.93752],[13.40802,48.98851],[13.39479,49.04812],[13.28242,49.1228],[13.23689,49.11412],[13.20405,49.12303],[13.17019,49.14339],[13.17665,49.16713],[13.05883,49.26259],[13.02952,49.2706],[13.02995,49.30475],[12.95185,49.3419],[12.88138,49.3514],[12.88794,49.3306],[12.84799,49.34184],[12.78001,49.3448],[12.75854,49.3989],[12.70886,49.42437],[12.669,49.42935],[12.64121,49.47628],[12.64782,49.52565],[12.59779,49.53066],[12.56466,49.61438],[12.53544,49.61888],[12.52321,49.64512],[12.52553,49.68415],[12.4462,49.70233],[12.40489,49.76321],[12.46603,49.78882],[12.48256,49.83575],[12.55197,49.92094],[12.47264,49.94222],[12.49908,49.97305],[12.31824,50.05129],[12.26111,50.06331],[12.27433,50.0771],[12.23709,50.10213],[12.2073,50.10315],[12.1917,50.13434],[12.21484,50.16399],[12.19335,50.19997],[12.09287,50.25032],[12.13716,50.27396],[12.10573,50.32086],[12.1247,50.31576],[12.18323,50.32245],[12.20823,50.2729],[12.25119,50.27079],[12.26953,50.25189],[12.24791,50.25525],[12.23943,50.24594],[12.28755,50.22429],[12.28063,50.19544],[12.29232,50.17524],[12.32596,50.17146],[12.33847,50.19432],[12.32445,50.20442],[12.33532,50.22008],[12.33262,50.24259],[12.35871,50.24059],[12.36594,50.28289],[12.40158,50.29521],[12.39924,50.32302],[12.43371,50.32506],[12.43722,50.33774],[12.46928,50.35489],[12.48261,50.34674],[12.49214,50.35228],[12.48707,50.37045],[12.51356,50.39694],[12.67261,50.41949],[12.70731,50.39948],[12.73133,50.42335],[12.73171,50.42709],[12.73435,50.43237],[12.82465,50.45738],[12.9418,50.40906],[12.98433,50.42016],[13.02147,50.44763],[13.02038,50.4734],[13.0236,50.48787],[13.03261,50.50111],[13.03107,50.50982],[13.09407,50.49896],[13.13424,50.51709],[13.19921,50.50048],[13.25158,50.59268],[13.29454,50.57904],[13.32594,50.58009],[13.32264,50.60317],[13.37805,50.627],[13.37388,50.65049],[13.42189,50.61243],[13.46413,50.60102],[13.50339,50.63372],[13.5226,50.64721],[13.53748,50.67654],[13.52474,50.70394],[13.65977,50.73096],[13.70204,50.71771],[13.76316,50.73487],[13.82942,50.7251],[13.89444,50.74142],[13.89375,50.78097],[13.98864,50.8177],[14.02982,50.80662],[14.22331,50.86049],[14.24314,50.88761],[14.27123,50.89386],[14.30098,50.88448],[14.38691,50.89907],[14.39848,50.93866],[14.31422,50.95243],[14.30251,50.96606],[14.32793,50.97379],[14.32353,50.98556],[14.29426,50.97831],[14.28177,50.9787],[14.2769,50.98293],[14.2593,50.98769],[14.30098,51.05515],[14.41835,51.01872],[14.45827,51.03712],[14.49202,51.02286],[14.49154,51.04382],[14.49991,51.04692],[14.50809,51.0427],[14.49873,51.02242],[14.53321,51.01679],[14.53438,51.00374],[14.56272,51.00778],[14.57864,51.00022],[14.58096,50.99398],[14.59907,50.98539],[14.59967,50.97983],[14.59452,50.96453],[14.58151,50.94279],[14.55757,50.92167],[14.58024,50.91443],[14.64802,50.93241],[14.65259,50.90513],[14.64091,50.90054],[14.61842,50.85792],[14.70661,50.84096],[14.79139,50.81438],[14.82803,50.86966],[14.81664,50.88148],[14.89681,50.9422],[14.89252,50.94999],[14.92942,50.99744],[14.95529,51.04552],[14.97938,51.07742],[14.98229,51.11354],[14.99689,51.12205],[14.99079,51.14284],[14.99646,51.14365],[15.00083,51.14974],[14.99414,51.15813],[14.99311,51.16249],[15.0047,51.16874],[15.01242,51.21285],[15.04288,51.28387],[14.98008,51.33449],[14.96899,51.38367],[14.9652,51.44793],[14.94749,51.47155],[14.73219,51.52922],[14.72652,51.53902],[14.73047,51.54606],[14.71125,51.56209],[14.7727,51.61263],[14.75759,51.62318],[14.75392,51.67445],[14.69065,51.70842],[14.66386,51.73282],[14.64625,51.79472],[14.60493,51.80473],[14.59027,51.83636],[14.64563,51.86801],[14.65601,51.88422],[14.69412,51.90234],[14.70601,51.92944],[14.7177,51.94048],[14.72163,51.95188],[14.71836,51.95606],[14.7139,51.95643],[14.70488,51.97679],[14.71339,52.00337],[14.76026,52.06624],[14.72971,52.09167],[14.6917,52.10283],[14.67683,52.13936],[14.70616,52.16927],[14.68344,52.19612],[14.71319,52.22144],[14.70139,52.25038],[14.58149,52.28007],[14.56378,52.33838],[14.55228,52.35264],[14.54423,52.42568],[14.63056,52.48993],[14.60081,52.53116],[14.6289,52.57136],[14.61073,52.59847],[14.22071,52.81175],[14.13806,52.82392],[14.12256,52.84311],[14.15873,52.87715],[14.14056,52.95786],[14.25954,53.00264],[14.35044,53.05829],[14.38679,53.13669],[14.36696,53.16444],[14.37853,53.20405],[14.40301,53.20717],[14.45125,53.26241],[14.44133,53.27427],[14.4215,53.27724],[14.35209,53.49506],[14.3273,53.50587],[14.30416,53.55499],[14.31904,53.61581],[14.2853,53.63392],[14.28477,53.65955],[14.27133,53.66613],[14.2836,53.67721],[14.26782,53.69866],[14.27249,53.74464],[14.21323,53.8664],[14.20823,53.90776],[14.18544,53.91258],[14.20647,53.91671],[14.22634,53.9291],[14.20811,54.12784],[13.93395,54.84044],[12.85844,54.82438],[11.90309,54.38543],[11.00303,54.63689],[10.31111,54.65968],[10.16755,54.73883],[9.89314,54.84171],[9.73563,54.8247],[9.61187,54.85548],[9.62734,54.88057],[9.58937,54.88785],[9.4659,54.83131],[9.43155,54.82586],[9.41213,54.84254],[9.38532,54.83968],[9.36496,54.81749],[9.33849,54.80233],[9.32771,54.80602],[9.2474,54.8112],[9.23445,54.83432],[9.24631,54.84726],[9.20571,54.85841],[9.14275,54.87421],[9.04629,54.87249],[8.92795,54.90452],[8.81178,54.90518],[8.76387,54.8948],[8.63979,54.91069],[8.55769,54.91837],[8.45719,55.06747],[8.02459,55.09613],[5.71287,54.07228]]],[[[8.65769,47.68928],[8.66837,47.68437],[8.68985,47.69552],[8.70847,47.68904],[8.71773,47.69088],[8.70237,47.71453],[8.66416,47.71367],[8.67508,47.6979],[8.65769,47.68928]]]]}},{"type":"Feature","properties":{"id":"BE"},"geometry":{"type":"MultiPolygon","coordinates":[[[[2.18458,51.52087],[2.55904,51.07014],[2.57457,51.00203],[2.60753,50.98323],[2.63074,50.94746],[2.59093,50.91751],[2.60564,50.90167],[2.61105,50.86529],[2.598,50.84993],[2.62006,50.84451],[2.6341,50.81301],[2.65676,50.81322],[2.67075,50.82201],[2.68036,50.81322],[2.71671,50.81358],[2.73344,50.78477],[2.76177,50.77115],[2.75662,50.76284],[2.77816,50.75101],[2.78988,50.7283],[2.81313,50.71604],[2.8483,50.72276],[2.86391,50.70865],[2.86985,50.7033],[2.87937,50.70298],[2.88504,50.70656],[2.90069,50.69263],[2.91036,50.6939],[2.90873,50.702],[2.92313,50.70309],[2.92965,50.72548],[2.94407,50.73183],[2.93944,50.74335],[2.95019,50.75138],[2.96778,50.75242],[3.00537,50.76588],[3.01922,50.77335],[3.03428,50.76949],[3.0402,50.77593],[3.05617,50.78021],[3.08552,50.7731],[3.10614,50.78303],[3.11144,50.79454],[3.11987,50.79188],[3.12612,50.78637],[3.15017,50.79031],[3.16476,50.76843],[3.17161,50.75866],[3.17994,50.75451],[3.18318,50.7503],[3.1877,50.74029],[3.20118,50.73561],[3.19041,50.72523],[3.21131,50.71735],[3.20478,50.71192],[3.20869,50.71102],[3.21332,50.71311],[3.22199,50.71015],[3.23152,50.71472],[3.23779,50.71146],[3.24547,50.71314],[3.25169,50.70637],[3.261,50.70203],[3.26141,50.69151],[3.25328,50.68987],[3.26525,50.67679],[3.24371,50.66959],[3.2405,50.65855],[3.2729,50.60718],[3.28575,50.52724],[3.32859,50.51097],[3.38516,50.49579],[3.4491,50.50753],[3.47385,50.53397],[3.52334,50.52393],[3.49622,50.49885],[3.49998,50.48663],[3.52991,50.49573],[3.54218,50.49571],[3.5683,50.50192],[3.58489,50.48939],[3.61314,50.49649],[3.64426,50.46275],[3.66153,50.45165],[3.67494,50.40239],[3.67262,50.38663],[3.65709,50.36873],[3.66976,50.34563],[3.68453,50.32771],[3.71009,50.30305],[3.70917,50.32217],[3.72857,50.31094],[3.73911,50.34809],[3.78092,50.3534],[3.82096,50.34622],[3.84365,50.35336],[3.89991,50.32708],[3.90888,50.32848],[3.96599,50.34939],[3.96812,50.3419],[4.0268,50.35793],[4.07104,50.32397],[4.07811,50.32072],[4.07974,50.31],[4.10172,50.31364],[4.10957,50.30234],[4.11962,50.30493],[4.13665,50.25609],[4.16808,50.25786],[4.15524,50.2833],[4.17407,50.28536],[4.17861,50.27443],[4.20651,50.27333],[4.21945,50.25539],[4.15524,50.21103],[4.1634,50.1892],[4.12985,50.13094],[4.19829,50.13496],[4.20209,50.10081],[4.23351,50.0697],[4.18965,50.04942],[4.16233,50.04944],[4.1318,50.01926],[4.16158,49.99157],[4.14239,49.98034],[4.20532,49.95803],[4.31963,49.97043],[4.35051,49.95315],[4.41822,49.94669],[4.43488,49.94122],[4.51098,49.94659],[4.5414,49.96911],[4.55091,49.96861],[4.68695,49.99685],[4.70154,50.09502],[4.75134,50.11192],[4.763,50.13642],[4.80634,50.15265],[4.82438,50.16878],[4.82359,50.16155],[4.83279,50.15331],[4.88602,50.15182],[4.8382,50.06738],[4.79041,49.9594],[4.88529,49.9236],[4.85134,49.86457],[4.86965,49.82271],[4.85464,49.78995],[4.96714,49.79872],[5.10158,49.75405],[5.16295,49.69437],[5.26232,49.69456],[5.31465,49.66846],[5.33039,49.6555],[5.30214,49.63055],[5.3137,49.61225],[5.33851,49.61599],[5.34837,49.62889],[5.3974,49.61596],[5.43713,49.5707],[5.46734,49.52648],[5.4475,49.51718],[5.46541,49.49825],[5.55885,49.53074],[5.60531,49.51016],[5.64345,49.54693],[5.64333,49.5507],[5.75627,49.54089],[5.7577,49.55915],[5.77435,49.56298],[5.79195,49.55228],[5.81838,49.54777],[5.84143,49.5533],[5.84692,49.55663],[5.8424,49.56082],[5.87256,49.57539],[5.86986,49.58756],[5.84971,49.58674],[5.84826,49.5969],[5.8762,49.60898],[5.87609,49.62047],[5.88393,49.62802],[5.88552,49.63507],[5.90599,49.63853],[5.90164,49.6511],[5.9069,49.66377],[5.86175,49.67862],[5.86527,49.69291],[5.88677,49.70951],[5.86503,49.72739],[5.84193,49.72161],[5.82562,49.72395],[5.83149,49.74729],[5.82245,49.75048],[5.78871,49.7962],[5.75409,49.79239],[5.74953,49.81428],[5.74364,49.82058],[5.74844,49.82435],[5.7404,49.83452],[5.74108,49.83922],[5.74975,49.83933],[5.74885,49.84542],[5.76044,49.84545],[5.74567,49.85368],[5.75861,49.85631],[5.75438,49.87146],[5.78415,49.87922],[5.73621,49.89796],[5.77314,49.93646],[5.77291,49.96056],[5.80833,49.96451],[5.81163,49.97142],[5.83467,49.97823],[5.83968,49.9892],[5.82331,49.99662],[5.81866,50.01286],[5.8551,50.02683],[5.86904,50.04614],[5.85474,50.06342],[5.8857,50.07824],[5.89488,50.11476],[5.95929,50.13295],[5.96453,50.17259],[6.02488,50.18283],[6.03093,50.16362],[6.06406,50.15344],[6.08577,50.17246],[6.12028,50.16374],[6.1137,50.13668],[6.1379,50.12964],[6.15298,50.14126],[6.14132,50.14971],[6.14588,50.17106],[6.18739,50.1822],[6.18364,50.20815],[6.16853,50.2234],[6.208,50.25179],[6.28797,50.27458],[6.29949,50.30887],[6.32488,50.32333],[6.35701,50.31139],[6.40641,50.32425],[6.40785,50.33557],[6.3688,50.35898],[6.34406,50.37994],[6.36852,50.40776],[6.37219,50.45397],[6.34005,50.46083],[6.3465,50.48833],[6.30809,50.50058],[6.26637,50.50272],[6.22335,50.49578],[6.20599,50.52089],[6.19193,50.5212],[6.18716,50.52653],[6.19579,50.5313],[6.19735,50.53576],[6.17802,50.54179],[6.17739,50.55875],[6.20281,50.56952],[6.22581,50.5907],[6.24005,50.58732],[6.24888,50.59869],[6.2476,50.60392],[6.26957,50.62444],[6.17852,50.6245],[6.11707,50.72231],[6.04428,50.72861],[6.0406,50.71848],[6.0326,50.72647],[6.03889,50.74618],[6.02067,50.75421],[5.97545,50.75441],[5.95942,50.7622],[5.89132,50.75124],[5.89129,50.75125],[5.88734,50.77092],[5.84888,50.75448],[5.84548,50.76542],[5.80673,50.7558],[5.77513,50.78308],[5.76533,50.78159],[5.74356,50.7691],[5.73904,50.75674],[5.72216,50.76398],[5.69469,50.75529],[5.68091,50.75804],[5.70107,50.7827],[5.69332,50.79763],[5.70118,50.80764],[5.68799,50.81183],[5.65486,50.82074],[5.64009,50.84742],[5.64504,50.87107],[5.668,50.88048],[5.67924,50.88069],[5.69858,50.91046],[5.71626,50.90796],[5.72644,50.91167],[5.72679,50.92289],[5.74644,50.94723],[5.75927,50.95601],[5.74752,50.96202],[5.72875,50.95428],[5.71864,50.96092],[5.7352,50.97588],[5.76147,50.99452],[5.77631,51.0246],[5.75961,51.03113],[5.77258,51.06196],[5.79835,51.05834],[5.79903,51.09371],[5.82921,51.09328],[5.83226,51.10585],[5.8109,51.10861],[5.80798,51.11661],[5.85508,51.14445],[5.82417,51.16831],[5.81412,51.15908],[5.80498,51.16268],[5.77717,51.15122],[5.77735,51.17845],[5.74617,51.18928],[5.7082,51.18193],[5.65528,51.18736],[5.65145,51.19788],[5.5603,51.22249],[5.5569,51.26544],[5.515,51.29462],[5.48476,51.30053],[5.46519,51.2849],[5.4407,51.28169],[5.41672,51.26248],[5.347,51.27502],[5.33886,51.26314],[5.29716,51.26104],[5.26461,51.26693],[5.23814,51.26064],[5.22542,51.26888],[5.24244,51.30495],[5.2002,51.32243],[5.16222,51.31035],[5.13377,51.31592],[5.13105,51.34791],[5.07102,51.39469],[5.10456,51.43163],[5.07891,51.4715],[5.04774,51.47022],[5.03281,51.48679],[5.0106,51.47167],[5.00393,51.44406],[4.92152,51.39487],[4.90016,51.41404],[4.84988,51.41502],[4.78941,51.41102],[4.77229,51.41337],[4.76577,51.43046],[4.78314,51.43319],[4.82946,51.4213],[4.82409,51.44736],[4.84139,51.4799],[4.78803,51.50284],[4.77321,51.50529],[4.74578,51.48937],[4.72935,51.48424],[4.65442,51.42352],[4.57489,51.4324],[4.53521,51.4243],[4.52846,51.45002],[4.54675,51.47265],[4.5388,51.48184],[4.47736,51.4778],[4.38122,51.44905],[4.39747,51.43316],[4.38064,51.41965],[4.43777,51.36989],[4.39292,51.35547],[4.34086,51.35738],[4.33265,51.37687],[4.21923,51.37443],[4.24024,51.35371],[4.16721,51.29348],[4.05165,51.24171],[4.01957,51.24504],[3.97889,51.22537],[3.90125,51.20371],[3.78783,51.2151],[3.78999,51.25766],[3.58939,51.30064],[3.51502,51.28697],[3.52698,51.2458],[3.43488,51.24135],[3.41704,51.25933],[3.38289,51.27331],[3.35847,51.31572],[3.38696,51.33436],[3.36263,51.37112],[2.65913,51.8564],[2.18458,51.52087]]],[[[4.91493,51.4353],[4.92652,51.43329],[4.92952,51.42984],[4.93986,51.43064],[4.94265,51.44003],[4.93471,51.43861],[4.93416,51.44185],[4.94025,51.44193],[4.93544,51.44634],[4.92879,51.44161],[4.92815,51.43856],[4.92566,51.44273],[4.92811,51.4437],[4.92287,51.44741],[4.91811,51.44621],[4.92227,51.44252],[4.91935,51.43634],[4.91493,51.4353]]],[[[4.93295,51.44945],[4.93909,51.44632],[4.9524,51.45014],[4.95244,51.45207],[4.93295,51.44945]]]]}},{"type":"Feature","properties":{"id":"AU-NSW"},"geometry":{"type":"Polygon","coordinates":[[[140.99934,-28.99903],[141.00268,-34.02172],[141.5377,-34.18902],[141.71349,-34.0924],[142.0211,-34.12651],[142.22023,-34.18334],[142.24495,-34.3014],[142.37404,-34.34563],[142.50999,-34.74267],[142.61711,-34.77765],[142.76268,-34.56871],[143.34221,-34.79344],[143.3271,-34.99618],[143.39027,-35.18047],[143.57155,-35.20741],[143.56743,-35.33634],[144.08241,-35.57238],[144.72487,-36.11229],[144.74298,-36.10785],[144.7477,-36.12075],[144.77182,-36.11638],[144.77697,-36.12872],[144.94896,-36.05236],[144.9778,-35.86673],[145.1165,-35.81774],[145.34447,-35.86005],[145.51872,-35.80417],[145.79578,-35.98314],[145.90702,-35.9655],[146.02203,-35.99884],[146.36894,-36.03571],[146.42387,-35.96794],[146.58645,-35.97383],[146.85097,-36.08788],[146.90463,-36.08351],[146.91329,-36.11069],[147.00496,-36.08455],[147.03569,-36.11541],[147.10503,-36.00683],[147.31926,-36.05458],[147.40991,-35.93298],[147.55823,-35.99772],[147.71083,-35.92992],[147.9915,-36.04909],[148.04573,-36.39248],[148.20366,-36.59782],[148.10753,-36.79272],[148.19405,-36.79602],[150.19141,-37.59274],[155.32243,-27.36405],[153.54177,-28.1687],[153.53414,-28.17635],[153.48092,-28.15509],[153.36639,-28.24708],[153.18121,-28.25289],[153.10649,-28.35817],[152.85209,-28.31208],[152.74738,-28.36315],[152.60902,-28.28185],[152.58018,-28.33822],[152.50345,-28.24663],[152.37899,-28.3633],[151.95173,-28.53702],[152.0131,-28.66091],[152.07052,-28.68832],[152.00511,-28.90547],[151.7777,-28.9606],[151.72552,-28.86683],[151.55248,-28.94858],[151.39318,-29.17186],[151.31092,-29.16115],[151.27508,-28.94017],[150.74499,-28.63446],[150.43737,-28.66098],[150.37021,-28.62039],[150.36206,-28.5904],[150.32043,-28.55757],[150.30022,-28.54807],[150.29159,-28.53672],[150.28146,-28.5426],[149.67519,-28.62723],[149.58044,-28.57056],[149.48705,-28.58202],[149.37788,-28.68628],[149.19248,-28.77479],[148.99108,-28.97285],[148.94654,-28.9989],[140.99934,-28.99903]],[[148.76247,-35.49504],[148.80951,-35.30698],[149.12159,-35.1241],[149.19746,-35.18502],[149.18956,-35.20157],[149.2469,-35.2285],[149.23488,-35.24336],[149.39418,-35.30362],[149.39796,-35.32435],[149.35058,-35.3518],[149.33719,-35.33976],[149.25136,-35.33024],[149.2057,-35.34732],[149.13429,-35.45338],[149.15283,-35.50566],[149.12983,-35.55288],[149.14219,-35.59337],[149.07936,-35.58193],[149.09549,-35.6411],[149.09824,-35.81223],[149.04811,-35.91684],[148.96194,-35.8971],[148.89602,-35.82504],[148.89431,-35.75095],[148.8768,-35.715],[148.85723,-35.76043],[148.78891,-35.69995],[148.76247,-35.49504]],[[150.59126,-35.17225],[150.60036,-35.1672],[150.60534,-35.15429],[150.59564,-35.15197],[150.59384,-35.14376],[150.66156,-35.11779],[150.70087,-35.12312],[150.78069,-35.10852],[150.7431,-35.20971],[150.59307,-35.18389],[150.59126,-35.17225]]]}},{"type":"Feature","properties":{"id":"BQ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-68.90012,12.62309],[-68.33524,11.78151],[-68.01417,11.77722],[-67.89186,12.4116],[-68.90012,12.62309]]],[[[-63.58819,17.61311],[-63.22932,17.32592],[-63.11114,17.23125],[-62.76692,17.64353],[-63.07669,17.79659],[-63.29212,17.90532],[-63.58819,17.61311]]]]}},{"type":"Feature","properties":{"id":"SJ"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-9.46764,70.6182],[-7.10917,70.97072],[-8.64261,71.45615],[-9.46764,70.6182]]],[[[5.93257,80.704],[16.30346,73.63706],[30.54194,75.9202],[36.27514,81.36349],[5.93257,80.704]]]]}},{"type":"Feature","properties":{"id":"OM-MU"},"geometry":{"type":"MultiPolygon","coordinates":[[[[55.81777,26.18798],[56.08666,26.05038],[56.15498,26.06828],[56.19189,25.97475],[56.17991,25.95217],[56.16545,25.94376],[56.17871,25.9047],[56.13963,25.82765],[56.16717,25.72668],[56.15564,25.6625],[56.18363,25.65508],[56.20473,25.61119],[56.25365,25.60211],[56.26636,25.60643],[56.26703,25.60805],[56.2615,25.61053],[56.25939,25.61745],[56.2645,25.62438],[56.82555,25.7713],[56.70052,26.88164],[55.81777,26.18798]]],[[[56.20838,25.25668],[56.20872,25.24104],[56.24341,25.22867],[56.27628,25.23404],[56.34438,25.26653],[56.35172,25.30681],[56.3111,25.30107],[56.3005,25.31815],[56.26062,25.33108],[56.24357,25.31827],[56.26003,25.29417],[56.24465,25.27505],[56.20838,25.25668]],[[56.27086,25.26128],[56.2716,25.27916],[56.28102,25.28486],[56.29379,25.2754],[56.28423,25.26344],[56.27086,25.26128]]]]}},{"type":"Feature","properties":{"id":"IN-PY"},"geometry":{"type":"MultiPolygon","coordinates":[[[[75.25839,11.52644],[75.26869,11.50222],[75.53649,11.69283],[75.54568,11.70409],[75.53778,11.71401],[75.55494,11.72283],[75.5419,11.73569],[75.54396,11.75998],[75.53271,11.7593],[75.5401,11.74132],[75.52654,11.7288],[75.53074,11.70527],[75.25839,11.52644]]],[[[79.59525,11.86735],[79.68109,11.80048],[79.99831,11.6993],[80.10406,11.97753],[79.64229,12.06752],[79.59525,11.86735]]],[[[79.71902,10.94152],[79.76417,10.89197],[79.8167,10.88556],[79.8167,10.82925],[80.05943,10.8306],[80.05393,10.99781],[79.7985,10.98955],[79.74803,11.0027],[79.71902,10.94152]]],[[[82.18305,16.73674],[82.18674,16.72975],[82.26802,16.70509],[82.60962,16.71347],[82.62817,16.73846],[82.31403,16.72811],[82.30562,16.748],[82.29154,16.72531],[82.24262,16.72219],[82.21713,16.73353],[82.22853,16.76123],[82.20897,16.74931],[82.20562,16.73526],[82.18305,16.73674]]]]}},{"type":"Feature","properties":{"id":"CN-GD"},"geometry":{"type":"Polygon","coordinates":[[[108.26073,20.07614],[111.04979,20.2622],[117.76968,23.10828],[117.19253,23.61904],[117.16206,23.65183],[117.11417,23.65513],[117.0637,23.69247],[117.05065,23.69137],[117.04825,23.73789],[117.03743,23.76751],[117.02181,23.82994],[117.00662,23.86134],[116.96156,23.86134],[116.97246,23.88167],[116.95263,23.9173],[116.97229,23.93244],[116.97933,24.00099],[116.95238,24.01651],[116.92251,24.12043],[116.99701,24.18935],[116.93195,24.22238],[116.92886,24.28812],[116.91564,24.28702],[116.9086,24.32473],[116.85075,24.42214],[116.84131,24.48418],[116.74879,24.5529],[116.80269,24.67821],[116.58931,24.6506],[116.51859,24.60332],[116.48735,24.67977],[116.36873,24.80933],[116.41267,24.84687],[116.35808,24.88861],[116.3404,24.83161],[116.25543,24.80247],[116.20428,24.85933],[116.08257,24.8489],[115.97047,24.91773],[115.88722,24.94154],[115.89889,24.87833],[115.86696,24.86743],[115.82405,24.91539],[115.76362,24.79109],[115.76499,24.71221],[115.79761,24.70098],[115.78559,24.63703],[115.84327,24.5671],[115.69358,24.54056],[115.65444,24.61799],[115.56758,24.63016],[115.4845,24.75587],[115.40657,24.79234],[115.35507,24.74121],[115.27988,24.75774],[115.10822,24.66792],[115.06599,24.70753],[114.93826,24.64826],[114.92059,24.69334],[114.87596,24.56632],[114.74464,24.61986],[114.69932,24.5315],[114.65194,24.58771],[114.41659,24.4873],[114.33094,24.61736],[114.15824,24.64857],[114.32338,24.75743],[114.40269,24.90449],[114.39239,24.94621],[114.53624,25.04377],[114.7455,25.12539],[114.66207,25.20121],[114.74069,25.23537],[114.70739,25.32959],[114.6655,25.32633],[114.54465,25.42622],[114.3881,25.32277],[114.30707,25.33844],[114.29403,25.29561],[114.18331,25.3192],[114.02675,25.2568],[113.99963,25.4442],[113.94126,25.44668],[113.78334,25.32385],[113.74093,25.36496],[113.58009,25.31951],[113.38542,25.40761],[113.27333,25.52276],[113.14561,25.47814],[113.12141,25.42436],[113.01721,25.34976],[112.96897,25.35504],[112.91636,25.30306],[112.84984,25.34278],[112.8713,25.24857],[112.98923,25.24857],[113.02854,25.20059],[112.96829,25.16998],[113.00571,24.93999],[112.7798,24.89328],[112.70393,25.08622],[112.65174,25.13751],[112.56488,25.12912],[112.357,25.18288],[112.18912,25.18474],[112.15049,25.02323],[112.11238,24.96722],[112.17143,24.92909],[112.16062,24.86915],[112.01728,24.74714],[111.93592,24.69568],[111.93111,24.61456],[112.01213,24.52494],[111.98123,24.46933],[112.03617,24.41104],[112.06277,24.33677],[111.89111,24.21487],[111.87171,24.10978],[111.93729,23.98617],[111.90553,23.94797],[111.85214,23.94923],[111.80245,23.80827],[111.65104,23.83819],[111.61422,23.73129],[111.66023,23.71338],[111.60744,23.64122],[111.4817,23.63084],[111.47621,23.54038],[111.42179,23.46875],[111.39364,23.473],[111.3521,23.2866],[111.38179,23.215],[111.37098,23.08968],[111.43621,23.0464],[111.36325,22.97277],[111.35776,22.89768],[111.19297,22.74056],[111.05778,22.73819],[111.08456,22.69638],[111.05066,22.65076],[110.95787,22.64229],[110.94903,22.61345],[110.89977,22.61512],[110.87934,22.58691],[110.82767,22.58889],[110.7972,22.56115],[110.75652,22.59016],[110.74184,22.46434],[110.6809,22.48242],[110.70871,22.38071],[110.78664,22.28846],[110.76261,22.2748],[110.72193,22.29719],[110.64159,22.2346],[110.67317,22.17659],[110.62442,22.15274],[110.56657,22.19773],[110.49173,22.14575],[110.42263,22.21382],[110.37586,22.1655],[110.34479,22.1971],[110.31869,22.16022],[110.35938,22.12253],[110.34603,22.03433],[110.35642,22.01046],[110.35062,21.97751],[110.38513,21.95506],[110.3689,21.93667],[110.39234,21.91199],[110.3847,21.89296],[110.32796,21.8916],[110.29123,21.91836],[110.24728,21.87703],[110.19613,21.89996],[110.12317,21.90227],[110.05373,21.86078],[109.99983,21.88141],[109.94662,21.85074],[109.92216,21.71198],[109.90087,21.65798],[109.79684,21.63357],[109.76534,21.67617],[109.74122,21.60556],[109.78912,21.47351],[108.26073,20.07614]],[[113.52659,22.18271],[113.53552,22.20607],[113.53301,22.21235],[113.53593,22.2137],[113.54093,22.21314],[113.54333,22.21688],[113.5508,22.21672],[113.56865,22.20973],[113.57123,22.20416],[113.60504,22.20464],[113.63011,22.10782],[113.57191,22.07696],[113.54839,22.10909],[113.54942,22.14519],[113.54093,22.15497],[113.52659,22.18271]],[[113.81621,22.2163],[113.86771,22.42972],[114.03113,22.5065],[114.05615,22.50252],[114.05812,22.51148],[114.06413,22.51681],[114.07424,22.51874],[114.07817,22.52997],[114.08606,22.53276],[114.09048,22.53716],[114.09692,22.53435],[114.10413,22.53588],[114.11134,22.52924],[114.11569,22.53122],[114.11659,22.53402],[114.12018,22.53503],[114.12477,22.53965],[114.12775,22.53957],[114.13089,22.54197],[114.1343,22.54157],[114.13831,22.54351],[114.14455,22.54113],[114.14867,22.54185],[114.14835,22.54367],[114.15198,22.54738],[114.1496,22.54827],[114.15228,22.55481],[114.15633,22.55455],[114.1609,22.56202],[114.16408,22.55885],[114.16779,22.56143],[114.17176,22.55956],[114.17683,22.5599],[114.17818,22.55542],[114.18685,22.55475],[114.20873,22.55687],[114.21916,22.55493],[114.22418,22.55086],[114.22693,22.54801],[114.22538,22.54549],[114.22841,22.5409],[114.25154,22.55977],[114.44998,22.55977],[114.50148,22.15017],[113.92195,22.13873],[113.83338,22.1826],[113.81621,22.2163]]]}},{"type":"Feature","properties":{"id":"BE-WAL"},"geometry":{"type":"MultiPolygon","coordinates":[[[[2.84206,50.75155],[2.85301,50.74061],[2.84219,50.73572],[2.85258,50.72453],[2.86967,50.71302],[2.86391,50.70865],[2.86985,50.7033],[2.87937,50.70298],[2.88504,50.70656],[2.90069,50.69263],[2.91036,50.6939],[2.90873,50.702],[2.92313,50.70309],[2.92965,50.72548],[2.94407,50.73183],[2.93944,50.74335],[2.95019,50.75138],[2.96778,50.75242],[3.00537,50.76588],[3.01922,50.77335],[3.02141,50.77666],[3.02943,50.77877],[3.02947,50.78021],[3.01415,50.78423],[3.00866,50.79804],[3.01514,50.80268],[3.01085,50.80772],[3.00231,50.80899],[2.99986,50.81084],[2.96012,50.79777],[2.93686,50.79375],[2.94755,50.79101],[2.96266,50.77354],[2.94832,50.77294],[2.91678,50.76358],[2.92365,50.75633],[2.8882,50.75331],[2.87683,50.76149],[2.87936,50.75473],[2.87073,50.75418],[2.86987,50.75991],[2.85584,50.75763],[2.84206,50.75155]]],[[[3.17994,50.75451],[3.18318,50.7503],[3.1877,50.74029],[3.20118,50.73561],[3.19041,50.72523],[3.21131,50.71735],[3.20478,50.71192],[3.20869,50.71102],[3.21332,50.71311],[3.22199,50.71015],[3.23152,50.71472],[3.23779,50.71146],[3.24547,50.71314],[3.25169,50.70637],[3.261,50.70203],[3.26141,50.69151],[3.25328,50.68987],[3.26525,50.67679],[3.24371,50.66959],[3.2405,50.65855],[3.2729,50.60718],[3.28575,50.52724],[3.32859,50.51097],[3.38516,50.49579],[3.4491,50.50753],[3.47385,50.53397],[3.52334,50.52393],[3.49622,50.49885],[3.49998,50.48663],[3.52991,50.49573],[3.54218,50.49571],[3.5683,50.50192],[3.58489,50.48939],[3.61314,50.49649],[3.64426,50.46275],[3.66153,50.45165],[3.67494,50.40239],[3.67262,50.38663],[3.65709,50.36873],[3.66976,50.34563],[3.68453,50.32771],[3.71009,50.30305],[3.70917,50.32217],[3.72857,50.31094],[3.73911,50.34809],[3.78092,50.3534],[3.82096,50.34622],[3.84365,50.35336],[3.89991,50.32708],[3.90888,50.32848],[3.96599,50.34939],[3.96812,50.3419],[4.0268,50.35793],[4.07104,50.32397],[4.07811,50.32072],[4.07974,50.31],[4.10172,50.31364],[4.10957,50.30234],[4.11962,50.30493],[4.13665,50.25609],[4.16808,50.25786],[4.15524,50.2833],[4.17407,50.28536],[4.17861,50.27443],[4.20651,50.27333],[4.21945,50.25539],[4.15524,50.21103],[4.1634,50.1892],[4.12985,50.13094],[4.19829,50.13496],[4.20209,50.10081],[4.23351,50.0697],[4.18965,50.04942],[4.16233,50.04944],[4.1318,50.01926],[4.16158,49.99157],[4.14239,49.98034],[4.20532,49.95803],[4.31963,49.97043],[4.35051,49.95315],[4.41822,49.94669],[4.43488,49.94122],[4.51098,49.94659],[4.5414,49.96911],[4.55091,49.96861],[4.68695,49.99685],[4.70154,50.09502],[4.75134,50.11192],[4.763,50.13642],[4.80634,50.15265],[4.82438,50.16878],[4.82359,50.16155],[4.83279,50.15331],[4.88602,50.15182],[4.8382,50.06738],[4.79041,49.9594],[4.88529,49.9236],[4.85134,49.86457],[4.86965,49.82271],[4.85464,49.78995],[4.96714,49.79872],[5.10158,49.75405],[5.16295,49.69437],[5.26232,49.69456],[5.31465,49.66846],[5.33039,49.6555],[5.30214,49.63055],[5.3137,49.61225],[5.33851,49.61599],[5.34837,49.62889],[5.3974,49.61596],[5.43713,49.5707],[5.46734,49.52648],[5.4475,49.51718],[5.46541,49.49825],[5.55885,49.53074],[5.60531,49.51016],[5.64345,49.54693],[5.64333,49.5507],[5.75627,49.54089],[5.7577,49.55915],[5.77435,49.56298],[5.79195,49.55228],[5.81838,49.54777],[5.84143,49.5533],[5.84692,49.55663],[5.8424,49.56082],[5.87256,49.57539],[5.86986,49.58756],[5.84971,49.58674],[5.84826,49.5969],[5.8762,49.60898],[5.87609,49.62047],[5.88393,49.62802],[5.88552,49.63507],[5.90599,49.63853],[5.90164,49.6511],[5.9069,49.66377],[5.86175,49.67862],[5.86527,49.69291],[5.88677,49.70951],[5.86503,49.72739],[5.84193,49.72161],[5.82562,49.72395],[5.83149,49.74729],[5.82245,49.75048],[5.78871,49.7962],[5.75409,49.79239],[5.74953,49.81428],[5.74364,49.82058],[5.74844,49.82435],[5.7404,49.83452],[5.74108,49.83922],[5.74975,49.83933],[5.74885,49.84542],[5.76044,49.84545],[5.74567,49.85368],[5.75861,49.85631],[5.75438,49.87146],[5.78415,49.87922],[5.73621,49.89796],[5.77314,49.93646],[5.77291,49.96056],[5.80833,49.96451],[5.81163,49.97142],[5.83467,49.97823],[5.83968,49.9892],[5.82331,49.99662],[5.81866,50.01286],[5.8551,50.02683],[5.86904,50.04614],[5.85474,50.06342],[5.8857,50.07824],[5.89488,50.11476],[5.95929,50.13295],[5.96453,50.17259],[6.02488,50.18283],[6.03093,50.16362],[6.06406,50.15344],[6.08577,50.17246],[6.12028,50.16374],[6.1137,50.13668],[6.1379,50.12964],[6.15298,50.14126],[6.14132,50.14971],[6.14588,50.17106],[6.18739,50.1822],[6.18364,50.20815],[6.16853,50.2234],[6.208,50.25179],[6.28797,50.27458],[6.29949,50.30887],[6.32488,50.32333],[6.35701,50.31139],[6.40641,50.32425],[6.40785,50.33557],[6.3688,50.35898],[6.34406,50.37994],[6.36852,50.40776],[6.37219,50.45397],[6.34005,50.46083],[6.3465,50.48833],[6.30809,50.50058],[6.26637,50.50272],[6.22335,50.49578],[6.20599,50.52089],[6.19193,50.5212],[6.18716,50.52653],[6.19579,50.5313],[6.19735,50.53576],[6.17802,50.54179],[6.17739,50.55875],[6.20281,50.56952],[6.22581,50.5907],[6.24005,50.58732],[6.24888,50.59869],[6.2476,50.60392],[6.26957,50.62444],[6.17852,50.6245],[6.11707,50.72231],[6.04428,50.72861],[6.0406,50.71848],[6.0326,50.72647],[6.03889,50.74618],[6.02067,50.75421],[5.97545,50.75441],[5.95942,50.7622],[5.89132,50.75124],[5.91261,50.7358],[5.883,50.70999],[5.86584,50.71689],[5.81382,50.71521],[5.80018,50.73683],[5.77331,50.75052],[5.74945,50.74481],[5.73992,50.75578],[5.72095,50.74634],[5.68731,50.75312],[5.68091,50.75804],[5.70107,50.7827],[5.69332,50.79763],[5.70118,50.80764],[5.68799,50.81183],[5.68705,50.80381],[5.67315,50.80783],[5.65675,50.80593],[5.63675,50.78662],[5.54878,50.75931],[5.52311,50.75888],[5.52234,50.74395],[5.47883,50.72314],[5.46544,50.73982],[5.45359,50.72281],[5.41909,50.71847],[5.38982,50.74791],[5.37634,50.74243],[5.25292,50.71515],[5.2361,50.7252],[5.19378,50.71673],[5.16975,50.72493],[5.18108,50.70482],[5.15636,50.69335],[5.12048,50.70809],[5.06924,50.70732],[5.04718,50.73868],[5.0059,50.76512],[4.9841,50.76974],[4.9259,50.74335],[4.90625,50.74981],[4.90754,50.76909],[4.83527,50.76333],[4.83887,50.77706],[4.81312,50.77625],[4.7933,50.79774],[4.7466,50.80701],[4.72,50.79112],[4.66245,50.79264],[4.64301,50.79926],[4.6379,50.77652],[4.64635,50.76105],[4.65575,50.75747],[4.64099,50.74517],[4.60365,50.74115],[4.59743,50.76393],[4.57829,50.75673],[4.58129,50.75407],[4.56172,50.744],[4.55649,50.74873],[4.53366,50.73922],[4.52576,50.74256],[4.53336,50.73295],[4.52314,50.72719],[4.51254,50.73254],[4.5013,50.7377],[4.5007,50.74454],[4.49542,50.7399],[4.49151,50.74291],[4.50113,50.75361],[4.49418,50.7572],[4.45856,50.75307],[4.42457,50.73602],[4.37187,50.72961],[4.37273,50.71624],[4.3644,50.71692],[4.35144,50.71504],[4.33775,50.73479],[4.32384,50.72808],[4.32462,50.7242],[4.33556,50.71564],[4.31436,50.72015],[4.29024,50.6939],[4.2614,50.70042],[4.25007,50.69186],[4.14253,50.72884],[4.13609,50.7189],[4.07146,50.71178],[4.05867,50.69417],[4.0506,50.70385],[4.03198,50.69395],[4.00769,50.69808],[3.98855,50.68819],[3.93799,50.68949],[3.89001,50.71618],[3.8995,50.73694],[3.87658,50.7494],[3.77908,50.74707],[3.76419,50.75899],[3.7538,50.77725],[3.71492,50.76797],[3.68878,50.77451],[3.67256,50.75864],[3.66642,50.75435],[3.66132,50.75429],[3.65904,50.74696],[3.64291,50.73843],[3.64033,50.72208],[3.62359,50.72254],[3.60982,50.7342],[3.57527,50.72556],[3.54566,50.74014],[3.54648,50.75432],[3.53429,50.76485],[3.49618,50.75752],[3.45927,50.76349],[3.424,50.74623],[3.40589,50.74688],[3.3946,50.72969],[3.36889,50.72594],[3.36031,50.70977],[3.32392,50.72241],[3.32868,50.73124],[3.3119,50.75416],[3.26195,50.75092],[3.22058,50.76586],[3.19084,50.75543],[3.18526,50.75866],[3.17994,50.75451]]]]}},{"type":"Feature","properties":{"id":"BE-VLG"},"geometry":{"type":"MultiPolygon","coordinates":[[[[2.18458,51.52087],[2.55904,51.07014],[2.57457,51.00203],[2.60753,50.98323],[2.63074,50.94746],[2.59093,50.91751],[2.60564,50.90167],[2.61105,50.86529],[2.598,50.84993],[2.62006,50.84451],[2.6341,50.81301],[2.65676,50.81322],[2.67075,50.82201],[2.68036,50.81322],[2.71671,50.81358],[2.73344,50.78477],[2.76177,50.77115],[2.75662,50.76284],[2.77816,50.75101],[2.78988,50.7283],[2.81313,50.71604],[2.8483,50.72276],[2.86391,50.70865],[2.86967,50.71302],[2.85258,50.72453],[2.84219,50.73572],[2.85301,50.74061],[2.84206,50.75155],[2.85584,50.75763],[2.86987,50.75991],[2.87073,50.75418],[2.87936,50.75473],[2.87683,50.76149],[2.8882,50.75331],[2.92365,50.75633],[2.91678,50.76358],[2.94832,50.77294],[2.96266,50.77354],[2.94755,50.79101],[2.93686,50.79375],[2.96012,50.79777],[2.99986,50.81084],[3.00231,50.80899],[3.01085,50.80772],[3.01514,50.80268],[3.00866,50.79804],[3.01415,50.78423],[3.02947,50.78021],[3.02943,50.77877],[3.02141,50.77666],[3.01922,50.77335],[3.03428,50.76949],[3.0402,50.77593],[3.05617,50.78021],[3.08552,50.7731],[3.10614,50.78303],[3.11144,50.79454],[3.11987,50.79188],[3.12612,50.78637],[3.15017,50.79031],[3.16476,50.76843],[3.17161,50.75866],[3.17994,50.75451],[3.18526,50.75866],[3.19084,50.75543],[3.22058,50.76586],[3.26195,50.75092],[3.3119,50.75416],[3.32868,50.73124],[3.32392,50.72241],[3.36031,50.70977],[3.36889,50.72594],[3.3946,50.72969],[3.40589,50.74688],[3.424,50.74623],[3.45927,50.76349],[3.49618,50.75752],[3.53429,50.76485],[3.54648,50.75432],[3.54566,50.74014],[3.57527,50.72556],[3.60982,50.7342],[3.62359,50.72254],[3.64033,50.72208],[3.64291,50.73843],[3.65904,50.74696],[3.66132,50.75429],[3.66642,50.75435],[3.67256,50.75864],[3.68878,50.77451],[3.71492,50.76797],[3.7538,50.77725],[3.76419,50.75899],[3.77908,50.74707],[3.87658,50.7494],[3.8995,50.73694],[3.89001,50.71618],[3.93799,50.68949],[3.98855,50.68819],[4.00769,50.69808],[4.03198,50.69395],[4.0506,50.70385],[4.05867,50.69417],[4.07146,50.71178],[4.13609,50.7189],[4.14253,50.72884],[4.25007,50.69186],[4.2614,50.70042],[4.29024,50.6939],[4.31436,50.72015],[4.33556,50.71564],[4.32462,50.7242],[4.32384,50.72808],[4.33775,50.73479],[4.35144,50.71504],[4.3644,50.71692],[4.37273,50.71624],[4.37187,50.72961],[4.42457,50.73602],[4.45856,50.75307],[4.49418,50.7572],[4.50113,50.75361],[4.49151,50.74291],[4.49542,50.7399],[4.5007,50.74454],[4.5013,50.7377],[4.51254,50.73254],[4.52314,50.72719],[4.53336,50.73295],[4.52576,50.74256],[4.53366,50.73922],[4.55649,50.74873],[4.56172,50.744],[4.58129,50.75407],[4.57829,50.75673],[4.59743,50.76393],[4.60365,50.74115],[4.64099,50.74517],[4.65575,50.75747],[4.64635,50.76105],[4.6379,50.77652],[4.64301,50.79926],[4.66245,50.79264],[4.72,50.79112],[4.7466,50.80701],[4.7933,50.79774],[4.81312,50.77625],[4.83887,50.77706],[4.83527,50.76333],[4.90754,50.76909],[4.90625,50.74981],[4.9259,50.74335],[4.9841,50.76974],[5.0059,50.76512],[5.04718,50.73868],[5.06924,50.70732],[5.12048,50.70809],[5.15636,50.69335],[5.18108,50.70482],[5.16975,50.72493],[5.19378,50.71673],[5.2361,50.7252],[5.25292,50.71515],[5.37634,50.74243],[5.38982,50.74791],[5.41909,50.71847],[5.45359,50.72281],[5.46544,50.73982],[5.47883,50.72314],[5.52234,50.74395],[5.52311,50.75888],[5.54878,50.75931],[5.63675,50.78662],[5.65675,50.80593],[5.67315,50.80783],[5.68705,50.80381],[5.68799,50.81183],[5.65486,50.82074],[5.64009,50.84742],[5.64504,50.87107],[5.668,50.88048],[5.67924,50.88069],[5.69858,50.91046],[5.71626,50.90796],[5.72644,50.91167],[5.72679,50.92289],[5.74644,50.94723],[5.75927,50.95601],[5.74752,50.96202],[5.72875,50.95428],[5.71864,50.96092],[5.7352,50.97588],[5.76147,50.99452],[5.77631,51.0246],[5.75961,51.03113],[5.77258,51.06196],[5.79835,51.05834],[5.79903,51.09371],[5.82921,51.09328],[5.83226,51.10585],[5.8109,51.10861],[5.80798,51.11661],[5.85508,51.14445],[5.82417,51.16831],[5.81412,51.15908],[5.80498,51.16268],[5.77717,51.15122],[5.77735,51.17845],[5.74617,51.18928],[5.7082,51.18193],[5.65528,51.18736],[5.65145,51.19788],[5.5603,51.22249],[5.5569,51.26544],[5.515,51.29462],[5.48476,51.30053],[5.46519,51.2849],[5.4407,51.28169],[5.41672,51.26248],[5.347,51.27502],[5.33886,51.26314],[5.29716,51.26104],[5.26461,51.26693],[5.23814,51.26064],[5.22542,51.26888],[5.24244,51.30495],[5.2002,51.32243],[5.16222,51.31035],[5.13377,51.31592],[5.13105,51.34791],[5.07102,51.39469],[5.10456,51.43163],[5.07891,51.4715],[5.04774,51.47022],[5.03281,51.48679],[5.0106,51.47167],[5.00393,51.44406],[4.92152,51.39487],[4.90016,51.41404],[4.84988,51.41502],[4.78941,51.41102],[4.77229,51.41337],[4.76577,51.43046],[4.78314,51.43319],[4.82946,51.4213],[4.82409,51.44736],[4.84139,51.4799],[4.78803,51.50284],[4.77321,51.50529],[4.74578,51.48937],[4.72935,51.48424],[4.65442,51.42352],[4.57489,51.4324],[4.53521,51.4243],[4.52846,51.45002],[4.54675,51.47265],[4.5388,51.48184],[4.47736,51.4778],[4.38122,51.44905],[4.39747,51.43316],[4.38064,51.41965],[4.43777,51.36989],[4.39292,51.35547],[4.34086,51.35738],[4.33265,51.37687],[4.21923,51.37443],[4.24024,51.35371],[4.16721,51.29348],[4.05165,51.24171],[4.01957,51.24504],[3.97889,51.22537],[3.90125,51.20371],[3.78783,51.2151],[3.78999,51.25766],[3.58939,51.30064],[3.51502,51.28697],[3.52698,51.2458],[3.43488,51.24135],[3.41704,51.25933],[3.38289,51.27331],[3.35847,51.31572],[3.38696,51.33436],[3.36263,51.37112],[2.65913,51.8564],[2.18458,51.52087]],[[4.25089,50.81741],[4.25694,50.8355],[4.27164,50.83625],[4.27323,50.83876],[4.28273,50.83789],[4.28376,50.84816],[4.28844,50.84777],[4.28282,50.85403],[4.28988,50.85586],[4.28043,50.85992],[4.27891,50.86648],[4.29904,50.87934],[4.29438,50.88833],[4.31839,50.89464],[4.33011,50.90094],[4.34245,50.90226],[4.36363,50.90113],[4.37788,50.89721],[4.37916,50.90151],[4.38753,50.90985],[4.4062,50.91331],[4.43264,50.89437],[4.42693,50.89071],[4.43697,50.87885],[4.41929,50.86956],[4.43019,50.86117],[4.44345,50.85943],[4.44727,50.85464],[4.45989,50.85247],[4.46813,50.83746],[4.46572,50.83548],[4.4771,50.82052],[4.4474,50.80821],[4.48199,50.79302],[4.39414,50.76681],[4.34788,50.77637],[4.34075,50.77356],[4.33073,50.77527],[4.31921,50.78705],[4.31704,50.79623],[4.30494,50.7987],[4.30181,50.80273],[4.30636,50.81398],[4.29692,50.80985],[4.28301,50.80725],[4.26009,50.81099],[4.25645,50.8174],[4.25089,50.81741]]],[[[4.91493,51.4353],[4.92652,51.43329],[4.92952,51.42984],[4.93986,51.43064],[4.94265,51.44003],[4.93471,51.43861],[4.93416,51.44185],[4.94025,51.44193],[4.93544,51.44634],[4.92879,51.44161],[4.92815,51.43856],[4.92566,51.44273],[4.92811,51.4437],[4.92287,51.44741],[4.91811,51.44621],[4.92227,51.44252],[4.91935,51.43634],[4.91493,51.4353]]],[[[4.93295,51.44945],[4.93909,51.44632],[4.9524,51.45014],[4.95244,51.45207],[4.93295,51.44945]]],[[[5.68091,50.75804],[5.68731,50.75312],[5.72095,50.74634],[5.73992,50.75578],[5.74945,50.74481],[5.77331,50.75052],[5.80018,50.73683],[5.81382,50.71521],[5.86584,50.71689],[5.883,50.70999],[5.91261,50.7358],[5.89132,50.75124],[5.89129,50.75125],[5.88734,50.77092],[5.84888,50.75448],[5.84548,50.76542],[5.80673,50.7558],[5.77513,50.78308],[5.76533,50.78159],[5.74356,50.7691],[5.73904,50.75674],[5.72216,50.76398],[5.69469,50.75529],[5.68091,50.75804]]]]}},{"type":"Feature","properties":{"id":"RU-CHU"},"geometry":{"type":"MultiPolygon","coordinates":[]}},{"type":"Feature","properties":{"id":"ES-VC"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-1.52023,39.4346],[-1.42169,39.35952],[-1.16077,39.3088],[-1.27166,39.03838],[-1.1388,38.92603],[-0.95649,38.94552],[-0.93023,38.78111],[-0.95993,38.77549],[-0.94164,38.72101],[-0.91615,38.69559],[-0.96593,38.65347],[-1.0267,38.65562],[-1.00301,38.57272],[-1.02713,38.47811],[-1.09365,38.42602],[-1.08472,38.34663],[-0.97177,38.31943],[-0.98876,38.19448],[-1.03717,38.13388],[-1.02516,38.07755],[-1.00627,38.05478],[-0.99658,38.04978],[-0.93203,37.96619],[-0.9201,37.9427],[-0.83358,37.86645],[-0.8014,37.85676],[-0.78024,37.84337],[-0.76897,37.84437],[-0.36811,37.94676],[0.74156,38.66836],[2.8815,40.70665],[0.51464,40.52319],[0.43241,40.54876],[0.43447,40.57132],[0.28392,40.62802],[0.17063,40.73216],[-0.06832,40.728],[-0.1902,40.79041],[-0.24272,40.68909],[-0.40546,40.65485],[-0.36872,40.61238],[-0.29182,40.59753],[-0.29388,40.46575],[-0.34675,40.44067],[-0.27912,40.36904],[-0.39619,40.29523],[-0.38143,40.25856],[-0.55892,40.22869],[-0.60424,40.1298],[-0.62896,40.10197],[-0.67806,40.04864],[-0.84045,39.97396],[-0.87238,39.83859],[-0.97503,39.98501],[-1.14518,39.9713],[-1.20849,39.9487],[-1.28402,39.67522],[-1.50203,39.64561],[-1.52023,39.4346]]],[[[-1.45671,40.14259],[-1.4186,40.09422],[-1.37895,40.02091],[-1.2502,39.99369],[-1.16523,40.01013],[-1.06653,40.0401],[-1.06867,40.0602],[-1.14772,40.11424],[-1.23218,40.11372],[-1.30857,40.2144],[-1.31681,40.15106],[-1.33775,40.13105],[-1.45671,40.14259]]]]}},{"type":"Feature","properties":{"id":"ES-NC"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-2.50162,42.60793],[-2.45681,42.56521],[-2.4139,42.60755],[-2.39484,42.51766],[-2.43209,42.51045],[-2.4148,42.48988],[-2.40102,42.47348],[-2.32652,42.46829],[-2.20928,42.41585],[-2.11298,42.4123],[-2.09135,42.3404],[-2.00672,42.36869],[-1.87076,42.25571],[-1.70717,42.20893],[-1.68519,42.14062],[-1.82973,42.15271],[-1.92054,42.03679],[-1.84878,42.00898],[-1.7427,41.96804],[-1.4253,41.91211],[-1.36196,41.96587],[-1.38839,42.28416],[-1.26565,42.55674],[-1.22016,42.53961],[-1.15613,42.6462],[-1.04146,42.64481],[-1.03769,42.68495],[-0.87633,42.75583],[-0.81539,42.90187],[-0.72251,42.92025],[-0.75478,42.96916],[-0.81652,42.95166],[-0.97133,42.96239],[-1.00963,42.99279],[-1.08556,43.00232],[-1.18197,43.03338],[-1.22881,43.05534],[-1.25016,43.04079],[-1.30745,43.06918],[-1.29897,43.09281],[-1.28252,43.10891],[-1.26759,43.11907],[-1.32209,43.1127],[-1.34419,43.09665],[-1.35272,43.02658],[-1.44067,43.047],[-1.47555,43.08372],[-1.41562,43.12815],[-1.3758,43.24511],[-1.40942,43.27272],[-1.44301,43.26749],[-1.50585,43.2936],[-1.53463,43.29459],[-1.5646,43.28887],[-1.55808,43.27911],[-1.57021,43.25253],[-1.61341,43.25269],[-1.63052,43.28591],[-1.62124,43.30682],[-1.66839,43.31504],[-1.69407,43.31378],[-1.73074,43.29481],[-1.78424,43.28376],[-1.79317,43.25082],[-1.91814,43.21643],[-1.90458,43.14045],[-2.0244,43.07039],[-2.01599,42.98305],[-2.22919,42.94926],[-2.26575,42.74839],[-2.36171,42.63048],[-2.40437,42.6688],[-2.50162,42.60793]]],[[[-1.18549,42.40038],[-1.15382,42.41414],[-1.14352,42.44322],[-1.18343,42.42472],[-1.18549,42.40038]]],[[[-1.13142,42.4455],[-1.04919,42.43663],[-1.09828,42.48754],[-1.13142,42.4455]]]]}},{"type":"Feature","properties":{"id":"ES-PV"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-3.45142,43.23644],[-3.41812,43.13193],[-3.25615,43.1981],[-3.22365,43.17252],[-3.13754,43.16981],[-3.15796,43.15955],[-3.18221,43.11392],[-3.13028,43.1006],[-3.15908,43.07243],[-3.14131,43.06876],[-3.14058,43.02927],[-3.1823,43.02425],[-3.15311,43.008],[-3.07252,43.00624],[-3.0366,42.98173],[-3.04578,42.97155],[-2.97918,42.94184],[-3.013,42.90992],[-3.08827,42.89703],[-3.21075,42.95076],[-3.2892,42.89118],[-3.25324,42.84714],[-3.16861,42.85526],[-3.11127,42.88954],[-3.14749,42.75684],[-3.06612,42.75968],[-2.98055,42.70615],[-2.95188,42.71006],[-2.90107,42.69328],[-2.90073,42.65895],[-2.84314,42.63023],[-2.8449,42.61308],[-2.81601,42.61425],[-2.83936,42.57861],[-2.82897,42.55592],[-2.78039,42.5776],[-2.76529,42.62549],[-2.67654,42.59669],[-2.70649,42.51665],[-2.68014,42.52569],[-2.6507,42.48488],[-2.60401,42.50298],[-2.59466,42.47121],[-2.56977,42.4926],[-2.54101,42.48216],[-2.5096,42.51987],[-2.50951,42.48855],[-2.4148,42.48988],[-2.43209,42.51045],[-2.39484,42.51766],[-2.4139,42.60755],[-2.45681,42.56521],[-2.50162,42.60793],[-2.40437,42.6688],[-2.36171,42.63048],[-2.26575,42.74839],[-2.22919,42.94926],[-2.01599,42.98305],[-2.0244,43.07039],[-1.90458,43.14045],[-1.91814,43.21643],[-1.79317,43.25082],[-1.78424,43.28376],[-1.73074,43.29481],[-1.73744,43.33007],[-1.74967,43.3316],[-1.75334,43.34107],[-1.75854,43.34434],[-1.77218,43.34211],[-1.79005,43.35216],[-1.7816,43.36155],[-1.79224,43.3745],[-1.77289,43.38957],[-1.81005,43.59738],[-3.10233,43.59549],[-3.1544,43.341],[-3.14895,43.30619],[-3.3413,43.28845],[-3.45142,43.23644]],[[-3.30422,43.2582],[-3.27276,43.26545],[-3.24714,43.26248],[-3.27349,43.20195],[-3.28868,43.1991],[-3.30422,43.2582]],[[-2.86648,42.73743],[-2.84288,42.7896],[-2.73611,42.79376],[-2.67087,42.77259],[-2.54161,42.75218],[-2.61036,42.70079],[-2.57749,42.66937],[-2.53123,42.68874],[-2.51595,42.64627],[-2.63689,42.6462],[-2.65465,42.67309],[-2.74211,42.66249],[-2.86648,42.73743]]],[[[-3.12306,42.96924],[-3.12256,42.96865],[-3.12227,42.96926],[-3.12301,42.96975],[-3.12306,42.96924]]]]}},{"type":"Feature","properties":{"id":"ES-AR"},"geometry":{"type":"Polygon","coordinates":[[[-2.19657,41.30927],[-2.11452,41.16469],[-1.91848,41.14014],[-1.61018,40.93504],[-1.54804,40.74361],[-1.5604,40.56858],[-1.69223,40.58058],[-1.78081,40.37401],[-1.53396,40.18779],[-1.43835,40.19238],[-1.45671,40.14259],[-1.33775,40.13105],[-1.31681,40.15106],[-1.30857,40.2144],[-1.23218,40.11372],[-1.14772,40.11424],[-1.06867,40.0602],[-1.06653,40.0401],[-1.16523,40.01013],[-1.14518,39.9713],[-0.97503,39.98501],[-0.87238,39.83859],[-0.84045,39.97396],[-0.67806,40.04864],[-0.62896,40.10197],[-0.60424,40.1298],[-0.55892,40.22869],[-0.38143,40.25856],[-0.39619,40.29523],[-0.27912,40.36904],[-0.34675,40.44067],[-0.29388,40.46575],[-0.29182,40.59753],[-0.36872,40.61238],[-0.40546,40.65485],[-0.24272,40.68909],[-0.1902,40.79041],[-0.06832,40.728],[0.17063,40.73216],[0.28049,40.82108],[0.23723,40.88055],[0.29045,40.97004],[0.19569,41.12152],[0.3804,41.23676],[0.34006,41.47668],[0.3955,41.49006],[0.44631,41.54366],[0.43052,41.60093],[0.34727,41.59798],[0.32649,41.67881],[0.40082,41.75607],[0.54485,41.82416],[0.60407,41.88093],[0.56236,41.94136],[0.69874,42.10331],[0.6978,42.17396],[0.75994,42.31705],[0.70175,42.46867],[0.77419,42.60806],[0.70432,42.62057],[0.67873,42.69458],[0.40214,42.69779],[0.36251,42.72282],[0.29407,42.67431],[0.25336,42.7174],[0.17569,42.73424],[-0.02468,42.68513],[-0.10519,42.72761],[-0.16141,42.79535],[-0.17939,42.78974],[-0.3122,42.84788],[-0.38833,42.80132],[-0.41319,42.80776],[-0.44334,42.79939],[-0.50863,42.82713],[-0.52725,42.79565],[-0.55497,42.77846],[-0.67637,42.88303],[-0.69837,42.87945],[-0.72608,42.89318],[-0.73422,42.91228],[-0.72251,42.92025],[-0.81539,42.90187],[-0.87633,42.75583],[-1.03769,42.68495],[-1.04146,42.64481],[-1.15613,42.6462],[-1.22016,42.53961],[-1.26565,42.55674],[-1.38839,42.28416],[-1.36196,41.96587],[-1.4253,41.91211],[-1.7427,41.96804],[-1.84878,42.00898],[-1.77703,41.71674],[-1.98921,41.56305],[-1.93977,41.38324],[-2.11315,41.43474],[-2.19657,41.30927]],[[-1.18549,42.40038],[-1.18343,42.42472],[-1.14352,42.44322],[-1.15382,42.41414],[-1.18549,42.40038]],[[-1.13142,42.4455],[-1.09828,42.48754],[-1.04919,42.43663],[-1.13142,42.4455]]]}},{"type":"Feature","properties":{"id":"MD-GA"},"geometry":{"type":"MultiPolygon","coordinates":[[[[28.29975,45.73014],[28.30201,45.54744],[28.41836,45.51715],[28.43072,45.48538],[28.51449,45.49982],[28.49252,45.56716],[28.43502,45.56839],[28.43879,45.65268],[28.47879,45.66994],[28.52823,45.73803],[28.29975,45.73014]]],[[[28.38455,45.85558],[28.50128,45.86993],[28.47347,45.89837],[28.38455,45.85558]]],[[[28.47553,46.17769],[28.51982,46.09561],[28.47965,46.09109],[28.57337,46.04845],[28.50128,46.01532],[28.62831,45.98384],[28.55003,45.94971],[28.74383,45.96664],[28.71688,46.0818],[28.81021,45.97709],[28.99799,46.23495],[28.95275,46.25988],[28.70555,46.29002],[28.90056,46.37985],[28.53286,46.47711],[28.56616,46.34337],[28.49063,46.31777],[28.59157,46.23661],[28.47553,46.17769]]],[[[28.526,45.84327],[28.61749,45.80558],[28.69852,45.81753],[28.78589,45.83286],[28.76946,45.88515],[28.526,45.84327]]]]}},{"type":"Feature","properties":{"id":"PT"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-31.64064,39.2663],[-24.73024,36.54496],[-24.15895,38.62546],[-31.55275,39.97713],[-31.64064,39.2663]]],[[[-17.69898,33.17894],[-16.18156,29.73901],[-15.56383,29.89383],[-16.06202,33.33053],[-17.69898,33.17894]]],[[[-9.81081,39.52524],[-9.12416,36.69927],[-7.37282,36.7712],[-7.39769,37.16868],[-7.41133,37.20314],[-7.41854,37.23813],[-7.43227,37.25152],[-7.43974,37.38913],[-7.46878,37.47127],[-7.51759,37.56119],[-7.41981,37.75729],[-7.33441,37.81193],[-7.27314,37.90145],[-7.24544,37.98884],[-7.12648,38.00296],[-7.10366,38.04404],[-7.05966,38.01966],[-7.00375,38.01914],[-6.93418,38.21454],[-7.09389,38.17227],[-7.15581,38.27597],[-7.32529,38.44336],[-7.265,38.61674],[-7.26174,38.72107],[-7.03848,38.87221],[-7.051,38.907],[-6.95211,39.0243],[-6.97004,39.07619],[-7.04011,39.11919],[-7.10692,39.10275],[-7.14929,39.11287],[-7.12811,39.17101],[-7.23566,39.20132],[-7.23986,39.26675],[-7.3223,39.38055],[-7.2927,39.45847],[-7.49477,39.58794],[-7.54121,39.66717],[-7.33507,39.64569],[-7.24707,39.66576],[-7.01613,39.66877],[-6.97492,39.81488],[-6.90982,39.86183],[-6.86737,40.01986],[-6.94233,40.10716],[-7.00589,40.12087],[-7.02544,40.18564],[-7.00426,40.23169],[-6.86085,40.26776],[-6.86565,40.2957],[-6.80415,40.3297],[-6.77953,40.36409],[-6.83313,40.40595],[-6.84901,40.45434],[-6.7973,40.51723],[-6.80218,40.55067],[-6.84703,40.56853],[-6.79567,40.65955],[-6.82826,40.74603],[-6.82337,40.84472],[-6.79892,40.84842],[-6.80707,40.88047],[-6.84292,40.89771],[-6.8527,40.93958],[-6.9357,41.02888],[-6.913,41.03922],[-6.88843,41.03027],[-6.84781,41.02692],[-6.80942,41.03629],[-6.8079,41.04523],[-6.75655,41.10187],[-6.77319,41.13049],[-6.69711,41.1858],[-6.68286,41.21641],[-6.65046,41.24725],[-6.55937,41.24417],[-6.38551,41.35274],[-6.38553,41.38655],[-6.3306,41.37677],[-6.28675,41.46459],[-6.19128,41.57638],[-6.29863,41.66432],[-6.44204,41.68258],[-6.49907,41.65823],[-6.54838,41.68475],[-6.56426,41.74219],[-6.51374,41.8758],[-6.57078,41.88358],[-6.5447,41.94371],[-6.58544,41.96674],[-6.61831,41.94036],[-6.75004,41.94129],[-6.76959,41.98734],[-6.81196,41.99097],[-6.82174,41.94493],[-6.94396,41.94403],[-6.95537,41.96553],[-6.98144,41.9728],[-7.01078,41.94977],[-7.07596,41.94977],[-7.08574,41.97401],[-7.14115,41.98855],[-7.18549,41.97515],[-7.18677,41.88793],[-7.32366,41.8406],[-7.37092,41.85031],[-7.42864,41.80589],[-7.42699,41.83293],[-7.44759,41.84451],[-7.44922,41.86436],[-7.49803,41.87095],[-7.51981,41.84073],[-7.61904,41.82961],[-7.58603,41.87944],[-7.65774,41.88308],[-7.69848,41.90977],[-7.84188,41.88065],[-7.84939,41.86201],[-7.88638,41.85568],[-7.88493,41.92597],[-7.90672,41.92649],[-7.92336,41.8758],[-7.9804,41.87337],[-8.01136,41.83453],[-8.0961,41.81024],[-8.16455,41.81753],[-8.16944,41.87944],[-8.19551,41.87459],[-8.21961,41.91064],[-8.15476,41.98509],[-8.08796,42.01398],[-8.08847,42.05767],[-8.11282,42.08339],[-8.17623,42.06477],[-8.18534,42.07171],[-8.18837,42.10812],[-8.19406,42.12141],[-8.18771,42.13722],[-8.19748,42.15436],[-8.2262,42.13069],[-8.25007,42.14018],[-8.25957,42.12108],[-8.27296,42.12432],[-8.29809,42.106],[-8.32161,42.10218],[-8.32703,42.08726],[-8.33912,42.08358],[-8.36353,42.09065],[-8.38323,42.07683],[-8.40143,42.08052],[-8.42512,42.07199],[-8.4444,42.08377],[-8.52837,42.07658],[-8.5252,42.06264],[-8.54563,42.0537],[-8.58086,42.05147],[-8.59493,42.05708],[-8.63791,42.04691],[-8.64626,42.03668],[-8.65832,42.02972],[-8.6681,41.99703],[-8.7478,41.96282],[-8.74606,41.9469],[-8.75712,41.92833],[-8.81794,41.90375],[-8.87157,41.86488],[-9.14112,41.86623],[-9.81081,39.52524]]]]}},{"type":"Feature","properties":{"id":"BA-SRP"},"geometry":{"type":"MultiPolygon","coordinates":[[[[16.20423,44.99218],[16.2171,44.90804],[16.28989,44.87612],[16.33993,44.81009],[16.40447,44.86341],[16.62446,44.84339],[16.64952,44.80491],[16.66917,44.82483],[16.77131,44.82434],[16.79861,44.73063],[16.90418,44.54252],[16.84804,44.49638],[16.52446,44.52784],[16.40052,44.49895],[17.13523,44.06119],[17.35187,44.07599],[17.18999,44.32998],[17.20664,44.43954],[17.36457,44.45424],[17.55992,44.32851],[17.95852,44.47923],[17.97637,44.53286],[17.87672,44.64661],[17.92419,44.72234],[18.01534,44.72917],[18.06504,44.70368],[18.07053,44.67579],[18.06727,44.667],[18.0774,44.66132],[18.078,44.64905],[18.09233,44.64453],[18.0871,44.63366],[18.0998,44.62749],[18.10289,44.61032],[18.08538,44.60623],[18.09963,44.57518],[18.21224,44.60696],[18.24863,44.5731],[18.40639,44.58215],[18.43385,44.61026],[18.39351,44.61173],[18.38913,44.64032],[18.35708,44.63757],[18.31575,44.67713],[18.29927,44.67445],[18.2718,44.69574],[18.13954,44.72222],[18.1422,44.77537],[18.31094,44.79011],[18.35197,44.81204],[18.31764,44.84661],[18.38913,44.89655],[18.40373,44.92713],[18.45016,44.93071],[18.51728,44.88792],[18.53814,44.83116],[18.56096,44.82929],[18.60671,44.8454],[18.63899,44.90975],[18.74143,44.95017],[18.60028,45.00055],[18.50827,45.04454],[18.47926,45.05951],[18.46943,45.06787],[18.39214,45.03999],[18.36708,45.01366],[18.34596,45.02009],[18.33334,44.99345],[18.3118,44.99315],[18.30871,45.01445],[18.28399,45.02998],[18.27438,45.04447],[18.2476,45.06412],[18.17756,45.07739],[18.10718,45.0877],[18.06366,45.14596],[18.03121,45.12632],[18.01774,45.15111],[17.99479,45.14958],[17.97834,45.13831],[17.97336,45.12245],[17.93706,45.08016],[17.87148,45.04645],[17.84754,45.04478],[17.66571,45.13408],[17.60112,45.10836],[17.51469,45.10791],[17.48748,45.13264],[17.45942,45.12574],[17.4498,45.16119],[17.41727,45.13398],[17.34337,45.14148],[17.32092,45.16246],[17.26982,45.18832],[17.25131,45.14957],[17.2442,45.14581],[17.18004,45.14657],[17.0415,45.20759],[16.9767,45.24292],[16.93954,45.2289],[16.94203,45.26872],[16.92272,45.27694],[16.91001,45.25579],[16.83534,45.21614],[16.83804,45.18951],[16.81137,45.18434],[16.77723,45.19262],[16.7344,45.20719],[16.68754,45.20048],[16.64962,45.20714],[16.59952,45.23156],[16.58484,45.22506],[16.5501,45.2212],[16.53618,45.22702],[16.52403,45.22545],[16.49185,45.20877],[16.47974,45.18524],[16.4703,45.14621],[16.41091,45.12035],[16.38456,45.06424],[16.38219,45.05139],[16.3749,45.05206],[16.35863,45.03529],[16.35572,45.0031],[16.28937,44.99661],[16.24929,44.98337],[16.20423,44.99218]]],[[[17.90822,43.17977],[18.02169,43.10085],[17.96401,42.99284],[17.99663,42.83695],[18.17619,42.73427],[18.28262,42.62334],[18.3615,42.61867],[18.43735,42.55921],[18.49778,42.58409],[18.53751,42.57376],[18.55504,42.58409],[18.52232,42.62279],[18.57373,42.64429],[18.54841,42.68328],[18.54603,42.69171],[18.55221,42.69045],[18.56789,42.72074],[18.47324,42.74992],[18.45921,42.81682],[18.47633,42.85829],[18.4935,42.86433],[18.49661,42.89306],[18.49076,42.95553],[18.52232,43.01451],[18.66254,43.03928],[18.64735,43.14766],[18.66808,43.20473],[18.71747,43.2286],[18.69409,43.25014],[18.76538,43.29838],[18.85342,43.32426],[18.84794,43.33735],[18.83912,43.34795],[18.90911,43.36383],[18.95999,43.3306],[18.95001,43.29327],[19.01046,43.24911],[19.03724,43.29042],[19.08093,43.29969],[19.08393,43.31949],[19.04071,43.397],[19.01078,43.43854],[18.96053,43.45042],[18.95469,43.49367],[18.91021,43.50087],[19.01078,43.55806],[19.04934,43.50384],[19.13933,43.5282],[19.15685,43.53943],[19.22807,43.5264],[19.24774,43.53061],[19.25611,43.59997],[19.33426,43.58833],[19.36778,43.6114],[19.38589,43.59232],[19.40082,43.58741],[19.41301,43.53946],[19.42829,43.55591],[19.41618,43.57777],[19.51755,43.57958],[19.49172,43.60034],[19.49386,43.637],[19.5094,43.62744],[19.53317,43.70399],[19.4815,43.73284],[19.46373,43.76254],[19.3986,43.79668],[19.33885,43.8625],[19.23465,43.98764],[19.24363,44.01502],[19.38614,43.961],[19.40314,43.96607],[19.42872,43.95816],[19.4488,43.96014],[19.47669,43.95606],[19.52656,43.956],[19.54193,43.97595],[19.5681,43.98558],[19.56364,43.99898],[19.58956,44.00334],[19.59832,44.01007],[19.61806,44.01374],[19.62342,44.01883],[19.61394,44.03522],[19.62467,44.05268],[19.57467,44.04716],[19.55368,44.07186],[19.50965,44.08129],[19.49292,44.11384],[19.46966,44.12129],[19.48386,44.14332],[19.47338,44.15034],[19.43905,44.13088],[19.40927,44.16722],[19.36323,44.18165],[19.35344,44.18955],[19.35889,44.20903],[19.34443,44.21816],[19.34825,44.23124],[19.32791,44.26745],[19.28649,44.27565],[19.25311,44.26397],[19.23229,44.26241],[19.208,44.29243],[19.18328,44.28383],[19.16741,44.28648],[19.1323,44.31604],[19.13423,44.34017],[19.11547,44.34218],[19.1083,44.3558],[19.11925,44.36684],[19.10372,44.36877],[19.10365,44.37795],[19.10704,44.38249],[19.10749,44.39421],[19.11616,44.40141],[19.14693,44.41364],[19.15045,44.45148],[19.12174,44.50091],[19.13277,44.52674],[19.16483,44.52209],[19.19517,44.55053],[19.18307,44.57426],[19.21959,44.59175],[19.24229,44.62224],[19.25886,44.6608],[19.29928,44.68903],[19.33464,44.73758],[19.33224,44.76727],[19.3458,44.78804],[19.37181,44.87922],[19.29903,44.9095],[19.19191,44.92202],[19.01994,44.85493],[18.98527,44.84643],[19.02123,44.82851],[19.01767,44.82113],[18.98851,44.83001],[18.96974,44.82915],[18.92163,44.76836],[18.89086,44.77589],[18.85816,44.77699],[18.84271,44.78326],[18.8188,44.76709],[18.82202,44.75864],[18.80902,44.75535],[18.83485,44.74752],[18.83005,44.71209],[18.74851,44.69098],[18.72722,44.64386],[18.79005,44.57408],[18.9218,44.52466],[18.94703,44.59719],[18.98428,44.62822],[19.04977,44.61674],[19.03501,44.54264],[19.03647,44.49277],[19.00686,44.49742],[19.02188,44.4589],[18.97725,44.4573],[18.94042,44.40852],[18.87768,44.40883],[18.73117,44.32556],[18.857,44.171],[18.70817,44.11458],[18.52921,43.97533],[18.48286,43.91094],[18.46724,43.89532],[18.48325,43.88428],[18.47303,43.87577],[18.45535,43.82],[18.43694,43.84146],[18.41853,43.84597],[18.3621,43.8357],[18.32519,43.78243],[18.353,43.77623],[18.40175,43.71603],[18.53882,43.72086],[18.69727,43.66898],[18.75658,43.77313],[18.86026,43.76328],[18.89657,43.77351],[18.97261,43.69698],[18.99845,43.68531],[19.01038,43.67004],[19.01767,43.63334],[18.81919,43.57162],[18.71744,43.58001],[18.43591,43.67383],[18.33412,43.56397],[18.34665,43.47422],[18.08847,43.47758],[17.94445,43.44793],[18.03989,43.25783],[17.90822,43.17977]]]]}},{"type":"Feature","properties":{"id":"BA-BIH"},"geometry":{"type":"MultiPolygon","coordinates":[[[[15.72584,44.82334],[15.8255,44.71501],[15.89348,44.74964],[15.95824,44.69361],[16.06407,44.61142],[16.01729,44.58025],[16.03012,44.55572],[16.10566,44.52586],[16.17144,44.40594],[16.12969,44.38275],[16.22028,44.34883],[16.18766,44.30855],[16.19088,44.27141],[16.22079,44.23572],[16.21727,44.2177],[16.26337,44.17764],[16.37666,44.08129],[16.43662,44.07523],[16.43629,44.02826],[16.50528,44.0244],[16.55472,43.95326],[16.70922,43.84887],[16.74882,43.77394],[16.80736,43.76011],[16.99748,43.58559],[17.02983,43.56391],[17.08313,43.54263],[17.16218,43.49272],[17.22321,43.49956],[17.28475,43.47154],[17.28612,43.43266],[17.25579,43.40353],[17.286,43.33065],[17.3529,43.2527],[17.42826,43.21868],[17.43118,43.18402],[17.64268,43.08595],[17.70879,42.97223],[17.5392,42.92787],[17.6444,42.88641],[17.6873,42.92839],[17.78961,42.89344],[17.82394,42.91796],[17.89775,42.81781],[18.13611,42.68142],[18.15713,42.65359],[18.17816,42.66028],[18.25052,42.60541],[18.3615,42.61867],[18.28262,42.62334],[18.17619,42.73427],[17.99663,42.83695],[17.96401,42.99284],[18.02169,43.10085],[17.90822,43.17977],[18.03989,43.25783],[17.94445,43.44793],[18.08847,43.47758],[18.34665,43.47422],[18.33412,43.56397],[18.43591,43.67383],[18.71744,43.58001],[18.81919,43.57162],[19.01767,43.63334],[19.01038,43.67004],[18.99845,43.68531],[18.97261,43.69698],[18.89657,43.77351],[18.86026,43.76328],[18.75658,43.77313],[18.69727,43.66898],[18.53882,43.72086],[18.40175,43.71603],[18.353,43.77623],[18.32519,43.78243],[18.3621,43.8357],[18.41853,43.84597],[18.43694,43.84146],[18.45535,43.82],[18.47303,43.87577],[18.48325,43.88428],[18.46724,43.89532],[18.48286,43.91094],[18.52921,43.97533],[18.70817,44.11458],[18.857,44.171],[18.73117,44.32556],[18.87768,44.40883],[18.94042,44.40852],[18.97725,44.4573],[19.02188,44.4589],[19.00686,44.49742],[19.03647,44.49277],[19.03501,44.54264],[19.04977,44.61674],[18.98428,44.62822],[18.94703,44.59719],[18.9218,44.52466],[18.79005,44.57408],[18.72722,44.64386],[18.74851,44.69098],[18.83005,44.71209],[18.83485,44.74752],[18.80902,44.75535],[18.72456,44.74959],[18.65916,44.70679],[18.63504,44.73606],[18.56414,44.73636],[18.51762,44.81691],[18.56096,44.82929],[18.53814,44.83116],[18.51728,44.88792],[18.45016,44.93071],[18.40373,44.92713],[18.38913,44.89655],[18.31764,44.84661],[18.35197,44.81204],[18.31094,44.79011],[18.1422,44.77537],[18.13954,44.72222],[18.2718,44.69574],[18.29927,44.67445],[18.31575,44.67713],[18.35708,44.63757],[18.38913,44.64032],[18.39351,44.61173],[18.43385,44.61026],[18.40639,44.58215],[18.24863,44.5731],[18.21224,44.60696],[18.09963,44.57518],[18.08538,44.60623],[18.10289,44.61032],[18.0998,44.62749],[18.0871,44.63366],[18.09233,44.64453],[18.078,44.64905],[18.0774,44.66132],[18.06727,44.667],[18.07053,44.67579],[18.06504,44.70368],[18.01534,44.72917],[17.92419,44.72234],[17.87672,44.64661],[17.97637,44.53286],[17.95852,44.47923],[17.55992,44.32851],[17.36457,44.45424],[17.20664,44.43954],[17.18999,44.32998],[17.35187,44.07599],[17.13523,44.06119],[16.40052,44.49895],[16.52446,44.52784],[16.84804,44.49638],[16.90418,44.54252],[16.79861,44.73063],[16.77131,44.82434],[16.66917,44.82483],[16.64952,44.80491],[16.62446,44.84339],[16.40447,44.86341],[16.33993,44.81009],[16.28989,44.87612],[16.2171,44.90804],[16.20423,44.99218],[16.24929,44.98337],[16.28937,44.99661],[16.12153,45.09616],[16.00965,45.21838],[15.92382,45.22739],[15.89103,45.21626],[15.8337,45.22201],[15.82709,45.20786],[15.81022,45.20979],[15.80108,45.20036],[15.77816,45.18515],[15.77778,45.17653],[15.76371,45.16508],[15.79061,45.11635],[15.74585,45.0638],[15.78374,44.9722],[15.74723,44.96818],[15.76096,44.87045],[15.79472,44.8455],[15.72584,44.82334]]],[[[18.17756,45.07739],[18.2476,45.06412],[18.27438,45.04447],[18.28399,45.02998],[18.30871,45.01445],[18.3118,44.99315],[18.33334,44.99345],[18.34596,45.02009],[18.36708,45.01366],[18.39214,45.03999],[18.46943,45.06787],[18.41896,45.11083],[18.32176,45.10151],[18.27541,45.13458],[18.20846,45.12804],[18.21344,45.08927],[18.17756,45.07739]]],[[[18.47926,45.05951],[18.50827,45.04454],[18.60028,45.00055],[18.74143,44.95017],[18.78515,44.96742],[18.78687,44.98142],[18.66482,45.06667],[18.58886,45.08824],[18.53659,45.0583],[18.47926,45.05951]]]]}},{"type":"Feature","properties":{"id":"ES-CL"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-7.04858,42.53284],[-6.80242,42.48633],[-6.83564,42.39981],[-6.70612,42.33621],[-7.03708,42.07006],[-6.97631,42.04056],[-6.98144,41.9728],[-6.95537,41.96553],[-6.94396,41.94403],[-6.82174,41.94493],[-6.81196,41.99097],[-6.76959,41.98734],[-6.75004,41.94129],[-6.61831,41.94036],[-6.58544,41.96674],[-6.5447,41.94371],[-6.57078,41.88358],[-6.51374,41.8758],[-6.56426,41.74219],[-6.54838,41.68475],[-6.49907,41.65823],[-6.44204,41.68258],[-6.29863,41.66432],[-6.19128,41.57638],[-6.28675,41.46459],[-6.3306,41.37677],[-6.38553,41.38655],[-6.38551,41.35274],[-6.55937,41.24417],[-6.65046,41.24725],[-6.68286,41.21641],[-6.69711,41.1858],[-6.77319,41.13049],[-6.75655,41.10187],[-6.8079,41.04523],[-6.80942,41.03629],[-6.84781,41.02692],[-6.88843,41.03027],[-6.913,41.03922],[-6.9357,41.02888],[-6.8527,40.93958],[-6.84292,40.89771],[-6.80707,40.88047],[-6.79892,40.84842],[-6.82337,40.84472],[-6.82826,40.74603],[-6.79567,40.65955],[-6.84703,40.56853],[-6.80218,40.55067],[-6.7973,40.51723],[-6.84901,40.45434],[-6.83313,40.40595],[-6.77953,40.36409],[-6.80415,40.3297],[-6.86565,40.2957],[-6.86085,40.26776],[-6.84688,40.25385],[-6.6826,40.24297],[-6.54871,40.28581],[-6.56398,40.32848],[-6.21551,40.49317],[-6.07097,40.39558],[-6.10221,40.35857],[-5.91819,40.27756],[-5.87751,40.32744],[-5.79477,40.35321],[-5.79305,40.2845],[-5.66825,40.28646],[-5.57436,40.1807],[-5.37197,40.27192],[-5.3366,40.11457],[-4.95861,40.12481],[-4.78042,40.26826],[-4.703,40.28358],[-4.66678,40.18621],[-4.59056,40.21532],[-4.50284,40.31644],[-4.45444,40.31998],[-4.42543,40.40696],[-4.32655,40.41009],[-4.28689,40.63857],[-4.25445,40.59955],[-4.1633,40.62124],[-4.17274,40.68037],[-4.06717,40.79158],[-3.98048,40.78405],[-3.90701,40.98067],[-3.79234,40.99596],[-3.58085,41.16521],[-3.5513234,41.1727026],[-3.39323,41.2121],[-3.40078,41.24386],[-3.1826,41.30643],[-3.04561,41.27367],[-2.87601,41.32642],[-2.86056,41.26993],[-2.6059,41.23141],[-2.56959,41.16198],[-2.41561,41.05851],[-2.05101,41.10031],[-2.11452,41.16469],[-2.19657,41.30927],[-2.11315,41.43474],[-1.93977,41.38324],[-1.98921,41.56305],[-1.77703,41.71674],[-1.84878,42.00898],[-1.87282,41.92488],[-2.11624,41.96],[-2.15744,42.09796],[-2.39982,42.15156],[-2.52925,42.08319],[-2.58436,41.99216],[-2.76211,42.01671],[-2.69233,42.12394],[-2.78314,42.11821],[-2.82382,42.00759],[-2.91498,42.02162],[-2.93729,42.08752],[-3.03892,42.08522],[-3.12646,42.19647],[-3.09574,42.41838],[-3.0596,42.40938],[-3.04278,42.47159],[-3.10037,42.4807],[-3.06973,42.5317],[-3.13204,42.53423],[-3.05136,42.60275],[-3.09037,42.64014],[-2.93824,42.63749],[-2.84314,42.63023],[-2.90073,42.65895],[-2.90107,42.69328],[-2.95188,42.71006],[-2.98055,42.70615],[-3.06612,42.75968],[-3.14749,42.75684],[-3.11127,42.88954],[-3.16861,42.85526],[-3.25324,42.84714],[-3.2892,42.89118],[-3.21075,42.95076],[-3.08827,42.89703],[-3.013,42.90992],[-2.97918,42.94184],[-3.04578,42.97155],[-3.0366,42.98173],[-3.07252,43.00624],[-3.15311,43.008],[-3.1823,43.02425],[-3.14058,43.02927],[-3.14131,43.06876],[-3.15908,43.07243],[-3.13028,43.1006],[-3.18221,43.11392],[-3.15796,43.15955],[-3.13754,43.16981],[-3.22365,43.17252],[-3.25615,43.1981],[-3.41812,43.13193],[-3.65793,43.17363],[-3.73775,43.08167],[-3.85551,43.08029],[-3.81963,43.04367],[-3.96297,42.99761],[-3.99078,42.93116],[-3.92992,42.90023],[-3.86281,42.95849],[-3.82676,42.92224],[-3.86435,42.89923],[-3.90297,42.90765],[-3.89731,42.87816],[-3.92375,42.85413],[-3.86916,42.85029],[-3.86581,42.88885],[-3.79749,42.8022],[-3.97979,42.75596],[-4.01464,42.82738],[-4.05326,42.76365],[-4.12596,42.79231],[-4.15703,42.78803],[-4.16553,42.82902],[-4.12201,42.85507],[-4.15163,42.87112],[-4.21875,42.84324],[-4.22184,42.91281],[-4.41942,43.05283],[-4.68841,43.01431],[-4.8405,43.10724],[-4.83552,43.18077],[-4.89715,43.2382],[-5.00427,43.17263],[-5.0774,43.17864],[-5.09868,43.10323],[-5.38433,43.07565],[-5.39548,43.04844],[-5.59804,43.02347],[-5.7043,43.05948],[-5.78636,42.95579],[-5.96197,43.00715],[-5.97106,43.06437],[-6.09106,43.05872],[-6.1247,43.02083],[-6.215,43.04518],[-6.23165,43.00991],[-6.36983,43.05421],[-6.47283,42.96986],[-6.41567,42.94787],[-6.56484,42.9123],[-6.84984,42.91418],[-6.83813,42.80006],[-6.96009,42.75495],[-6.96799,42.72772],[-7.02026,42.71637],[-7.04858,42.53284]]],[[[-4.10905,42.82219],[-4.1006,42.79354],[-4.07966,42.81243],[-4.10905,42.82219]]],[[[-2.99961,42.60048],[-2.98158,42.6109],[-2.99291,42.61665],[-2.99961,42.60048]]],[[[-2.95017,42.59624],[-2.92287,42.59359],[-2.93437,42.61488],[-2.95017,42.59624]]],[[[-2.86648,42.73743],[-2.74211,42.66249],[-2.65465,42.67309],[-2.63689,42.6462],[-2.51595,42.64627],[-2.53123,42.68874],[-2.57749,42.66937],[-2.61036,42.70079],[-2.54161,42.75218],[-2.67087,42.77259],[-2.73611,42.79376],[-2.84288,42.7896],[-2.86648,42.73743]]]]}},{"type":"Feature","properties":{"id":"ES-CB"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-4.8405,43.10724],[-4.68841,43.01431],[-4.41942,43.05283],[-4.22184,42.91281],[-4.21875,42.84324],[-4.15163,42.87112],[-4.12201,42.85507],[-4.16553,42.82902],[-4.15703,42.78803],[-4.12596,42.79231],[-4.05326,42.76365],[-4.01464,42.82738],[-3.97979,42.75596],[-3.79749,42.8022],[-3.86581,42.88885],[-3.86916,42.85029],[-3.92375,42.85413],[-3.89731,42.87816],[-3.90297,42.90765],[-3.86435,42.89923],[-3.82676,42.92224],[-3.86281,42.95849],[-3.92992,42.90023],[-3.99078,42.93116],[-3.96297,42.99761],[-3.81963,43.04367],[-3.85551,43.08029],[-3.73775,43.08167],[-3.65793,43.17363],[-3.41812,43.13193],[-3.45142,43.23644],[-3.3413,43.28845],[-3.14895,43.30619],[-3.1544,43.341],[-3.10233,43.59549],[-4.52988,43.62298],[-4.51134,43.37941],[-4.54117,43.35785],[-4.51366,43.3005],[-4.54173,43.26801],[-4.6055,43.30088],[-4.64069,43.2712],[-4.73167,43.25495],[-4.72927,43.17864],[-4.83552,43.18077],[-4.8405,43.10724]],[[-4.10905,42.82219],[-4.07966,42.81243],[-4.1006,42.79354],[-4.10905,42.82219]]],[[[-3.30422,43.2582],[-3.28868,43.1991],[-3.27349,43.20195],[-3.24714,43.26248],[-3.27276,43.26545],[-3.30422,43.2582]]]]}},{"type":"Feature","properties":{"id":"ES-RI"},"geometry":{"type":"Polygon","coordinates":[[[-3.13204,42.53423],[-3.06973,42.5317],[-3.10037,42.4807],[-3.04278,42.47159],[-3.0596,42.40938],[-3.09574,42.41838],[-3.12646,42.19647],[-3.03892,42.08522],[-2.93729,42.08752],[-2.91498,42.02162],[-2.82382,42.00759],[-2.78314,42.11821],[-2.69233,42.12394],[-2.76211,42.01671],[-2.58436,41.99216],[-2.52925,42.08319],[-2.39982,42.15156],[-2.15744,42.09796],[-2.11624,41.96],[-1.87282,41.92488],[-1.84878,42.00898],[-1.92054,42.03679],[-1.82973,42.15271],[-1.68519,42.14062],[-1.70717,42.20893],[-1.87076,42.25571],[-2.00672,42.36869],[-2.09135,42.3404],[-2.11298,42.4123],[-2.20928,42.41585],[-2.32652,42.46829],[-2.40102,42.47348],[-2.4148,42.48988],[-2.50951,42.48855],[-2.5096,42.51987],[-2.54101,42.48216],[-2.56977,42.4926],[-2.59466,42.47121],[-2.60401,42.50298],[-2.6507,42.48488],[-2.68014,42.52569],[-2.70649,42.51665],[-2.67654,42.59669],[-2.76529,42.62549],[-2.78039,42.5776],[-2.82897,42.55592],[-2.83936,42.57861],[-2.81601,42.61425],[-2.8449,42.61308],[-2.84314,42.63023],[-2.93824,42.63749],[-3.09037,42.64014],[-3.05136,42.60275],[-3.13204,42.53423]],[[-2.99961,42.60048],[-2.99291,42.61665],[-2.98158,42.6109],[-2.99961,42.60048]],[[-2.95017,42.59624],[-2.93437,42.61488],[-2.92287,42.59359],[-2.95017,42.59624]]]}},{"type":"Feature","properties":{"id":"ES-CT"},"geometry":{"type":"MultiPolygon","coordinates":[[[[0.17063,40.73216],[0.28392,40.62802],[0.43447,40.57132],[0.43241,40.54876],[0.51464,40.52319],[2.8815,40.70665],[3.56345,42.43174],[3.11379,42.43646],[3.09059,42.42304],[3.02935,42.47312],[2.96518,42.46692],[2.94283,42.48174],[2.92107,42.4573],[2.88413,42.45938],[2.8736,42.46751],[2.86329,42.4638],[2.86417,42.45968],[2.86123,42.45367],[2.84335,42.45724],[2.77464,42.41046],[2.75497,42.42578],[2.72056,42.42298],[2.65311,42.38771],[2.67933,42.33637],[2.57934,42.35808],[2.55516,42.35351],[2.54382,42.33406],[2.48457,42.33933],[2.43508,42.37568],[2.43401,42.38941],[2.25551,42.43757],[2.20578,42.41633],[2.16599,42.42314],[2.12789,42.41291],[2.11621,42.38393],[2.06241,42.35906],[2.00488,42.35399],[1.96482,42.37787],[1.9574,42.42401],[1.94084,42.43039],[1.94061,42.43333],[1.94292,42.44316],[1.93663,42.45439],[1.88853,42.4501],[1.83037,42.48395],[1.72334,42.4973],[1.70571,42.48867],[1.66826,42.50779],[1.65674,42.47125],[1.58933,42.46275],[1.57953,42.44957],[1.55937,42.45808],[1.55073,42.43299],[1.5127,42.42959],[1.44529,42.43724],[1.43838,42.47848],[1.41648,42.48315],[1.46661,42.50949],[1.44759,42.54431],[1.41245,42.53539],[1.4234,42.55959],[1.44529,42.56722],[1.42512,42.58292],[1.44197,42.60217],[1.35562,42.71944],[1.15928,42.71407],[1.0804,42.78569],[0.98292,42.78754],[0.96166,42.80629],[0.93089,42.79154],[0.711,42.86372],[0.66121,42.84021],[0.65421,42.75872],[0.67873,42.69458],[0.70432,42.62057],[0.77419,42.60806],[0.70175,42.46867],[0.75994,42.31705],[0.6978,42.17396],[0.69874,42.10331],[0.56236,41.94136],[0.60407,41.88093],[0.54485,41.82416],[0.40082,41.75607],[0.32649,41.67881],[0.34727,41.59798],[0.43052,41.60093],[0.44631,41.54366],[0.3955,41.49006],[0.34006,41.47668],[0.3804,41.23676],[0.19569,41.12152],[0.29045,40.97004],[0.23723,40.88055],[0.28049,40.82108],[0.17063,40.73216]]],[[[1.95606,42.45785],[1.96125,42.45364],[1.98378,42.44697],[1.99838,42.44682],[2.01564,42.45171],[1.99216,42.46208],[1.98579,42.47486],[1.99766,42.4858],[1.98916,42.49351],[1.98022,42.49569],[1.97697,42.48568],[1.97227,42.48487],[1.97003,42.48081],[1.96215,42.47854],[1.95606,42.45785]]]]}},{"type":"Feature","properties":{"id":"IN"},"geometry":{"type":"MultiPolygon","coordinates":[[[[68.11329,23.53945],[69.02459,21.72856],[70.8467,20.44438],[72.47526,20.38318],[72.4768,20.16425],[73.38042,15.62832],[73.87481,14.75496],[74.66307,12.69394],[75.25839,11.52644],[75.26869,11.50222],[76.77589,8.02795],[77.66782,7.85769],[79.37385,8.98767],[79.45362,9.159],[79.42124,9.80115],[80.42924,10.17542],[80.05943,10.8306],[80.05393,10.99781],[79.99831,11.6993],[80.10406,11.97753],[80.56272,13.46443],[82.60962,16.71347],[82.62817,16.73846],[84.98748,18.92967],[87.69835,20.81775],[89.13606,21.42955],[89.13927,21.60785],[89.03553,21.77397],[89.07268,22.19455],[89.04058,22.22713],[89.03234,22.25812],[89.01638,22.25462],[89.01775,22.27114],[88.99423,22.28846],[89.02908,22.29918],[88.98556,22.33086],[88.99011,22.39277],[88.96985,22.41198],[89.00058,22.42991],[88.98977,22.44514],[88.99406,22.47243],[88.97981,22.48385],[88.96059,22.55235],[88.93775,22.55837],[88.93947,22.5934],[88.96436,22.61939],[88.93449,22.61892],[88.92814,22.65045],[88.9472,22.66486],[88.96041,22.70113],[88.92299,22.72251],[88.91492,22.75861],[88.96007,22.80814],[88.96779,22.84738],[88.87063,22.95235],[88.85836,22.94574],[88.8557,22.96708],[88.87115,22.97854],[88.86248,23.00153],[88.84334,23.00849],[88.87776,23.00422],[88.87368,23.0186],[88.88411,23.04044],[88.8748,23.04462],[88.86875,23.08636],[88.93003,23.14446],[88.93672,23.1735],[89.0035,23.21815],[88.94205,23.20821],[88.90737,23.23487],[88.84729,23.23298],[88.81141,23.25506],[88.80437,23.21579],[88.71683,23.2538],[88.7642,23.44781],[88.78892,23.4445],[88.80043,23.5089],[88.7412,23.48576],[88.56525,23.64075],[88.58413,23.87076],[88.67048,23.86857],[88.7,23.90482],[88.73828,23.9191],[88.70429,24.16241],[88.74841,24.1959],[88.69434,24.31737],[88.50934,24.32474],[88.12296,24.51301],[88.08786,24.63232],[88.00683,24.66477],[88.10314,24.78127],[88.1052,24.80387],[88.16167,24.85996],[88.14004,24.93529],[88.22278,24.96271],[88.27325,24.88796],[88.33917,24.86803],[88.46277,25.07468],[88.46054,25.14652],[88.44423,25.19173],[88.48491,25.21177],[88.55632,25.19251],[88.56422,25.17061],[88.61537,25.20121],[88.72163,25.20664],[88.73605,25.18474],[88.80317,25.17076],[88.84265,25.21239],[88.87853,25.179],[88.93758,25.15864],[88.9611,25.20835],[88.95544,25.25354],[89.00762,25.26518],[89.0108,25.30213],[88.99449,25.29607],[88.97981,25.30577],[88.95329,25.30244],[88.92814,25.30352],[88.90565,25.33813],[88.87905,25.3306],[88.86334,25.35488],[88.84034,25.36535],[88.8375,25.40141],[88.81896,25.40932],[88.83888,25.47024],[88.82823,25.48945],[88.81381,25.48992],[88.80918,25.52338],[88.77948,25.51626],[88.75974,25.52648],[88.76661,25.4955],[88.7182,25.50464],[88.70944,25.47985],[88.66756,25.47163],[88.64662,25.48535],[88.6461,25.49798],[88.62396,25.49829],[88.60662,25.5161],[88.59701,25.50836],[88.54019,25.50913],[88.53315,25.53903],[88.49435,25.55916],[88.4983,25.58363],[88.44783,25.5971],[88.45436,25.66349],[88.41161,25.67154],[88.35514,25.72738],[88.26948,25.78165],[88.26004,25.81627],[88.20768,25.78799],[88.18691,25.80128],[88.14811,25.77639],[88.11841,25.8002],[88.08804,25.91334],[88.17661,26.03426],[88.1797,26.14927],[88.35067,26.22244],[88.35865,26.24292],[88.3493,26.25223],[88.35102,26.2841],[88.37634,26.30803],[88.41479,26.3158],[88.41144,26.33642],[88.43487,26.33542],[88.4559,26.37403],[88.48131,26.35042],[88.49727,26.35965],[88.5256,26.35072],[88.48414,26.4602],[88.36938,26.48683],[88.35393,26.4469],[88.33093,26.48929],[88.3705,26.55951],[88.36887,26.57486],[88.40955,26.63802],[88.41642,26.63549],[88.42277,26.56542],[88.44826,26.53593],[88.47504,26.54492],[88.49135,26.51266],[88.51959,26.51136],[88.5274,26.48186],[88.55598,26.48524],[88.56147,26.45743],[88.59323,26.45059],[88.59538,26.47064],[88.62353,26.47088],[88.63005,26.43499],[88.68833,26.40593],[88.67374,26.39686],[88.68876,26.39571],[88.6855,26.38018],[88.70352,26.3358],[88.74197,26.35011],[88.74481,26.33673],[88.73459,26.3298],[88.75502,26.32549],[88.70275,26.31218],[88.67837,26.32265],[88.6679,26.26078],[88.78961,26.31093],[88.83579,26.22983],[88.87493,26.2363],[88.889,26.29772],[88.97071,26.23922],[89.05328,26.2469],[89.05998,26.29403],[88.98599,26.3028],[88.99784,26.33496],[88.91132,26.37018],[88.92728,26.40878],[88.96162,26.45781],[89.00659,26.41401],[89.09105,26.39279],[89.08744,26.32465],[89.13722,26.32049],[89.12195,26.28802],[89.09791,26.31265],[89.141,26.22306],[89.13757,26.18055],[89.15869,26.13708],[89.22992,26.1203],[89.26975,26.05986],[89.35953,26.0077],[89.39025,26.01158],[89.4275,26.04614],[89.43008,26.0122],[89.46544,25.99832],[89.49205,26.0058],[89.54612,26.0058],[89.53908,25.97],[89.58346,25.96761],[89.57951,26.02493],[89.61393,26.05169],[89.65187,26.06156],[89.60517,26.1468],[89.6135,26.17716],[89.65719,26.17161],[89.63058,26.2286],[89.68294,26.22675],[89.68826,26.16067],[89.70201,26.15138],[89.72117,26.15674],[89.74499,26.15959],[89.76465,26.1334],[89.75555,26.10858],[89.78456,26.10519],[89.78198,26.08461],[89.79537,26.08916],[89.79555,26.06788],[89.77272,26.03704],[89.811,26.04336],[89.82722,25.97532],[89.83503,26.01197],[89.8752,25.97895],[89.82241,25.94507],[89.88996,25.9443],[89.83503,25.87111],[89.81683,25.81441],[89.86232,25.73465],[89.84704,25.69529],[89.86515,25.66357],[89.87151,25.66086],[89.88292,25.61807],[89.8655,25.54453],[89.85219,25.53942],[89.86138,25.51541],[89.85074,25.49248],[89.85297,25.47078],[89.83958,25.44908],[89.81185,25.37078],[89.84086,25.31854],[89.83288,25.29553],[89.87837,25.2835],[89.90567,25.30864],[90.11724,25.22419],[90.31568,25.19049],[90.44151,25.14233],[90.50794,25.172],[90.63463,25.17418],[90.65926,25.18979],[90.68819,25.16424],[90.7378,25.15818],[90.78088,25.18102],[90.82071,25.16299],[90.91598,25.16144],[91.07082,25.1936],[91.1939,25.20214],[91.24746,25.19764],[91.27106,25.20789],[91.29261,25.18428],[91.46985,25.15251],[91.47156,25.13557],[91.55422,25.15126],[91.57748,25.17224],[91.61361,25.17643],[91.5979,25.1459],[91.62013,25.14489],[91.62692,25.12306],[91.69438,25.13432],[91.71275,25.16431],[91.74322,25.14722],[91.75334,25.17496],[91.7875,25.16532],[91.95565,25.18117],[91.97916,25.16866],[92.0044,25.18358],[92.03186,25.18342],[92.03538,25.18863],[92.10388,25.1776],[92.13027,25.16311],[92.1431,25.14396],[92.20653,25.13533],[92.22773,25.09803],[92.34652,25.0737],[92.34807,25.05224],[92.42832,25.0293],[92.42197,24.99189],[92.4193,24.96543],[92.45887,24.97049],[92.47029,24.96186],[92.44823,24.93991],[92.48805,24.94909],[92.48599,24.92567],[92.49921,24.90558],[92.48308,24.8633],[92.44188,24.87693],[92.43827,24.85622],[92.38652,24.85147],[92.37356,24.8753],[92.36377,24.87195],[92.34592,24.89453],[92.34043,24.87833],[92.29871,24.91555],[92.27588,24.90558],[92.24018,24.90792],[92.24052,24.85933],[92.29511,24.75478],[92.21991,24.50307],[92.16258,24.53306],[92.12173,24.37461],[91.96603,24.3799],[91.89943,24.12654],[91.84553,24.20798],[91.75283,24.23835],[91.73807,24.14158],[91.65292,24.22095],[91.64125,24.10993],[91.57911,24.07577],[91.3732,24.11448],[91.39663,24.04834],[91.37689,23.97527],[91.30462,23.9944],[91.29406,23.96727],[91.28265,23.97707],[91.26634,23.94766],[91.27793,23.92177],[91.23853,23.92632],[91.24892,23.90828],[91.22574,23.89486],[91.25432,23.84007],[91.22763,23.79453],[91.226,23.73962],[91.16249,23.74701],[91.14618,23.69294],[91.20746,23.68823],[91.20583,23.64924],[91.16008,23.66276],[91.16163,23.59734],[91.22437,23.51496],[91.22016,23.50103],[91.25016,23.48269],[91.24746,23.45297],[91.27089,23.43127],[91.28106,23.37798],[91.29741,23.37881],[91.29428,23.35226],[91.32634,23.36424],[91.2969,23.32602],[91.32445,23.24615],[91.32848,23.15945],[91.37028,23.07128],[91.41423,23.05501],[91.38015,23.18171],[91.40659,23.28589],[91.46693,23.22825],[91.54632,23.0133],[91.61571,22.93929],[91.74347,23.00651],[91.81677,23.08052],[91.79154,23.215],[91.75832,23.28913],[91.84209,23.40693],[91.95642,23.47361],[91.95093,23.73284],[92.04706,23.64229],[92.15417,23.73409],[92.26352,23.72344],[92.40119,23.2385],[92.34455,23.2344],[92.37665,22.9435],[92.5181,22.71441],[92.60029,22.1522],[92.56616,22.13554],[92.60949,21.97638],[92.67532,22.03547],[92.70416,22.16017],[92.86208,22.05456],[92.90588,21.93826],[92.93899,22.02656],[92.99804,21.98964],[92.99255,22.05965],[93.04885,22.20595],[93.15734,22.18687],[93.14224,22.24535],[93.19991,22.25425],[93.18206,22.43716],[93.13537,22.45873],[93.11477,22.54374],[93.134,22.59573],[93.09417,22.69459],[93.134,22.92498],[93.12988,23.05772],[93.2878,23.00464],[93.38478,23.13698],[93.36862,23.35426],[93.38781,23.36139],[93.39981,23.38828],[93.38805,23.4728],[93.43475,23.68299],[93.3908,23.7622],[93.3908,23.92925],[93.36059,23.93176],[93.32351,24.04468],[93.34735,24.10151],[93.41415,24.07854],[93.46633,23.97067],[93.50616,23.94432],[93.62871,24.00922],[93.75952,24.0003],[93.80279,23.92549],[93.92089,23.95812],[94.14149,23.83427],[94.2366,24.03329],[94.28844,24.23092],[94.30518,24.23984],[94.32362,24.27692],[94.3365,24.32895],[94.35333,24.33364],[94.46765,24.57366],[94.54919,24.63687],[94.54696,24.70816],[94.60204,24.70889],[94.73937,25.00545],[94.74212,25.13606],[94.57458,25.20318],[94.68069,25.45815],[94.81421,25.49204],[95.04272,25.72382],[95.03585,25.93056],[95.18556,26.07338],[95.11428,26.1019],[95.12555,26.28318],[95.12801,26.38397],[95.05798,26.45408],[95.23618,26.68266],[95.30339,26.65372],[95.437,26.7083],[95.81603,27.01335],[95.93002,27.04149],[96.04949,27.19428],[96.15591,27.24572],[96.40779,27.29818],[96.55761,27.29928],[96.73888,27.36638],[96.88445,27.25046],[96.84531,27.18939],[97.14675,27.09041],[97.17422,27.14052],[96.91431,27.45752],[96.90696,27.61236],[97.29919,27.92233],[97.35824,27.87256],[97.38845,28.01329],[97.35412,28.06663],[97.31292,28.06784],[97.34547,28.21385],[97.1289,28.3619],[96.98882,28.32564],[96.88445,28.39452],[96.85561,28.4875],[96.6455,28.61657],[96.48895,28.42955],[96.40929,28.51526],[96.61391,28.72742],[96.3626,29.10607],[96.20467,29.02325],[96.18682,29.11087],[96.31316,29.18643],[96.05361,29.38167],[95.84899,29.31464],[95.75149,29.32063],[95.72086,29.20797],[95.50842,29.13487],[95.41091,29.13007],[95.3038,29.13847],[95.26122,29.07727],[95.2214,29.10727],[95.11291,29.09527],[95.0978,29.14446],[94.81353,29.17804],[94.69318,29.31739],[94.2752,29.11687],[94.35897,29.01965],[93.72797,28.68821],[93.62377,28.68426],[93.37623,28.53687],[93.19221,28.52903],[93.14635,28.37035],[92.93609,28.23181],[92.67486,28.15018],[92.65472,28.07632],[92.73025,28.05814],[92.7275,27.98662],[92.42538,27.80092],[92.32101,27.79363],[92.27432,27.89077],[91.87471,27.71605],[91.83437,27.78411],[91.73738,27.77332],[91.66975,27.82435],[91.61189,27.83786],[91.57842,27.82313],[91.64794,27.7756],[91.55819,27.6144],[91.65007,27.48287],[92.01132,27.47352],[92.11512,27.27667],[92.04702,27.26861],[92.03457,27.07334],[92.10834,26.98082],[92.11469,26.89405],[92.08637,26.85684],[92.02045,26.84995],[91.87351,26.93018],[91.82458,26.86358],[91.7391,26.82315],[91.50067,26.79223],[90.67715,26.77215],[90.54914,26.81671],[90.40838,26.90829],[90.29972,26.84857],[90.23174,26.85868],[90.18882,26.76768],[90.05527,26.72859],[89.86124,26.73307],[89.63369,26.74402],[89.42349,26.83727],[89.3901,26.84225],[89.38319,26.85963],[89.37913,26.86224],[89.31816,26.84815],[89.26219,26.82322],[89.1949,26.81135],[89.12924,26.81189],[89.09362,26.87223],[89.09525,26.89153],[89.07937,26.89742],[89.01904,26.94173],[88.98136,26.91694],[88.949,26.93247],[88.95217,26.96927],[88.92153,26.99467],[88.87836,26.94594],[88.87072,26.95627],[88.8799,27.0397],[88.87184,27.10917],[88.74219,27.144],[88.82257,27.25478],[88.90531,27.27522],[88.91901,27.32483],[88.82981,27.38814],[88.77517,27.45415],[88.88091,27.85192],[88.83559,28.01936],[88.63235,28.12356],[88.54858,28.06057],[88.25332,27.9478],[88.1278,27.95417],[88.13378,27.88015],[88.1973,27.85067],[88.19107,27.79285],[88.04008,27.49223],[88.07277,27.43007],[88.01646,27.21612],[88.00889,27.15142],[87.98975,27.1175],[88.02108,27.08747],[88.11743,26.9882],[88.13519,26.98625],[88.12219,26.96845],[88.12084,26.95051],[88.13781,26.93329],[88.14549,26.92055],[88.13747,26.89872],[88.16914,26.872],[88.19107,26.75516],[88.16502,26.67798],[88.16452,26.64111],[88.13163,26.6031],[88.12665,26.57993],[88.09963,26.54195],[88.10382,26.51927],[88.08923,26.50168],[88.104,26.46957],[88.09284,26.43706],[88.02906,26.38494],[88.02992,26.36457],[88.00572,26.36145],[87.99233,26.3711],[87.99164,26.39202],[87.96607,26.39755],[87.93332,26.41758],[87.92083,26.42984],[87.92452,26.44583],[87.8989,26.44886],[87.9089,26.45996],[87.8895,26.48724],[87.85869,26.46327],[87.86144,26.44921],[87.83792,26.43484],[87.82865,26.44982],[87.78951,26.47303],[87.7605,26.40805],[87.73308,26.40828],[87.68033,26.43764],[87.67785,26.41608],[87.64978,26.40597],[87.65132,26.39379],[87.62763,26.39371],[87.61261,26.39033],[87.60498,26.38025],[87.58678,26.38187],[87.58815,26.3914],[87.55274,26.40596],[87.5473,26.41819],[87.51,26.43188],[87.46566,26.44058],[87.36491,26.40463],[87.34568,26.34787],[87.26569,26.37518],[87.26587,26.40592],[87.24682,26.4143],[87.18863,26.40558],[87.15325,26.40509],[87.09147,26.45039],[87.0707,26.58571],[87.04691,26.58685],[87.04004,26.56595],[87.01559,26.53228],[86.95912,26.52076],[86.94543,26.52076],[86.83301,26.43906],[86.76797,26.45892],[86.74025,26.42386],[86.69277,26.45044],[86.62686,26.46891],[86.61313,26.48658],[86.57217,26.49661],[86.54258,26.53819],[86.49726,26.54218],[86.4563,26.5668],[86.39545,26.58484],[86.31975,26.61922],[86.25546,26.61492],[86.23151,26.58975],[86.19812,26.59489],[86.18662,26.61515],[86.13596,26.60651],[86.13942,26.61738],[86.06934,26.65731],[86.03453,26.66502],[85.96312,26.65137],[85.94664,26.61492],[85.84562,26.56254],[85.85583,26.59017],[85.85126,26.60866],[85.83126,26.61134],[85.82317,26.59865],[85.79386,26.62528],[85.76907,26.63076],[85.73756,26.64784],[85.72315,26.67471],[85.73483,26.79613],[85.71996,26.8207],[85.66239,26.84822],[85.61621,26.86721],[85.59388,26.85095],[85.5757,26.85955],[85.56471,26.84133],[85.47752,26.79292],[85.34302,26.74954],[85.21159,26.75933],[85.18046,26.80519],[85.19291,26.86909],[85.15883,26.86966],[85.0237,26.85003],[85.05592,26.88991],[85.00536,26.89523],[84.97186,26.9149],[84.96687,26.95599],[84.89152,26.97241],[84.86608,26.98354],[84.85754,26.98984],[84.85333,27.00836],[84.85187,27.00889],[84.84904,27.0095],[84.82531,27.02063],[84.793,26.9968],[84.77651,27.01623],[84.75686,27.00308],[84.64399,27.04613],[84.64725,27.07632],[84.67257,27.09726],[84.68708,27.22295],[84.62161,27.33885],[84.29315,27.39],[84.25735,27.44941],[84.21376,27.45218],[84.10791,27.52399],[84.02229,27.43836],[83.93306,27.44939],[83.86182,27.4241],[83.85595,27.35797],[83.61288,27.47013],[83.38932,27.4804],[83.40579,27.40758],[83.35136,27.33885],[83.29999,27.32778],[83.2673,27.36235],[83.27197,27.38309],[83.16993,27.45694],[83.03552,27.44781],[82.94969,27.47004],[82.93261,27.50328],[82.80395,27.497],[82.73597,27.50202],[82.75073,27.5865],[82.70378,27.72122],[82.46405,27.6716],[82.06554,27.92222],[81.97214,27.93322],[81.91223,27.84995],[81.47867,28.08303],[81.48179,28.12148],[81.38683,28.17638],[81.32923,28.13521],[81.19847,28.36284],[81.08507,28.38346],[80.89648,28.47237],[80.72427,28.57472],[80.55142,28.69182],[80.50575,28.6706],[80.52443,28.54897],[80.43906,28.63576],[80.37589,28.63071],[80.26576,28.71994],[80.25701,28.75396],[80.21495,28.75562],[80.11762,28.8288],[80.07471,28.82452],[80.06466,28.83813],[80.05743,28.91479],[80.10011,28.98546],[80.11745,28.98156],[80.13547,29.06007],[80.12895,29.06217],[80.13187,29.09232],[80.1844,29.13746],[80.23049,29.11666],[80.26843,29.14061],[80.24285,29.219],[80.29336,29.19604],[80.30611,29.28946],[80.31718,29.31237],[80.28113,29.34417],[80.24272,29.44389],[80.3019,29.45193],[80.28662,29.47644],[80.34464,29.51245],[80.35726,29.53201],[80.34147,29.553],[80.37932,29.56091],[80.37876,29.57129],[80.38932,29.57241],[80.40803,29.59741],[80.40824,29.61805],[80.42447,29.63106],[80.40584,29.65952],[80.38975,29.66504],[80.38155,29.68693],[80.37825,29.70154],[80.36636,29.72406],[80.36477,29.74086],[80.36769,29.75014],[80.3858,29.75062],[80.38576,29.75893],[80.39713,29.75878],[80.41009,29.79417],[80.43026,29.7989],[80.43485,29.80557],[80.44494,29.79905],[80.46159,29.80218],[80.49283,29.79514],[80.49875,29.81227],[80.50699,29.82269],[80.52291,29.8282],[80.53047,29.83781],[80.53506,29.83848],[80.53862,29.84734],[80.54536,29.84712],[80.54802,29.85021],[80.55287,29.85054],[80.55446,29.86107],[80.56247,29.86661],[80.55948,29.86811],[80.57218,29.89453],[80.57441,29.92161],[80.60128,29.9582],[80.62917,29.96512],[80.65355,29.95471],[80.67389,29.95657],[80.72161,30.00013],[80.74384,29.99924],[80.78281,30.06731],[80.87705,30.12798],[80.8925,30.22273],[80.92906,30.17644],[81.03953,30.20059],[80.83343,30.32023],[80.54504,30.44936],[80.20721,30.58541],[79.93255,30.88288],[79.59884,30.93943],[79.22805,31.34963],[79.14016,31.43403],[79.01931,31.42817],[78.77898,31.31209],[78.71032,31.50197],[78.84516,31.60631],[78.69933,31.78723],[78.78036,31.99478],[78.74404,32.00384],[78.68754,32.10256],[78.49609,32.2762],[78.4645,32.45367],[78.38897,32.53938],[78.73916,32.69438],[78.7831,32.46873],[78.96713,32.33655],[78.99322,32.37948],[79.0979,32.38051],[79.13174,32.47766],[79.26768,32.53277],[79.46562,32.69668],[79.14016,33.02545],[79.15252,33.17156],[78.73636,33.56521],[78.67599,33.66445],[78.77349,33.73871],[78.73367,34.01121],[78.65657,34.03195],[78.66225,34.08858],[78.91769,34.15452],[78.99802,34.3027],[79.05364,34.32482],[78.74465,34.45174],[78.56475,34.50835],[78.54964,34.57283],[78.27781,34.61484],[78.18435,34.7998],[78.22692,34.88771],[78.00033,35.23954],[78.03466,35.3785],[78.11664,35.48022],[77.80532,35.52058],[77.70232,35.46244],[77.44277,35.46132],[76.96624,35.5932],[76.84539,35.67356],[76.77323,35.66062],[76.75475,35.52617],[76.85088,35.39754],[76.93465,35.39866],[77.11796,35.05419],[76.93725,34.99569],[76.89966,34.92999],[76.77675,34.94702],[76.74377,34.84039],[76.67409,34.74598],[76.47186,34.78965],[76.14074,34.63462],[76.04614,34.67566],[75.75438,34.51827],[75.38009,34.55021],[75.01479,34.64629],[74.89194,34.66628],[74.6663,34.703],[74.58137,34.76389],[74.31667,34.78673],[74.12149,34.67528],[73.9706,34.68516],[73.93401,34.63386],[73.95584,34.56269],[73.90125,34.51843],[73.88732,34.48911],[73.75731,34.38226],[73.74862,34.34183],[73.8475,34.32935],[73.90517,34.35317],[74.01077,34.21677],[73.90678,34.10686],[73.89636,34.05635],[73.94373,34.01617],[74.07875,34.03523],[74.21625,34.02605],[74.26637,33.98635],[74.2783,33.89535],[74.14001,33.83002],[74.05763,33.82443],[74.00891,33.75437],[73.96232,33.72298],[73.98968,33.66155],[73.98296,33.64338],[74.03295,33.57662],[74.1372,33.56092],[74.18354,33.47626],[74.17983,33.3679],[74.1275,33.30571],[74.00794,33.25462],[74.01309,33.21556],[74.15374,33.13477],[74.17548,33.075],[74.31854,33.02891],[74.33976,32.95682],[74.30783,32.92858],[74.41143,32.89904],[74.44687,32.79506],[74.46335,32.77695],[74.54137,32.74815],[74.6369,32.75407],[74.64008,32.82089],[74.70952,32.84202],[74.65227,32.69724],[74.69758,32.66351],[74.64068,32.61118],[74.65251,32.56416],[74.67175,32.56844],[74.69098,32.52995],[74.68351,32.49209],[74.81243,32.48116],[74.82101,32.49796],[74.99181,32.45024],[75.02683,32.49745],[75.10906,32.47428],[75.13532,32.41329],[75.19334,32.42402],[75.1954,32.40445],[75.28784,32.37322],[75.33265,32.32703],[75.32226,32.29946],[75.37719,32.2716],[75.36672,32.22325],[75.32432,32.21527],[75.25763,32.09951],[74.99516,32.04147],[74.93079,32.06657],[74.8774,32.04991],[74.81595,31.96439],[74.60866,31.88776],[74.55253,31.76655],[74.48884,31.71809],[74.57498,31.60382],[74.62248,31.54686],[74.58626,31.51175],[74.65355,31.45685],[74.64574,31.41796],[74.60334,31.42507],[74.53223,31.30321],[74.51013,31.13848],[74.56023,31.08303],[74.60952,31.09586],[74.60008,31.13334],[74.69836,31.12467],[74.68497,31.05594],[74.55948,31.04359],[74.53957,30.98997],[74.41829,30.93594],[74.37074,30.85493],[74.32371,30.84756],[74.27959,30.74006],[74.23427,30.72294],[74.10793,30.61147],[74.09694,30.56684],[74.07154,30.52426],[74.01849,30.52441],[73.95566,30.46938],[73.93232,30.49483],[73.93953,30.40796],[73.88993,30.36305],[73.95736,30.28466],[73.97225,30.19829],[73.80299,30.06969],[73.58665,30.01848],[73.3962,29.94707],[73.28094,29.56646],[73.08654,29.23877],[73.01337,29.16422],[72.94272,29.02487],[72.40402,28.78283],[72.29495,28.66367],[72.20329,28.3869],[71.9244,28.11555],[71.89921,27.96035],[70.79054,27.68423],[70.60927,28.02178],[70.37307,28.01208],[70.12502,27.8057],[70.03136,27.56627],[69.58519,27.18109],[69.50904,26.74892],[69.88555,26.56836],[70.05584,26.60398],[70.17532,26.55362],[70.17532,26.24118],[70.08193,26.08094],[70.0985,25.93238],[70.2687,25.71156],[70.37444,25.67443],[70.53649,25.68928],[70.60378,25.71898],[70.67382,25.68186],[70.66695,25.39314],[70.89148,25.15064],[70.94002,24.92843],[71.09405,24.69017],[70.97594,24.60904],[71.00341,24.46038],[71.12838,24.42662],[71.04461,24.34657],[70.98266,24.36586],[70.87142,24.29766],[70.90164,24.22066],[70.70079,24.21377],[70.58097,24.25447],[70.58235,24.42339],[70.11268,24.29359],[70.03428,24.172],[69.73335,24.17007],[69.59579,24.29777],[69.30484,24.27826],[69.1888,24.23256],[69.09679,24.272],[68.99105,24.21972],[68.92719,24.31393],[68.76411,24.30736],[68.74643,23.97027],[68.39339,23.96838],[68.20763,23.85849],[68.11329,23.53945]]],[[[71.37481,12.9453],[73.04319,7.79398],[73.97643,10.90343],[71.37481,12.9453]]],[[[91.77977,11.01669],[93.62843,6.54546],[94.20774,6.67551],[94.64499,13.56452],[92.61282,13.95915],[91.77977,11.01669]]]]}},{"type":"Feature","properties":{"id":"IN-DN"},"geometry":{"type":"MultiPolygon","coordinates":[[[[72.92346,20.26348],[72.97067,20.21516],[72.96775,20.1354],[73.04088,20.0727],[73.06783,20.09946],[73.18971,20.04948],[73.21615,20.11961],[73.16971,20.20251],[73.06268,20.15078],[73.05736,20.21983],[73.17872,20.29335],[73.09512,20.35911],[73.03221,20.29424],[72.97925,20.29021],[72.98526,20.27041],[72.93067,20.29681],[72.92346,20.26348]],[[72.98681,20.21661],[72.98698,20.23578],[73.0129,20.23578],[73.00947,20.21339],[72.98681,20.21661]]],[[[72.92346,20.32804],[72.96106,20.30712],[73.00621,20.33448],[72.96569,20.36715],[72.92346,20.32804]]]]}},{"type":"Feature","properties":{"id":"IN-GJ"},"geometry":{"type":"Polygon","coordinates":[[[68.11329,23.53945],[69.02459,21.72856],[70.8467,20.44438],[70.87331,20.73203],[71.00154,20.74648],[72.83901,20.48555],[72.90801,20.43087],[72.89291,20.36748],[72.47526,20.38318],[72.4768,20.16425],[72.78665,20.1238],[72.8754,20.22869],[72.97067,20.21516],[72.92346,20.26348],[72.93067,20.29681],[72.98526,20.27041],[72.97925,20.29021],[73.03221,20.29424],[73.09512,20.35911],[73.17872,20.29335],[73.05736,20.21983],[73.06268,20.15078],[73.16971,20.20251],[73.21615,20.11961],[73.27889,20.15087],[73.28919,20.20405],[73.42128,20.20034],[73.40137,20.39258],[73.48274,20.54022],[73.39399,20.64868],[73.46437,20.73652],[73.48548,20.66153],[73.55432,20.64756],[73.58814,20.64563],[73.65663,20.56095],[73.69354,20.57783],[73.7622,20.57493],[73.88545,20.72753],[73.93901,20.73588],[73.9179,20.90981],[73.89678,20.96127],[73.75602,21.09218],[73.66916,21.10788],[73.57578,21.16024],[73.73319,21.14295],[73.7368,21.1676],[73.82228,21.17624],[73.81164,21.21658],[73.82915,21.26746],[73.93541,21.29065],[74.01712,21.42047],[74.04922,21.42023],[74.04622,21.44779],[74.06158,21.46161],[74.10037,21.44875],[74.15857,21.46824],[74.22346,21.48038],[74.24886,21.4676],[74.32302,21.50274],[74.32628,21.55751],[74.25916,21.54075],[74.2317,21.55352],[74.16689,21.56917],[74.12904,21.55129],[74.11874,21.55648],[74.03205,21.54562],[73.97489,21.54147],[73.97747,21.52103],[73.84923,21.49907],[73.78864,21.63365],[73.89284,21.65966],[73.79602,21.82803],[74.14947,21.95323],[74.09763,22.01563],[74.17625,22.08341],[74.13333,22.10552],[74.11651,22.2149],[74.07875,22.22205],[74.06776,22.38246],[74.15685,22.32911],[74.29418,22.39182],[73.98296,22.51477],[74.18346,22.55187],[74.26929,22.64633],[74.37675,22.6346],[74.46945,22.86605],[74.44507,22.92266],[74.38259,22.90021],[74.3201,23.0573],[74.28216,23.09252],[74.24594,23.18107],[74.1469,23.14967],[74.11634,23.18376],[74.13351,23.26752],[74.04287,23.29559],[74.02381,23.33232],[73.98502,23.33658],[73.96064,23.37913],[73.90657,23.31624],[73.82984,23.44104],[73.72049,23.41394],[73.63088,23.45348],[73.63655,23.65678],[73.51089,23.61401],[73.48136,23.72501],[73.34094,23.78063],[73.42231,23.92601],[73.37116,24.11761],[73.2431,24.00319],[73.0632,24.18966],[73.21134,24.37383],[73.131,24.34725],[73.07916,24.39916],[73.09169,24.49542],[72.97874,24.46933],[72.96157,24.35241],[72.70992,24.36836],[72.68417,24.46418],[72.59954,24.46886],[72.53877,24.51589],[72.46925,24.41292],[72.35303,24.56304],[72.24952,24.54493],[72.27493,24.62751],[72.14618,24.63359],[72.04593,24.70317],[71.9522,24.63921],[71.87461,24.66605],[71.83341,24.62173],[71.79805,24.67197],[71.65626,24.6442],[71.57901,24.67946],[71.30126,24.62766],[71.09405,24.69017],[70.97594,24.60904],[71.00341,24.46038],[71.12838,24.42662],[71.04461,24.34657],[70.98266,24.36586],[70.87142,24.29766],[70.90164,24.22066],[70.70079,24.21377],[70.58097,24.25447],[70.58235,24.42339],[70.11268,24.29359],[70.03428,24.172],[69.73335,24.17007],[69.59579,24.29777],[69.30484,24.27826],[69.1888,24.23256],[69.09679,24.272],[68.99105,24.21972],[68.92719,24.31393],[68.76411,24.30736],[68.74643,23.97027],[68.39339,23.96838],[68.20763,23.85849],[68.11329,23.53945]],[[72.92346,20.32804],[72.96569,20.36715],[73.00621,20.33448],[72.96106,20.30712],[72.92346,20.32804]]]}}]} \ No newline at end of file diff --git a/core/src/main/resources/com/graphhopper/custom_models/avoid_turns.json b/core/src/main/resources/com/graphhopper/custom_models/avoid_turns.json new file mode 100644 index 00000000000..1c37ff4b9b7 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/avoid_turns.json @@ -0,0 +1,16 @@ +{ + "turn_penalty": [ + // straight: + { "if": "change_angle > -25 && change_angle < 25", "add": "0" }, + // right: + { "else_if": "change_angle >= 25 && change_angle < 80", "add": "3" }, + // sharp right: + { "else_if": "change_angle >= 80 && change_angle <= 180", "add": "3" }, + // left: + { "else_if": "change_angle <= -25 && change_angle > -80", "add": "3" }, + // sharp left: + { "else_if": "change_angle <= -80 && change_angle >= -180", "add": "3" }, + // uTurnCosts: + { "else": "", "add": "Infinity" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike.json b/core/src/main/resources/com/graphhopper/custom_models/bike.json index 74a81723925..8cd4fab50a2 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bike.json @@ -1,21 +1,33 @@ // to use this custom model you need to set the following option in the config.yml -// graph.vehicles: roads,bike -// graph.encoded_values: average_slope,max_slope // graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: bike_priority, bike_access, bike_network, roundabout, bike_average_speed, bike_road_access, foot_road_access, average_slope, mtb_rating, hike_rating, country, road_class, ferry_speed // profiles: // - name: bike -// vehicle: roads -// weighting: custom -// custom_model_files: [bike.json, bike_elevation.json] +// custom_model_files: [bike.json, bike_avoid_private_node.json, bike_elevation.json] +// +// To reduce turns and enable a more advanced "private-road avoidance" you can enable turn costs: +// profiles: +// - name: bike +// turn_costs: +// vehicle_types: [bicycle] +// u_turn_costs: 10 +// custom_model_files: [bike.json, bike_avoid_private.json, avoid_turns.json, bike_elevation.json] { "priority": [ { "if": "true", "multiply_by": "bike_priority" }, + { "if": "bike_network == INTERNATIONAL || bike_network == NATIONAL", "multiply_by": "1.4" }, + { "else_if": "bike_network == REGIONAL || bike_network == LOCAL", "multiply_by": "1.2" }, + { "if": "road_environment == FERRY", "multiply_by": "0.5" }, + { "if": "mtb_rating > 2", "multiply_by": "0" }, + { "if": "hike_rating > 1", "multiply_by": "0" }, + { "if": "bike_road_access == NO", "multiply_by": "0" }, { "if": "!bike_access && (!backward_bike_access || roundabout)", "multiply_by": "0" }, { "else_if": "!bike_access && backward_bike_access", "multiply_by": "0.2" } ], "speed": [ - { "if": "true", "limit_to": "bike_average_speed" }, - { "if": "!bike_access && backward_bike_access && !roundabout", "limit_to": "5" } + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "bike_average_speed" }, + { "if": "!bike_access && backward_bike_access", "limit_to": "6" } ] -} \ No newline at end of file +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private.json b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private.json new file mode 100644 index 00000000000..30a616e40f0 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private.json @@ -0,0 +1,9 @@ +{ + // Note, 'turn_penalty' requires enabled turn_costs in profile + "turn_penalty": [ + { + "if": "prev_bike_road_access != bike_road_access && bike_road_access == PRIVATE", + "add": "2000" + } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_node.json b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_node.json new file mode 100644 index 00000000000..f17c78bbcc8 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_node.json @@ -0,0 +1,6 @@ +{ + // prefer bike_avoid_private.json as this is less precise + "priority": [ + { "if": "bike_road_access == PRIVATE", "multiply_by": "0.1" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_elevation.json b/core/src/main/resources/com/graphhopper/custom_models/bike_elevation.json index 75e2bf4df96..2ff62561fb8 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bike_elevation.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_elevation.json @@ -1,8 +1,8 @@ { "speed": [ { "if": "average_slope >= 15", "limit_to": "3"}, - { "if": "average_slope >= 12", "limit_to": "6"}, - { "else_if": "average_slope >= 8", "multiply_by": "0.80"}, + { "else_if": "average_slope >= 12", "limit_to": "6"}, + { "else_if": "average_slope >= 8", "multiply_by": "0.60"}, { "else_if": "average_slope >= 4", "multiply_by": "0.90"}, { "else_if": "average_slope <= -4", "multiply_by": "1.10"} ] diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json new file mode 100644 index 00000000000..9a423e0462b --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json @@ -0,0 +1,33 @@ +// Configures bike that avoids zig-zag routes and uses the turn_costs based approach to avoid private-only and destination-only roads. +// +// Note, it is not recommended to increase these costs heavily as otherwise larger, bike-unfriendly +// roads will be preferred as they often require less turns. +// +// To use this custom model you need to set the following option in the config.yml +// graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: bike_priority, bike_access, roundabout, bike_average_speed, bike_road_access, average_slope, orientation, ferry_speed +// profiles: +// - name: bike +// turn_costs: +// vehicle_types: [bicycle] +// u_turn_costs: 20 +// custom_model_files: [bike_tc.json, avoid_turns.json, bike_avoid_private.json, bike_elevation.json] + +{ + "priority": [ + { "if": "true", "multiply_by": "bike_priority" }, + { "if": "bike_network == INTERNATIONAL || bike_network == NATIONAL", "multiply_by": "1.4" }, + { "else_if": "bike_network == REGIONAL || bike_network == LOCAL", "multiply_by": "1.2" }, + { "if": "road_environment == FERRY", "multiply_by": "0.5" }, + { "if": "mtb_rating > 2", "multiply_by": "0" }, + { "if": "hike_rating > 1", "multiply_by": "0" }, + { "if": "bike_road_access == NO", "multiply_by": "0" }, + { "if": "!bike_access && (!backward_bike_access || roundabout)", "multiply_by": "0" }, + { "else_if": "!bike_access && backward_bike_access", "multiply_by": "0.2" } + ], + "speed": [ + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "bike_average_speed" }, + { "if": "!bike_access && backward_bike_access", "limit_to": "6" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/bus.json b/core/src/main/resources/com/graphhopper/custom_models/bus.json index 226524625e5..1e7208781e2 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bus.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bus.json @@ -1,24 +1,25 @@ // to use this custom model you need to set the following option in the config.yml -// graph.vehicles: roads|transportation_mode=BUS,car -// graph.encoded_values: max_width,max_height +// graph.encoded_values: max_weight, max_width, max_height, bus_access, road_class, car_average_speed, max_speed, ferry_speed // profiles: // - name: bus -// vehicle: roads -// weighting: custom // custom_model_files: [bus.json] +// +// There is also a hov_access which might be suitable for carpooling and can replace or be combined with bus_access { "distance_influence": 90, "priority": [ { "if": "max_weight < 5 || max_width < 3 || max_height < 4", "multiply_by": "0" }, - { "if": "bus_access && (road_class == MOTORWAY || road_class == TRUNK || road_class == PRIMARY || road_class == SECONDARY || road_class == TERTIARY || road_class == UNCLASSIFIED || road_class == LIVING_STREET || road_class == RESIDENTIAL || road_class == SERVICE || road_class == ROAD)", + { "if": "bus_access && (road_class == BUSWAY || road_class == MOTORWAY || road_class == TRUNK || road_class == PRIMARY || road_class == SECONDARY || road_class == TERTIARY || road_class == UNCLASSIFIED || road_class == LIVING_STREET || road_class == RESIDENTIAL || road_class == SERVICE || road_class == ROAD)", "multiply_by": "1" }, { "else": "", "multiply_by": "0" } ], "speed": [ - { "if": "bus_access && car_average_speed < 10", "limit_to": "10" }, + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else_if": "bus_access && car_average_speed < 10", "limit_to": "10" }, { "else": "", "limit_to": "car_average_speed * 0.9" }, + { "if": "true", "limit_to": "max_speed * 0.9" }, { "if": "true", "limit_to": "100" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/car.json b/core/src/main/resources/com/graphhopper/custom_models/car.json new file mode 100644 index 00000000000..15c60f4cc58 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/car.json @@ -0,0 +1,19 @@ +// to use this custom model you need to set the following option in the config.yml +// graph.encoded_values: car_access|block_private=false, car_average_speed, road_access, max_speed, ferry_speed +// profiles: +// - name: car +// turn_costs: +// vehicle_types: [motorcar, motor_vehicle] +// custom_model_files: [car.json, car_avoid_private_etc.json ] + +{ + "distance_influence": 90, + "priority": [ + { "if": "!car_access", "multiply_by": "0" } + ], + "speed": [ + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "car_average_speed" }, + { "if": "true", "limit_to": "max_speed * 0.9" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/car4wd.json b/core/src/main/resources/com/graphhopper/custom_models/car4wd.json index fa5db9d809e..d1dc37eb73c 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/car4wd.json +++ b/core/src/main/resources/com/graphhopper/custom_models/car4wd.json @@ -1,11 +1,10 @@ // to use this custom model you need to set the following option in the config.yml -// graph.vehicles: roads -// graph.encoded_values: track_type +// graph.encoded_values: car_access, car_average_speed, track_type, road_access, max_speed, ferry_speed // profiles: // - name: car4wd -// vehicle: roads -// weighting: custom -// custom_model_files: [car4wd.json] +// turn_costs: +// vehicle_types: [motorcar, motor_vehicle +// custom_model_files: [car4wd.json, car_avoid_private_etc.json] { "distance_influence": 1, @@ -13,14 +12,10 @@ { "if": "track_type != GRADE4 && track_type != GRADE5 && car_access == false", "multiply_by": "0" } ], "speed": [ - { - "if": "track_type == GRADE4 || track_type == GRADE5", - "limit_to": 5 - }, - { - "else": "", - "limit_to": "car_average_speed" - } + { "if": "track_type == GRADE4 || track_type == GRADE5", "limit_to": 5 }, + { "else_if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "car_average_speed" }, + { "if": "true", "limit_to": "max_speed * 0.9" } ] -} \ No newline at end of file +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc.json b/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc.json new file mode 100644 index 00000000000..7da55d9c72c --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc.json @@ -0,0 +1,10 @@ +{ + // Note, 'turn_penalty' requires enabled turn_costs in profile and due to the use of road_access + // it is suitable for motor vehicles only. + "turn_penalty": [ + { + "if": "prev_road_access != road_access && (road_access == DESTINATION || road_access == PRIVATE || road_access == DELIVERY || road_access == CUSTOMERS || road_access == MILITARY)", + "add": "2000" + } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc_node.json b/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc_node.json new file mode 100644 index 00000000000..fcfb3b87fc9 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/car_avoid_private_etc_node.json @@ -0,0 +1,5 @@ +{ + "priority": [ + { "if": "road_access == DESTINATION || road_access == PRIVATE || road_access == DELIVERY || road_access == CUSTOMERS || road_access == MILITARY", "multiply_by": "0.1" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json b/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json index ce8cfa00b82..8ffcf10b00b 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/cargo_bike.json @@ -3,7 +3,6 @@ { "priority": [ - { "if": "road_access == PRIVATE", "multiply_by": "0" }, { "if": "road_class == STEPS", "multiply_by": 0 }, { "if": "surface == SAND", "multiply_by": 0.5 }, { "if": "track_type != MISSING && track_type != GRADE1", "multiply_by": 0.9 }, @@ -12,7 +11,8 @@ { "if": "max_width < 1.2", "multiply_by": 0 } ], "speed": [ - { "if": "road_class == PRIMARY", "limit_to": 28 }, + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else_if": "road_class == PRIMARY", "limit_to": 28 }, { "else": "", "limit_to": 25 } ] -} \ No newline at end of file +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/foot.json b/core/src/main/resources/com/graphhopper/custom_models/foot.json new file mode 100644 index 00000000000..7cde24c39dd --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/foot.json @@ -0,0 +1,21 @@ +// to use this custom model you need to set the following option in the config.yml +// graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: foot_access, foot_priority, foot_average_speed, foot_road_access, hike_rating, mtb_rating, average_slope, country, road_class, ferry_speed +// profiles: +// - name: foot +// custom_model_files: [foot.json, foot_avoid_private_node.json, foot_elevation.json] + +{ + "priority": [ + { "if": "!foot_access || hike_rating >= 2", "multiply_by": "0" }, + { "else": "", "multiply_by": "foot_priority"}, + { "if": "foot_road_access == NO", "multiply_by": "0" }, + // note that mtb_rating=0 is the default and mtb_rating=1 corresponds to mtb:scale=0 and so on + { "if": "mtb_rating > 3", "multiply_by": "0.7" }, + { "if": "road_environment == FERRY", "multiply_by": "0.5" } + ], + "speed": [ + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "foot_average_speed" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private.json b/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private.json new file mode 100644 index 00000000000..8b1f13382ae --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private.json @@ -0,0 +1,9 @@ +{ + // Note, 'turn_penalty' requires enabled turn_costs in profile + "turn_penalty": [ + { + "if": "prev_foot_road_access != foot_road_access && foot_road_access == PRIVATE", + "add": "2000" + } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private_node.json b/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private_node.json new file mode 100644 index 00000000000..82d957dcac1 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/foot_avoid_private_node.json @@ -0,0 +1,6 @@ +{ + // prefer foot_avoid_private.json as this is less precise + "priority": [ + { "if": "foot_road_access == PRIVATE", "multiply_by": "0.1" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/hike.json b/core/src/main/resources/com/graphhopper/custom_models/hike.json index 9771a3f5e6d..9743f3752f5 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/hike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/hike.json @@ -1,23 +1,22 @@ // to use this custom model you set the following option in the config.yml: -// graph.vehicles: roads,foot -// graph.encoded_values: hike_rating,average_slope,max_slope // graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: foot_access, foot_priority, foot_network, foot_average_speed, foot_road_access, hike_rating, average_slope, ferry_speed // profiles: // - name: hike -// vehicle: roads -// weighting: custom // custom_model_files: [hike.json, foot_elevation.json] { "priority": [ - { "if": "!foot_access && (hike_rating < 4 || road_access == PRIVATE)", "multiply_by": "0"}, - { "if": "true", "multiply_by": "foot_priority"}, + { "if": "!foot_access || hike_rating >= 6", "multiply_by": "0"}, + { "else": "", "multiply_by": "foot_priority"}, { "if": "foot_network == INTERNATIONAL || foot_network == NATIONAL", "multiply_by": "1.7"}, - { "else_if": "foot_network == REGIONAL || foot_network == LOCAL", "multiply_by": "1.5"} + { "else_if": "foot_network == REGIONAL || foot_network == LOCAL", "multiply_by": "1.5"}, + { "if": "road_environment == FERRY", "multiply_by": "0.5" } ], "speed": [ - { "if": "hike_rating < 1", "limit_to": "foot_average_speed" }, - { "else_if": "hike_rating > 2", "limit_to": "2" }, + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else_if": "hike_rating < 1", "limit_to": "foot_average_speed" }, + { "else_if": "hike_rating > 2", "limit_to": "1.5" }, { "else": "", "limit_to": "4" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json b/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json index 7d2b0cbd1c3..7a68d73f035 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json +++ b/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json @@ -1,11 +1,8 @@ // to use this custom model you need to set the following option in the config.yml -// graph.vehicles: roads|transportation_mode=MOTORCYCLE,car // graph.urban_density.threads: 4 # expensive to calculate but very useful -// graph.encoded_values: curvature,track_type,surface +// graph.encoded_values: car_access, track_type, road_access, road_class, curvature, car_average_speed, surface, max_speed, ferry_speed // profiles: // - name: motorcycle -// vehicle: roads -// weighting: custom // custom_model_files: [motorcycle.json,curvature.json] { @@ -13,12 +10,13 @@ "priority": [ { "if": "!car_access", "multiply_by": "0"}, { "if": "track_type.ordinal() > 1", "multiply_by": "0" }, - { "if": "road_access == PRIVATE", "multiply_by": "0" }, { "if": "road_class == MOTORWAY || road_class == TRUNK", "multiply_by": "0.1" } // { "if": "urban_density != RURAL", "multiply_by": "0.3" }, ], "speed": [ - { "if": "true", "limit_to": "0.9 * car_average_speed" }, + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "0.9 * car_average_speed" }, + { "if": "true", "limit_to": "max_speed * 0.9" }, { "if": "true", "limit_to": "120" }, { "if": "surface==COBBLESTONE || surface==GRASS || surface==GRAVEL || surface==SAND || surface==PAVING_STONES || surface==DIRT || surface==GROUND || surface==UNPAVED || surface==COMPACTED", "limit_to": "30" diff --git a/core/src/main/resources/com/graphhopper/custom_models/mtb.json b/core/src/main/resources/com/graphhopper/custom_models/mtb.json new file mode 100644 index 00000000000..2b57f0de795 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/mtb.json @@ -0,0 +1,27 @@ +// to use this custom model you need to set the following option in the config.yml +// graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: mtb_priority, mtb_access, bike_network, mtb_network, roundabout, mtb_average_speed, bike_road_access, average_slope, mtb_rating, hike_rating, country, road_class, ferry_speed +// profiles: +// - name: mtb +// custom_model_files: [mtb.json, bike_elevation.json] + +{ + "priority": [ + { "if": "true", "multiply_by": "mtb_priority" }, + { "if": "bike_network == INTERNATIONAL || bike_network == NATIONAL || mtb_network == INTERNATIONAL || mtb_network == NATIONAL", "multiply_by": "1.5" }, + { "else_if": "bike_network == REGIONAL || bike_network == LOCAL || mtb_network == REGIONAL || mtb_network == LOCAL", "multiply_by": "1.3" }, + { "if": "road_environment == FERRY", "multiply_by": "0.5" }, + { "if": "mtb_rating > 6", "multiply_by": "0" }, + { "else_if": "mtb_rating > 3", "multiply_by": "0.5" }, + { "if": "hike_rating > 4", "multiply_by": "0" }, + { "if": "bike_road_access == NO", "multiply_by": "0" }, + { "if": "!mtb_access && (!backward_mtb_access || roundabout)", "multiply_by": "0" }, + { "else_if": "!mtb_access && backward_mtb_access", "multiply_by": "0.2" } + ], + "speed": [ + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "mtb_average_speed" }, + { "if": "mtb_rating > 3", "limit_to": "4" }, + { "if": "!mtb_access && backward_mtb_access", "limit_to": "6" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/racingbike.json b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json new file mode 100644 index 00000000000..c609166fff9 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/racingbike.json @@ -0,0 +1,26 @@ +// to use this custom model you need to set the following option in the config.yml +// graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: racingbike_priority, racingbike_access, bike_network, roundabout, racingbike_average_speed, bike_road_access, average_slope, mtb_rating, hike_rating, sac_scale, country, road_class, ferry_speed +// profiles: +// - name: racingbike +// custom_model_files: [racingbike.json, bike_elevation.json] + +{ + "priority": [ + { "if": "true", "multiply_by": "racingbike_priority" }, + { "if": "bike_network == INTERNATIONAL || bike_network == NATIONAL", "multiply_by": "1.4" }, + { "else_if": "bike_network == REGIONAL || bike_network == LOCAL", "multiply_by": "1.2" }, + { "if": "road_environment == FERRY", "multiply_by": "0.5" }, + { "if": "mtb_rating > 2", "multiply_by": "0" }, + { "else_if": "mtb_rating == 2", "multiply_by": "0.5" }, + { "if": "hike_rating > 1", "multiply_by": "0" }, + { "if": "bike_road_access == NO", "multiply_by": "0" }, + { "if": "!racingbike_access && (!backward_racingbike_access || roundabout)", "multiply_by": "0" }, + { "else_if": "!racingbike_access && backward_racingbike_access", "multiply_by": "0.2" } + ], + "speed": [ + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "racingbike_average_speed" }, + { "if": "!racingbike_access && backward_racingbike_access", "limit_to": "5" } + ] +} diff --git a/core/src/main/resources/com/graphhopper/custom_models/truck.json b/core/src/main/resources/com/graphhopper/custom_models/truck.json index 7e96751d22f..29a87aef932 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/truck.json +++ b/core/src/main/resources/com/graphhopper/custom_models/truck.json @@ -1,20 +1,24 @@ // to use this custom model you need to set the following option in the config.yml -// graph.vehicles: roads|transportation_mode=HGV,car -// graph.encoded_values: toll,hgv,surface,max_width,max_height +// graph.encoded_values: road_access, car_access, hgv, max_width, max_height, car_average_speed, max_weight, max_weight_except, max_speed, ferry_speed // profiles: // - name: truck -// vehicle: roads -// weighting: custom +// turn_costs: +// vehicle_types: [hgv, motorcar, motor_vehicle] // custom_model_files: [truck.json] { "distance_influence": 1, "priority": [ - { "if": "road_access == PRIVATE", "multiply_by": "0" }, - { "if": "car_access == false || hgv == NO || max_width < 3 || max_height < 4", "multiply_by": "0" } + { "if": "hgv == NO", "multiply_by": "0" }, + { "if": "!car_access && road_access != PRIVATE && hgv != DELIVERY && hgv != DESTINATION", "multiply_by": "0" }, + { "if": "hgv == DELIVERY || hgv == DESTINATION", "multiply_by": "0.1" }, + { "if": "max_width < 3 || max_height < 4", "multiply_by": "0" }, + { "if": "max_weight < 18 && max_weight_except == MISSING", "multiply_by": "0" } ], "speed": [ - { "if": "true", "limit_to": "car_average_speed * 0.9" }, + { "if": "road_environment == FERRY", "limit_to": "ferry_speed" }, + { "else": "", "limit_to": "car_average_speed * 0.9" }, + { "if": "true", "limit_to": "max_speed * 0.9" }, { "if": "true", "limit_to": "95" } ] -} \ No newline at end of file +} diff --git a/core/src/main/resources/com/graphhopper/routing/util/legal_default_speeds.json b/core/src/main/resources/com/graphhopper/routing/util/legal_default_speeds.json index 64ed39f7d45..e46ceaf07cf 100644 --- a/core/src/main/resources/com/graphhopper/routing/util/legal_default_speeds.json +++ b/core/src/main/resources/com/graphhopper/routing/util/legal_default_speeds.json @@ -1,10256 +1,10411 @@ { - "meta": { - "license": "Creative Commons Attribution-ShareAlike 2.0 license", - "licenseUrl": "https://wiki.openstreetmap.org/wiki/Wiki_content_license", - "revisionId": "2538588", - "source": "https://wiki.openstreetmap.org/wiki/Default_speed_limits", - "timestamp": "2023-06-06T11:55:04" - }, - "roadTypesByName": { - "Alabama: rural highway": { - "filter": "{rural} and ref~\"(US|AL|I).*\"", - "fuzzyFilter": "{rural} and highway~trunk|trunk_link|primary|primary_link", - "relationFilter": "type=route and route=road and network~\"US:(AL|US)(:.*)?\"" - }, - "Alabama: rural highway with 2 or more lanes in each direction": { - "filter": "{Alabama: rural highway} and {road with 2 or more lanes in each direction}" - }, - "Albania: Rrug\u00eb interurbane kryesore": { - "filter": "{rural} and {dual carriageway} and lanes>=2" - }, - "Alberta: Provincial highway": { - "filter": "ref" - }, - "Alberta: Urban provincial highway": { - "filter": "{urban} and ref" - }, - "Alberta: Urban road": { - "filter": "{urban} and !ref" - }, - "Andorra: Carretera general": { - "filter": "ref~CG.*", - "fuzzyFilter": "highway=primary" - }, - "Andorra: Carretera secund\u00e0ria": { - "filter": "ref~CS.*", - "fuzzyFilter": "highway=secondary" - }, - "Argentina: Avenida": { - "filter": "{urban} and highway~trunk|trunk_link|primary|primary_link|secondary|secondary_link" - }, - "Argentina: Calle": { - "filter": "{urban} and highway~tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|service|track" - }, - "Argentina: Semiautopista": { - "filter": "highway~trunk|trunk_link and {motorroad}" - }, - "Benin: Route nationale": { - "filter": "ref~\"RN(IE)?.*\"", - "relationFilter": "type=route and route=road and name~\"RN(IE)?.*\"" - }, - "Brasil: Via arterial": { - "fuzzyFilter": "{urban} and highway~primary|primary_link and {has shoulder}" - }, - "Brasil: Via coletora": { - "fuzzyFilter": "{urban} and highway~secondary|secondary_link|tertiary|tertiary_link" - }, - "Brasil: Via de tr\u00e2nsito r\u00e1pido": { - "fuzzyFilter": "{urban} and (highway~primary|primary_link and {has shoulder} or highway~trunk|motorway)" - }, - "Brasil: Via local": { - "fuzzyFilter": "{urban} and highway~service|residential|unclassified" - }, - "California: alley": { - "filter": "highway=service and (!width or width<=7.6)" - }, - "Colorado: freeway or expressway with 2 or more lanes in each direction": { - "filter": "({motorway} or {expressway}) and {road with 2 or more lanes in each direction}" - }, - "Croatia:brza cesta": { - "filter": "highway~trunk|trunk_link and ref~B.*" - }, - "District of Columbia: alley": { - "filter": "highway=service and (!width or width<9.144)" - }, - "Ecuador: Via perimetral": { - "fuzzyFilter": "{urban} and highway~trunk|trunk_link and ref~E.*" - }, - "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0633\u0631\u064a\u0639\u0629 \u0623\u0648 \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629 \u0627\u0644\u062a\u0649 \u062a\u0631\u0628\u0637 \u0627\u0644\u0645\u062d\u0627\u0641\u0638\u0627\u062a \u060c \u0648\u0627\u0644\u062a\u0649 \u062a\u062a\u0628\u0639 \u0627\u0644\u0645\u062d\u0644\u064a\u0627\u062a \u0623\u0648 \u0627\u0644\u0647\u064a\u0626\u0629 \u0627\u0644\u0639\u0627\u0645\u0629 \u0644\u0644\u0637\u0631\u0642 \u0648\u0627\u0644\u0643\u0628\u0627\u0631\u0649\"": { - "fuzzyFilter": "{rural} and highway~primary|primary_link|secondary|secondary_link" - }, - "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0635\u062d\u0631\u0627\u0648\u064a\u0629\"": { - "fuzzyFilter": "{rural} and highway~trunk|trunk_link|motorway|motorway_link" - }, - "Flanders:Jaagpad": { - "filter": "designation=towpath" - }, - "Guatemala: Arteria principal": { - "filter": "{urban} and ((!{oneway} and lanes>=3 and width>=10.5) or ({oneway} and lanes>=2 and width>=7))", - "fuzzyFilter": "{urban} and highway~primary|primary_link" - }, - "Guatemala: Arteria secundaria": { - "filter": "{urban} and ((!{oneway} and lanes>=3 and width>=9) or ({oneway} and lanes>=2 and width>=6))", - "fuzzyFilter": "{urban} and highway~secondary|secondary_link" - }, - "Guatemala: Camino": { - "filter": "{unpaved road}" - }, - "Guatemala: Carretera principal": { - "filter": "{rural} and lanes>=2 and width>=7 and {has shoulder}", - "fuzzyFilter": "highway~primary|primary_link" - }, - "Guatemala: Carretera secundaria": { - "filter": "{rural} and lanes>=2 and width>=5.5", - "fuzzyFilter": "highway~secondary|secondary_link|tertiary|tertiary_link|unclassified" - }, - "Guatemala: V\u00eda local": { - "filter": "{urban} and width>=5", - "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|unclassified" - }, - "Guatemala: V\u00eda residencial": { - "filter": "{urban} and width>=3 and width<5.5", - "fuzzyFilter": "{urban} and highway~service|residential" - }, - "Guatemala: V\u00eda r\u00e1pida": { - "filter": "{rural} and {has shoulder} and (({oneway} and lanes>=2 and width>=7) or (!{oneway} and lanes>=4 and width>=14))", - "fuzzyFilter": "highway~trunk|trunk_link" - }, - "Guatemala: V\u00eda r\u00e1pida urbana": { - "filter": "{urban} and {has shoulder} and (({oneway} and lanes>=2 and width>=7) or (!{oneway} and lanes>=4 and width>=14))", - "fuzzyFilter": "{urban} and highway~trunk|trunk_link" - }, - "Guyana: Timehri Field / Linden Highway": { - "fuzzyFilter": "{rural} and highway~primary|primary_link" - }, - "Idaho: State highway": { - "filter": "ref~\"(ID|SH|US).*\"", - "relationFilter": "type=route and route=road and network~\"US:(ID|US)(:.*)?\"" - }, - "Iowa: suburban district": { - "filter": "{urban} and !{school zone} and !{residential district} and !{business district}" - }, - "Ireland: National road": { - "filter": "ref~N.* or (highway~trunk|trunk_link|primary|primary_link and (!ref or ref~N.*))" - }, - "Italy: Autostrada": { - "filter": "{motorway}" - }, - "Italy: Strada extraurbana local": { - "filter": "{rural}" - }, - "Italy: Strada extraurbana principale": { - "filter": "{rural} and highway~trunk|trunk_link and {dual carriageway} and lanes>=2 and {has shoulder}" - }, - "Italy: Strada extraurbana secondaria": { - "filter": "{rural} and {single carriageway} and lanes>=2" - }, - "Kansas: state highway": { - "filter": "ref~\"(KS|US).*\"", - "relationFilter": "type=route and route=road and network~\"US:(KS|US)(:.*)?\"" - }, - "Malawi: rural highway": { - "filter": "{rural} and surface~asphalt|concrete and width>5.5" - }, - "Mauritius: A road": { - "filter": "ref~A.*", - "fuzzyFilter": "highway~primary|primary_link" - }, - "Mauritius: B road": { - "filter": "ref~B.*", - "fuzzyFilter": "highway~secondary|secondary_link" - }, - "Mauritius: C road": { - "filter": "ref~C.*", - "fuzzyFilter": "highway~tertiary|tertiary_link" - }, - "Mauritius: M road": { - "filter": "ref~M.*" - }, - "Nebraska: State expressway or super-two highway": { - "filter": "{expressway} and {Nebraska: State highway}" - }, - "Nebraska: State highway": { - "filter": "ref~\"(NE|US).*\"", - "relationFilter": "type=route and route=road and network=US:NE" - }, - "Newfoundland and Labrador: Trans-Canada highway": { - "filter": "ref=1" - }, - "Nicaragua: Carretera": { - "fuzzyFilter": "{rural} and (name~Carretera.* or highway~trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link)" - }, - "Niger: autoroute": { - "filter": "highway~motorway|motorway_link" - }, - "Niger: national road": { - "filter": "ref~N.*" - }, - "Ohio: Urban state route": { - "filter": "ref~SR.* and {urban}", - "relationFilter": "type=route and route=road and network=US:OH" - }, - "Palestine: \u0634\u0627\u0631\u0639\u0645\u062e\u062a\u0644": { - "fuzzyFilter": "{urban} and (highway~living_street|service|pedestrian or highway=residential and {has no sidewalk})" - }, - "Panama: Avenida": { - "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and (!lanes or ((!{oneway} and lanes<4) or ({oneway} and lanes<2)))" - }, - "Panama: Avenida de dos carriles": { - "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and ((!{oneway} and lanes=4) or ({oneway} and lanes=2))" - }, - "Panama: Carretera multicarril en zona urbana": { - "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and ((!{oneway} and lanes>4) or ({oneway} and lanes>2))" - }, - "Per\u00fa: Avenida": { - "filter": "name~Avenida.*" - }, - "Per\u00fa: Carretera": { - "filter": "ref~PE.*", - "relationFilter": "type=route and route=road and ref~PE.*" - }, - "Per\u00fa: V\u00eda Expresa": { - "filter": "name~\"V\u00eda Expresa.*\"" - }, - "Philippines: Barangay road": { - "filter": "designation=barangay_road or designation=barangay", - "fuzzyFilter": "{rural} and highway~unclassified|track" - }, - "Philippines: National primary road": { - "filter": "designation=national_primary_road or ref~[0-9][0-9]?" - }, - "Philippines: National secondary road": { - "filter": "designation=national_secondary_road or ref~[0-9][0-9][0-9]" - }, - "Philippines: National tertiary road": { - "filter": "designation=national_tertiary_road" - }, - "Philippines: Provincial road": { - "filter": "designation=provincial_road" - }, - "Philippines: Through street": { - "filter": "{urban} and (ref or designation~provincial_road|national_tertiary_road|national_secondary_road|national_primary_road)" - }, - "Romania: drumurile expres sau pe cele na\u021bionale europene": { - "filter": "ref~DN.*", - "relationFilter": "type=route and route=road and network=RO:DN" - }, - "Senegal: National road": { - "filter": "ref~N.*" - }, - "Singapore: silver zone": { - "filter": "hazard=elderly or silver_zone=yes or hazard=silver_zone" - }, - "Spain: Traves\u00eda": { - "fuzzyFilter": "{urban} and ref" - }, - "Tennessee: Rural state road with 2 or more lanes in each direction": { - "filter": "{Tennessee: State route} and {rural} and {road with 2 or more lanes in each direction}" - }, - "Tennessee: State route": { - "filter": "ref~\"(TN|SR|US).*\"", - "relationFilter": "type=route and route=road and network~\"US:(TN|US)(:.*)?\"" - }, - "Thailand: motorway that is not a \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29": { - "filter": "{motorway} and ref" - }, - "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e02\u0e19\u0e4d\u0e32\u0e19": { - "filter": "{frontage road}" - }, - "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 not on bridge or in tunnel": { - "filter": "{motorway} and !ref and (!bridge or bridge=no) and (!tunnel or tunnel=no)" - }, - "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 on bridge or in tunnel": { - "filter": "{motorway} and !ref and ((bridge and bridge!=no) or (tunnel and tunnel!=no))" - }, - "US interstate highway": { - "filter": "ref~I.* and {motorway}", - "relationFilter": "type=route and route=road and network=US:I" - }, - "US interstate highway with 2 or more lanes in each direction": { - "filter": "{US interstate highway} and ((lanes>=2 and {oneway}) or (lanes>=4 and !{oneway}))" - }, - "United Kingdom: restricted road": { - "filter": "lit=yes or maxspeed:type=GB:nsl_restricted" - }, - "Virginia: Rustic road": { - "filter": "ref~R.*" - }, - "Virginia: State primary highway": { - "filter": "ref~\"(US|VA).*\"", - "relationFilter": "type=route and route=road and network~\"US:(VA|US)(:.*)?\"" - }, - "Washington: State highway": { - "filter": "ref~\"(US|WA).*\"", - "relationFilter": "type=route and route=road and network~\"US:(WA|US)(:.*)?\"" - }, - "Wisconsin: Rustic road": { - "filter": "ref~R.*" - }, - "Wyoming: State highway": { - "filter": "ref~WYO?.*", - "relationFilter": "type=route and route=road and network~\"US:(WY|US)(:.*)?\"" - }, - "alley": { - "filter": "highway=service and service=alley" - }, - "business district": { - "filter": "abutters~retail|commercial" - }, - "cycle street": { - "filter": "bicycle_road=yes or cyclestreet=yes" - }, - "dual carriageway": { - "filter": "dual_carriageway=yes or maxspeed:type~\".*nsl_dual\"", - "fuzzyFilter": "oneway~yes|-1 and junction!~roundabout|circular and dual_carriageway!=no" - }, - "dual carriageway in residential district": { - "filter": "{dual carriageway} and {residential district}" - }, - "dual carriageway with 2 or more lanes in each direction": { - "filter": "{dual carriageway} and lanes>=2" - }, - "expressway": { - "filter": "expressway=yes" - }, - "frontage road": { - "filter": "frontage_road=yes or side_road=yes" - }, - "has no sidewalk": { - "filter": "sidewalk~no|none or sidewalk:both~no|none or (sidewalk:left~no|none and sidewalk:right~no|none)" - }, - "has shoulder": { - "filter": "shoulder~yes|both|left|right or shoulder:left=yes or shoulder:right=yes or shoulder:both=yes" - }, - "has sidewalk": { - "filter": "sidewalk~yes|both|left|right|separate or sidewalk:left~yes|separate or sidewalk:right~yes|separate or sidewalk:both~yes|separate" - }, - "lettered road with 2 lanes": { - "filter": "ref~[A-Z] and lanes=2" - }, - "living street": { - "filter": "highway=living_street or living_street=yes" - }, - "motorroad": { - "filter": "motorroad=yes" - }, - "motorroad with 2 lanes in each direction": { - "filter": "{motorroad} and {road with 2 or more lanes in each direction}" - }, - "motorroad with dual carriageway": { - "filter": "{motorroad} and {dual carriageway}" - }, - "motorroad with single carriageway": { - "filter": "{motorroad} and {single carriageway}" - }, - "motorway": { - "filter": "highway~motorway|motorway_link" - }, - "motorway with 2 lanes in each direction": { - "filter": "{motorway} and {road with 2 or more lanes in each direction}" - }, - "numbered road": { - "filter": "ref", - "relationFilter": "type=route and route=road and ref" - }, - "oneway": { - "filter": "oneway~yes|-1 or junction~roundabout|circular" - }, - "parking lot": { - "filter": "highway=service and service=parking_aisle" - }, - "pedestrian zone": { - "filter": "highway=pedestrian" - }, - "playground zone": { - "filter": "hazard=children or playground_zone=yes or hazard=playground_zone or restriction=playground_zone or maxspeed:variable=playground_zone" - }, - "residential district": { - "filter": "abutters=residential", - "fuzzyFilter": "highway~living_street|residential" - }, - "road with 1 lane in each direction": { - "filter": "!lanes or ((!{oneway} and lanes=2) or ({oneway} and lanes=1))" - }, - "road with 2 lanes in each direction": { - "filter": "((!{oneway} and lanes=4) or ({oneway} and lanes=2))" - }, - "road with 2 or more lanes in each direction": { - "filter": "((!{oneway} and lanes>=4) or ({oneway} and lanes>=2))" - }, - "road with 3 lanes in each direction": { - "filter": "((!{oneway} and lanes=6) or ({oneway} and lanes=3))" - }, - "road with 4 or more lanes in each direction": { - "filter": "((!{oneway} and lanes>=8) or ({oneway} and lanes>=4))" - }, - "road with asphalt or concrete surface": { - "filter": "surface~asphalt|concrete or (!surface and tracktype=grade1)" - }, - "road without asphalt or concrete surface": { - "filter": "(surface and surface!~asphalt|concrete) or (!surface and tracktype and tracktype!=grade1)" - }, - "roundabout": { - "filter": "junction=roundabout" - }, - "rural": { - "filter": "source:maxspeed~\".*(rural|motorway|motorroad)\" or maxspeed:type~\".*(rural|motorway|motorroad|nsl_single|nsl_dual)\" or zone:maxspeed~\".*(rural|motorway|motorroad)\" or zone:traffic~\".*(rural|motorway|motorroad)\" or maxspeed~\".*(rural|motorway|motorroad)\" or HFCS~.*Rural.* or rural=yes", - "fuzzyFilter": "lit=no or {has no sidewalk}" - }, - "rural US interstate highway": { - "filter": "{rural} and {US interstate highway}" - }, - "rural business district": { - "filter": "{rural} and {business district}" - }, - "rural dual carriageway": { - "filter": "{rural} and {dual carriageway}" - }, - "rural dual carriageway with 2 or more lanes in each direction": { - "filter": "{rural} and {dual carriageway with 2 or more lanes in each direction}" - }, - "rural expressway": { - "filter": "{rural} and {expressway}" - }, - "rural motorroad": { - "filter": "{rural} and {motorroad}" - }, - "rural motorroad with single carriageway": { - "filter": "{rural} and {motorroad} and {single carriageway}" - }, - "rural motorway": { - "filter": "{rural} and {motorway}" - }, - "rural numbered road": { - "filter": "{rural} and {numbered road}" - }, - "rural one-way road with 1 lane": { - "filter": "{rural} and {oneway} and lanes=1" - }, - "rural playground zone": { - "filter": "{rural} and {playground zone}" - }, - "rural residential district": { - "filter": "{rural} and {residential district}" - }, - "rural road with 1 lane in each direction": { - "filter": "{rural} and {road with 1 lane in each direction}" - }, - "rural road with 2 lanes in each direction": { - "filter": "{rural} and {road with 2 lanes in each direction}" - }, - "rural road with 2 or more lanes in each direction": { - "filter": "{rural} and {road with 2 or more lanes in each direction}" - }, - "rural road with 3 lanes in each direction": { - "filter": "{rural} and {road with 3 lanes in each direction}" - }, - "rural road with 4 or more lanes in each direction": { - "filter": "{rural} and {road with 4 or more lanes in each direction}" - }, - "rural road with asphalt or concrete surface": { - "filter": "{rural} and {road with asphalt or concrete surface}" - }, - "rural road without asphalt or concrete surface": { - "filter": "{rural} and {road without asphalt or concrete surface}" - }, - "rural school zone": { - "filter": "{rural} and {school zone}" - }, - "rural single carriageway": { - "filter": "{rural} and !{oneway}" - }, - "rural single carriageway with 2 or more lanes in each direction": { - "filter": "{rural} and {single carriageway} and lanes>=4" - }, - "rural unpaved road": { - "filter": "{rural} and {unpaved road}" - }, - "school zone": { - "filter": "school_zone=yes or hazard=school_zone or restriction=school_zone or maxspeed:variable=school_zone" - }, - "service road": { - "filter": "highway=service" - }, - "single carriageway": { - "filter": "dual_carriageway=no or maxspeed:type~\".*nsl_single\"", - "fuzzyFilter": "oneway!~yes|-1 or junction~roundabout|circular or dual_carriageway=no" - }, - "single carriageway in residential district": { - "filter": "{single carriageway} and {residential district}" - }, - "trunk": { - "filter": "highway~trunk|trunk_link" - }, - "unpaved road": { - "filter": "surface~unpaved|ground|gravel|dirt|grass|compacted|sand|fine_gravel|earth|pebblestone|mud|clay|soil|rock or (!surface and tracktype and tracktype!=grade1)" - }, - "urban": { - "filter": "source:maxspeed~.*urban or maxspeed:type~.*urban or zone:maxspeed~.*urban or zone:traffic~.*urban or maxspeed~.*urban or HFCS~.*Urban.* or rural=no", - "fuzzyFilter": "highway~living_street|residential or lit=yes or {has sidewalk}" - }, - "urban US interstate highway": { - "filter": "{urban} and {US interstate highway}" - }, - "urban and without centerline": { - "filter": "{urban} and {without centerline}" - }, - "urban business district": { - "filter": "{urban} and {business district}" - }, - "urban dual carriageway": { - "filter": "{urban} and {dual carriageway}" - }, - "urban dual carriageway with 2 or more lanes in each direction": { - "filter": "{urban} and {dual carriageway with 2 or more lanes in each direction}" - }, - "urban expressway": { - "filter": "{urban} and {expressway}" - }, - "urban motorroad": { - "filter": "{urban} and {motorroad}" - }, - "urban motorway": { - "filter": "{urban} and {motorway}" - }, - "urban motorway with paved shoulders": { - "filter": "{urban} and {motorway} and {has shoulder}" - }, - "urban one-way road with 1 lane": { - "filter": "{urban} and {oneway} and lanes<2" - }, - "urban playground zone": { - "filter": "{urban} and {playground zone}" - }, - "urban residential district": { - "filter": "{urban} and {residential district}" - }, - "urban road which is not numbered": { - "filter": "{urban} and !{numbered road}" - }, - "urban road with 2 lanes in each direction": { - "filter": "{urban} and {road with 2 lanes in each direction}" - }, - "urban road with 2 or more lanes in each direction": { - "filter": "{urban} and {road with 2 or more lanes in each direction}" - }, - "urban road with 3 lanes in each direction": { - "filter": "{urban} and {road with 3 lanes in each direction}" - }, - "urban road with 4 or more lanes in each direction": { - "filter": "{urban} and {road with 4 or more lanes in each direction}" - }, - "urban road without sidewalk": { - "filter": "{urban} and {has no sidewalk}" - }, - "urban school zone": { - "filter": "{urban} and {school zone}" - }, - "urban single carriageway": { - "filter": "{urban} and {single carriageway}" - }, - "urban unpaved road": { - "filter": "{urban} and {unpaved road}" - }, - "without centerline": { - "filter": "lanes<2 or lane_markings=no" - } - }, - "speedLimitsByCountryCode": { - "AD": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Andorra: Carretera secund\u00e0ria", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Andorra: Carretera general", - "tags": { - "maxspeed": "90" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "120" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120" - } - } - ], - "AE": [ - { - "name": "service road", - "tags": { - "maxspeed": "25" - } - }, - { - "name": "urban single carriageway", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban dual carriageway", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "80", - "maxspeed:hgv": "80", - "maxspeed:school_bus": "80" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:hgv": "80", - "maxspeed:school_bus": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:hgv": "80", - "maxspeed:school_bus": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "80", - "maxspeed:hgv": "80", - "maxspeed:school_bus": "80", - "minspeed": "60" - } - } - ], - "AF": [ - { - "tags": { - "maxspeed": "90" - } - } - ], - "AG": [ - { - "name": "urban", - "tags": { - "maxspeed": "20 mph", - "maxspeed:bus": "15 mph", - "maxspeed:goods": "15 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "25 mph", - "maxspeed:goods": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "25 mph", - "maxspeed:goods": "25 mph" - } - } - ], - "AL": [ - { - "name": "urban", - "tags": { - "maxspeed": "40", - "maxspeed:bus:conditional": "35 @ (weightrating>8)", - "maxspeed:hazmat": "30", - "maxspeed:hgv:conditional": "35 @ (trailer); 35 @ (articulated)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "70 @ (weightrating>8)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "70 @ (weightrating>8)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "Albania: Rrug\u00eb interurbane kryesore", - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "70 @ (weightrating>8)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "90 @ (weightrating>8)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated); 80 @ (weightrating>12)", - "maxspeed:truck_bus:conditional": "80 @ (weightrating>5)" - } - } - ], - "AM": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:coach": "90", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "60" - } - } - ], - "AO": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:bus:conditional": "40 @ (trailer)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "60", - "maxspeed:goods:conditional": "50 @ (trailer)", - "maxspeed:hgv": "50", - "maxspeed:hgv:conditional": "50 @ (articulated); 40 @ (trailer)", - "maxspeed:motorcycle": "60", - "maxspeed:motorcycle:conditional": "50 @ (trailer)", - "maxspeed:tricycle": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (articulated); 70 @ (trailer)", - "maxspeed:motorcycle": "90", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (articulated); 70 @ (trailer)", - "maxspeed:motorcycle": "90", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)", - "maxspeed:motorcycle": "100", - "maxspeed:motorcycle:conditional": "80 @ (trailer)", - "maxspeed:tricycle": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "110", - "maxspeed:goods:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "90 @ (articulated); 80 @ (trailer)", - "maxspeed:motorcycle": "120", - "maxspeed:motorcycle:conditional": "100 @ (trailer)", - "maxspeed:tricycle": "100", - "minspeed": "40" - } - } - ], - "AR": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "Argentina: Calle", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "Argentina: Avenida", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "110", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80", - "maxspeed:motorcycle": "110", - "maxspeed:motorhome": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "110", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80", - "maxspeed:motorcycle": "110", - "maxspeed:motorhome": "90" - } - }, - { - "name": "Argentina: Semiautopista", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "110", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80", - "maxspeed:motorcycle": "120", - "maxspeed:motorhome": "90", - "minspeed": "40" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "110", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80", - "maxspeed:motorcycle": "130", - "maxspeed:motorhome": "100", - "minspeed": "65" - } - } - ], - "AS": [ - { - "name": "urban", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "25 mph" - } - } - ], - "AT": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", - "maxspeed:hgv": "70" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", - "maxspeed:hgv": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", - "maxspeed:hgv": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (articulated)", - "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (articulated)", - "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - } - ], - "AU": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100" - } - } - ], - "AU-ACT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - } - ], - "AU-NSW": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "100 @ (weightrating>4.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "100 @ (weightrating>4.5)" - } - } - ], - "AU-NT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - } - ], - "AU-QLD": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (trailer); 90 @ (articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (trailer); 90 @ (articulated)" - } - } - ], - "AU-SA": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - } - ], - "AU-TAS": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "80" - } - } - ], - "AU-VIC": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)", - "maxspeed:hgv:conditional": "100 @ (articulated AND weightrating>22)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12)", - "maxspeed:hgv:conditional": "100 @ (articulated AND weightrating>22)" - } - } - ], - "AU-WA": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12); 100 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "100 @ (weightrating>5)", - "maxspeed:conditional": "100 @ (weightrating>12); 100 @ (trailer)" - } - } - ], - "AW": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - } - ], - "AZ": [ - { - "name": "service road", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:truck_bus": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:goods": "90", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:goods": "90", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:coach": "90", - "maxspeed:goods": "110", - "maxspeed:hgv": "90", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "50", - "minspeed": "50" - } - } - ], - "BA": [ - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70", - "minspeed": "40" - } - } - ], - "BB": [ - { - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:coach": "50", - "maxspeed:conditional": "50 @ (weightrating>3); 30 @ (trailer)", - "maxspeed:hgv": "50", - "maxspeed:minibus": "50" - } - } - ], - "BE-BRU": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30" - } - }, - { - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural single carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - } - ], - "BE-VLG": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural single carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - }, - { - "name": "Flanders:Jaagpad", - "tags": { - "maxspeed": "30" - } - } - ], - "BE-WAL": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural single carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5)" - } - } - ], - "BF": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>10)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>10)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - } - ], - "BG": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "50", - "maxspeed:hazmat": "40", - "maxspeed:hgv": "50", - "maxspeed:motorcycle": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated)", - "maxspeed:motorcycle": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated)", - "maxspeed:motorcycle": "80" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hazmat": "90", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "90", - "minspeed": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "140", - "maxspeed:bus": "100", - "maxspeed:conditional": "100 @ (weightrating>3.5); 100 @ (trailer)", - "maxspeed:hazmat": "90", - "maxspeed:hgv": "100", - "maxspeed:motorcycle": "100", - "minspeed": "50" - } - } - ], - "BH": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:goods": "50", - "maxspeed:motorcycle": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:goods": "80", - "maxspeed:motorcycle": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:goods": "80", - "maxspeed:motorcycle": "80" - } - } - ], - "BI": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "40" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:conditional": "50 @ (weightrating>10)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:conditional": "50 @ (weightrating>10)" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "70", - "maxspeed:conditional": "90 @ (weightrating>10)" - } - } - ], - "BJ": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat:conditional": "50 @ (weightrating>12)" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (weightrating>10)" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "90", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (weightrating>10)" - } - }, - { - "name": "Benin: Route nationale", - "tags": { - "maxspeed": "90", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (weightrating>10)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv:conditional": "90 @ (weightrating>10)" - } - } - ], - "BN": [ - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "75 @ (trailer)", - "maxspeed:conditional": "75 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "75 @ (trailer)", - "maxspeed:conditional": "75 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" - } - } - ], - "BO": [ - { - "name": "school zone", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "70" - } - } - ], - "BR": [ - { - "name": "Brasil: Via local", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "Brasil: Via coletora", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "Brasil: Via arterial", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Brasil: Via de tr\u00e2nsito r\u00e1pido", - "tags": { - "maxspeed": "80" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:goods": "100", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "100", - "maxspeed:motorhome": "90" - } - }, - { - "name": "rural single carriageway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:goods": "100", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "100", - "maxspeed:motorhome": "90" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:goods": "110", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "110", - "maxspeed:motorhome": "90" - } - } - ], - "BS": [ - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "30 mph" - } - } - ], - "BT": [ - { - "name": "urban", - "tags": { - "maxspeed": "30" - } - }, - { - "tags": { - "maxspeed": "50", - "maxspeed:bus": "35", - "maxspeed:goods": "50", - "maxspeed:goods:conditional": "35 @ (weightrating>3)", - "maxspeed:motorcycle": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "35", - "maxspeed:goods": "50", - "maxspeed:goods:conditional": "35 @ (weightrating>3)", - "maxspeed:motorcycle": "50" - } - } - ], - "BW": [ - { - "name": "living street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80", - "maxspeed:motorcycle:conditional": "60 @ (trailer)", - "maxspeed:school_bus": "80" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80", - "maxspeed:motorcycle:conditional": "60 @ (trailer)", - "maxspeed:school_bus": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80", - "maxspeed:motorcycle:conditional": "60 @ (trailer)", - "maxspeed:school_bus": "80" - } - } - ], - "BY": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - } - ], - "CA-AB": [ - { - "name": "playground zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "Alberta: Urban road", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "Alberta: Urban provincial highway", - "tags": { - "maxspeed": "80" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "Alberta: Provincial highway", - "tags": { - "maxspeed": "100" - } - } - ], - "CA-BC": [ - { - "name": "playground zone", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "30 @ (08:00-17:00)" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "30 @ (08:00-17:00)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - } - ], - "CA-MB": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90" - } - } - ], - "CA-NB": [ - { - "name": "urban school zone", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "30 @ (07:30-16:00)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "50 @ (07:30-16:00)" - } - } - ], - "CA-NL": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "50 @ (07:00-17:00)" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Newfoundland and Labrador: Trans-Canada highway", - "tags": { - "maxspeed": "100" - } - } - ], - "CA-NS": [ - { - "name": "urban school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "urban public park", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "50" - } - } - ], - "CA-NT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90" - } - } - ], - "CA-NU": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90" - } - } - ], - "CA-ON": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - } - ], - "CA-PE": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "90 @ (sunset-sunrise)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "90 @ (sunset-sunrise)" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "60 @ (Sep-Jun Mo-Fr 08:00-16:00)" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "80" - } - } - ], - "CA-QC": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "50 @ (Sep-Jun Mo-Fr 08:00-16:00)" - } - }, - { - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "100", - "minspeed": "60" - } - } - ], - "CA-SK": [ - { - "tags": { - "maxspeed": "80" - } - } - ], - "CA-YT": [ - { - "name": "urban playground zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "rural playground zone", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban school zone", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "30 @ (Mo-Fr 08:00-16:30)" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "40 @ (Mo-Fr 08:00-16:30)" - } - }, - { - "tags": { - "maxspeed": "50" - } - } - ], - "CD": [ - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "75" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "75" - } - }, - { - "name": "dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>7.5)", - "minspeed": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>7.5)", - "minspeed": "60" - } - } - ], - "CF": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - } - ], - "CG": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - } - ], - "CH": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)", - "maxspeed:motorhome": "100", - "minspeed": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)", - "maxspeed:motorhome": "100", - "minspeed": "80" - } - } - ], - "CI": [ - { - "name": "urban", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "50", - "maxspeed:hgv": "50" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>16)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>16)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>16)" - } - } - ], - "CL": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:hgv": "100", - "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", - "maxspeed:school_bus": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:hgv": "100", - "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", - "maxspeed:school_bus": "90" - } - }, - { - "name": "rural road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:coach": "100", - "maxspeed:hgv": "100", - "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", - "maxspeed:school_bus": "90" - } - } - ], - "CM": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12); 60 @ (trailerweight>0.75)" - } - } - ], - "CN": [ - { - "name": "urban and without centerline", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "without centerline", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:goods": "100", - "maxspeed:motorcycle": "80", - "maxspeed:motorhome": "100", - "minspeed": "60" - } - } - ], - "CO": [ - { - "name": "residential district", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:goods": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:goods": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "80", - "maxspeed:goods": "80" - } - } - ], - "CR": [ - { - "name": "roundabout", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - } - ], - "CU": [ - { - "name": "service road", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban school zone", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "60", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "60", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "80 @ (trailer)" - } - } - ], - "CV": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hgv": "50", - "maxspeed:hgv:conditional": "40 @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (weightrating>3.5 AND trailer); 80 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:conditional": "100 @ (weightrating>3.5); 100 @ (trailer); 90 @ (weightrating>3.5 AND trailer)", - "maxspeed:goods": "110", - "maxspeed:goods:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (trailer)", - "maxspeed:tricycle": "100", - "minspeed": "60" - } - } - ], - "CW": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "80", - "minspeed": "40" - } - } - ], - "CY": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:hazmat": "40" - } - } - ], - "CZ": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban motorroad", - "tags": { - "maxspeed": "80", - "minspeed": "65" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "80", - "minspeed": "65" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "name": "rural motorroad with single carriageway", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "minspeed": "80" - } - }, - { - "name": "rural motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "110", - "maxspeed:conditional": "80 @ (weightrating>3.5)", - "minspeed": "80" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "130", - "maxspeed:conditional": "80 @ (weightrating>3.5)", - "minspeed": "80" - } - } - ], - "DE": [ - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", - "maxspeed:motorhome": "80", - "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", - "maxspeed:motorhome": "80", - "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed:advisory": "130", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", - "maxspeed:motorhome": "80", - "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" - } - }, - { - "name": "rural road with 2 or more lanes in each direction", - "tags": { - "maxspeed:advisory": "130", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", - "maxspeed:motorhome": "80", - "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed:advisory": "130", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "60 @ (trailer)", - "maxspeed:coach": "100", - "maxspeed:coach:conditional": "60 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "60 @ (trailers>=2)", - "maxspeed:motorcycle:advisory": "130", - "maxspeed:motorcycle:conditional": "60 @ (trailer)", - "maxspeed:motorhome:advisory": "130", - "maxspeed:motorhome:conditional": "80 @ (trailer)", - "minspeed": "60" - } - } - ], - "DK": [ - { - "name": "living street", - "tags": { - "maxspeed": "15" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "130", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:hgv": "80", - "minspeed": "50" - } - } - ], - "DO": [ - { - "name": "school zone", - "tags": { - "maxspeed": "35", - "maxspeed:conditional": "25 @ (06:00-18:00)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35" - } - }, - { - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:conditional": "50 @ (weightcapacity>2); 50 @ (emptyweight>2)", - "maxspeed:hazmat": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:conditional": "50 @ (weightcapacity>2); 50 @ (emptyweight>2)", - "maxspeed:hazmat": "50" - } - } - ], - "EC": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "40" - } - }, - { - "name": "Ecuador: Via perimetral", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "50 @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "50 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:hgv": "70", - "maxspeed:hgv:conditional": "50 @ (trailer)" - } - } - ], - "EE": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:truck_bus": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:truck_bus": "60" - } - } - ], - "EG": [ - { - "name": "residential district", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "60 @ (articulated); 60 @ (trailer)", - "maxspeed:goods": "70" - } - }, - { - "name": "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0633\u0631\u064a\u0639\u0629 \u0623\u0648 \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629 \u0627\u0644\u062a\u0649 \u062a\u0631\u0628\u0637 \u0627\u0644\u0645\u062d\u0627\u0641\u0638\u0627\u062a \u060c \u0648\u0627\u0644\u062a\u0649 \u062a\u062a\u0628\u0639 \u0627\u0644\u0645\u062d\u0644\u064a\u0627\u062a \u0623\u0648 \u0627\u0644\u0647\u064a\u0626\u0629 \u0627\u0644\u0639\u0627\u0645\u0629 \u0644\u0644\u0637\u0631\u0642 \u0648\u0627\u0644\u0643\u0628\u0627\u0631\u0649\"", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "60 @ (articulated); 60 @ (trailer)", - "maxspeed:goods": "70" - } - }, - { - "name": "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0635\u062d\u0631\u0627\u0648\u064a\u0629\"", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:conditional": "70 @ (articulated); 70 @ (trailer)", - "maxspeed:goods": "80" - } - } - ], - "ES": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban road without sidewalk", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "unpaved road", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "80", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70", - "minspeed": "60" - } - }, - { - "name": "urban road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat": "40" - } - }, - { - "name": "Spain: Traves\u00eda", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat": "40" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30", - "maxspeed:tricycle": "70" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (trailer); 80 @ (articulated)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (trailer); 80 @ (articulated)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80", - "maxspeed:tricycle": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "80", - "maxspeed:conditional": "90 @ (trailer); 90 @ (articulated)", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "90", - "maxspeed:minibus": "100", - "maxspeed:motorhome:conditional": "90 @ (weightrating>3.5)", - "maxspeed:school_bus": "90", - "maxspeed:tricycle": "70", - "minspeed": "60" - } - } - ], - "FI": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome": "100" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome": "100" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome": "100" - } - } - ], - "FJ": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", - "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", - "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", - "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" - } - } - ], - "FM": [ - { - "tags": { - "maxspeed": "25 mph" - } - } - ], - "FM-KSA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "25 mph" - } - } - ], - "FM-PNI": [ - { - "tags": { - "maxspeed": "25 mph" - } - } - ], - "FM-TRK": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph", - "maxspeed:conditional": "15 mph @ (Mo-Fr 08:00-15:30)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "25 mph" - } - } - ], - "FM-YAP": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph", - "maxspeed:conditional": "15 mph @ (Mo-Fr 08:00-15:30)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "25 mph" - } - } - ], - "FR": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hazmat": "70", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv": "70" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "rural road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "100 @ (weightrating>3.5); 90 @ (weightrating>10)", - "maxspeed:conditional": "100 @ (wet); 90 @ (weightrating>3.5); 80 @ (weightrating>12)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "110 @ (weightrating>3.5); 90 @ (weightrating>10)", - "maxspeed:coach": "100", - "maxspeed:conditional": "110 @ (wet); 90 @ (weightrating>3.5)", - "maxspeed:hazmat:conditional": "80 @ (weightrating>12)", - "maxspeed:hgv": "90" - } - } - ], - "GA": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - } - ], - "GB": [ - { - "name": "United Kingdom: restricted road", - "tags": { - "maxspeed": "30 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "name": "dual carriageway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "60 mph", - "maxspeed:conditional": "60 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "60 mph", - "maxspeed:motorhome": "70 mph", - "maxspeed:motorhome:conditional": "60 mph @ (emptyweight>3.05)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "70 mph", - "maxspeed:bus:conditional": "60 mph @ (length>12)", - "maxspeed:conditional": "60 mph @ (trailer); 40 mph @ (trailers>=2)", - "maxspeed:hgv": "70 mph", - "maxspeed:hgv:conditional": "60 mph @ (weightrating>7.5); 60 mph @ (articulated)", - "maxspeed:motorhome": "70 mph" - } - } - ], - "GB-SCT": [ - { - "name": "United Kingdom: restricted road", - "tags": { - "maxspeed": "30 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "50 mph", - "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" - } - }, - { - "name": "dual carriageway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "60 mph", - "maxspeed:conditional": "60 mph @ (trailer); 20 mph @ (trailers>=2)", - "maxspeed:hgv": "60 mph", - "maxspeed:hgv:conditional": "50 mph @ (weightrating>7.5)", - "maxspeed:motorhome": "70 mph", - "maxspeed:motorhome:conditional": "60 mph @ (emptyweight>3.05)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "70 mph", - "maxspeed:bus:conditional": "60 mph @ (length>12)", - "maxspeed:conditional": "60 mph @ (trailer); 40 mph @ (trailers>=2)", - "maxspeed:hgv": "70 mph", - "maxspeed:hgv:conditional": "60 mph @ (weightrating>7.5); 60 mph @ (articulated)", - "maxspeed:motorhome": "70 mph" - } - } - ], - "GD": [ - { - "name": "urban", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "35 mph", - "maxspeed:goods": "35 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "35 mph", - "maxspeed:goods": "35 mph" - } - } - ], - "GE": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75); 80 @ (weightrating>3.5 AND trailerweight>0.75)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:truck_bus": "60" - } - } - ], - "GG": [ - { - "tags": { - "maxspeed": "35 mph", - "maxspeed:conditional": "25 mph @ (articulated); 25 mph @ (emptyweight>2); 20 mph @ (trailer)", - "maxspeed:motorhome": "20 mph" - } - } - ], - "GH": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:hgv": "75", - "maxspeed:hgv:conditional": "80 @ (empty)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:hgv": "75", - "maxspeed:hgv:conditional": "80 @ (empty)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:hgv": "75", - "maxspeed:hgv:conditional": "80 @ (empty)" - } - } - ], - "GQ": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - } - ], - "GR": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "85", - "maxspeed:hgv:conditional": "80 @ (articulated); 80 @ (trailer)" - } - } - ], - "GT": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30", - "maxspeed:conditional": "20 @ (trailer); 20 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: V\u00eda residencial", - "tags": { - "maxspeed": "30", - "maxspeed:conditional": "20 @ (trailer); 20 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: V\u00eda local", - "tags": { - "maxspeed": "40", - "maxspeed:conditional": "30 @ (trailer); 30 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: Camino", - "tags": { - "maxspeed": "40", - "maxspeed:conditional": "30 @ (trailer); 30 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: Arteria secundaria", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "40 @ (trailer); 40 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: Arteria principal", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: V\u00eda r\u00e1pida urbana", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "70 @ (trailer); 70 @ (weightrating>3.5)", - "minspeed": "60" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "minspeed": "60" - } - }, - { - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: Carretera secundaria", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: Carretera principal", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "60 @ (trailer); 60 @ (weightrating>3.5)" - } - }, - { - "name": "Guatemala: V\u00eda r\u00e1pida", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "70 @ (trailer); 70 @ (weightrating>3.5)", - "minspeed": "60" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "minspeed": "60" - } - } - ], - "GU": [ - { - "tags": { - "maxspeed": "45 mph" - } - } - ], - "GY": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "40 mph", - "maxspeed:hgv": "40 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "40 mph", - "maxspeed:hgv": "40 mph" - } - }, - { - "name": "Guyana: Timehri Field / Linden Highway", - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "40 mph", - "maxspeed:conditional": "40 mph @ (emptyweight>1.270)", - "maxspeed:hgv": "40 mph" - } - } - ], - "HK": [ - { - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (seats>=19)", - "maxspeed:hgv:conditional": "70 @ (weightrating>5.5)" - } - } - ], - "HR": [ - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80" - } - }, - { - "name": "Croatia:brza cesta", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80", - "minspeed": "60" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", - "maxspeed:school_bus": "80", - "minspeed": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "90 @ (trailer); 90 @ (weightrating>3.5)", - "maxspeed:school_bus": "80", - "minspeed": "80" - } - } - ], - "HT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - } - ], - "HU": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "70", - "maxspeed:hgv": "70", - "maxspeed:tricycle": "40" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:tricycle": "40" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:tricycle": "40" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:hgv": "70", - "minspeed": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "80", - "minspeed": "60" - } - } - ], - "ID": [ - { - "tags": { - "maxspeed": "80", - "minspeed": "40" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "80", - "minspeed": "60" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "100", - "minspeed": "60" - } - } - ], - "IE": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "80" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "80" - } - }, - { - "name": "Ireland: National road", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "maxspeed:hgv": "90" - } - } - ], - "IL": [ - { - "name": "living street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "90", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>12)", - "maxspeed:minibus": "110" - } - } - ], - "IM": [ - { - "name": "living street", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:bus": "40 mph", - "maxspeed:conditional": "20 mph @ (trailers>=2); 40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", - "maxspeed:goods": "60 mph", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:minibus": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "40 mph", - "maxspeed:conditional": "40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", - "maxspeed:goods": "60 mph", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:minibus": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60 mph", - "maxspeed:bus": "40 mph", - "maxspeed:conditional": "40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", - "maxspeed:goods": "60 mph", - "maxspeed:hgv": "50 mph", - "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", - "maxspeed:minibus": "50 mph", - "maxspeed:motorhome": "60 mph", - "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" - } - } - ], - "IN": [ - { - "name": "urban", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "60", - "maxspeed:goods": "60", - "maxspeed:motorcycle": "60", - "maxspeed:tricycle": "50" - } - }, - { - "tags": { - "maxspeed": "70", - "maxspeed:bus": "60", - "maxspeed:goods": "60", - "maxspeed:motorcycle": "60", - "maxspeed:tricycle": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "60", - "maxspeed:goods": "60", - "maxspeed:motorcycle": "60", - "maxspeed:tricycle": "50" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:goods": "80", - "maxspeed:motorcycle": "80", - "maxspeed:tricycle": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:goods": "80", - "maxspeed:motorcycle": "80", - "tricycle": "no" - } - } - ], - "IS": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "parking lot", - "tags": { - "maxspeed": "15" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "90" - } - } - ], - "IT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat": "30" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "Italy: Strada extraurbana local", - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "Italy: Strada extraurbana secondaria", - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "Italy: Strada extraurbana principale", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", - "maxspeed:conditional": "90 @ (wet); 80 @ (weightrating>3.5); 70 @ (weightrating>12)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", - "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" - } - }, - { - "name": "Italy: Autostrada", - "tags": { - "maxspeed": "130", - "maxspeed:bus:conditional": "100 @ (weightrating>8); 80 @ (articulated)", - "maxspeed:conditional": "110 @ (wet); 100 @ (weightrating>3.5); 100 @ (weightrating>12)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>12); 80 @ (trailer); 80 @ (articulated)", - "maxspeed:truck_bus:conditional": "80 @ (weightrating>5)" - } - } - ], - "JE": [ - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "30 mph", - "maxspeed:conditional": "30 mph @ (trailer)", - "maxspeed:hgv": "30 mph" - } - } - ], - "JP": [ - { - "tags": { - "maxspeed": "60", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (weightrating>8)", - "maxspeed:hgv": "80", - "maxspeed:tricycle": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (weightrating>8)", - "maxspeed:hgv": "80", - "maxspeed:tricycle": "80" - } - } - ], - "KE": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "65 @ (trailer)", - "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "65 @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "65 @ (trailer)", - "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "65 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "65 @ (trailer)", - "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "65 @ (trailer)" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "65 @ (trailer)", - "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "65 @ (trailer)" - } - } - ], - "KG": [ - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:coach": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5); 70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60" - } - } - ], - "KH": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:hazmat": "40" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:tricycle": "70" - } - } - ], - "KI": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "40", - "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" - } - } - ], - "KN": [ - { - "name": "urban", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "30 mph", - "maxspeed:goods": "30 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "30 mph", - "maxspeed:goods": "30 mph" - } - } - ], - "KR": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "80", - "minspeed": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "80", - "minspeed": "50" - } - }, - { - "name": "motorroad with 2 lanes in each direction", - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "90", - "minspeed": "50" - } - }, - { - "name": "motorway with 2 lanes in each direction", - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "90", - "minspeed": "50" - } - } - ], - "KZ": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:truck_bus": "60" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "140", - "maxspeed:bus": "90", - "maxspeed:coach": "110", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:goods": "110", - "maxspeed:hgv": "90", - "maxspeed:minibus": "110", - "maxspeed:school_bus": "90", - "maxspeed:truck_bus": "60" - } - } - ], - "LI": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - } - ], - "LR": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "45 mph", - "maxspeed:motorcycle": "40 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "45 mph", - "maxspeed:motorcycle": "40 mph" - } - }, - { - "name": "rural residential district", - "tags": { - "maxspeed": "35 mph" - } - } - ], - "LS": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - } - ], - "LT": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "parking lot", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "110 @ (Nov-Mar); 90 @ (trailer); 90 @ (weightrating>3.5)", - "maxspeed:hgv": "90", - "minspeed": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "110 @ (Nov-Mar); 90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "90", - "minspeed": "60" - } - } - ], - "LU": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "75", - "maxspeed:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "75", - "maxspeed:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "75 @ (weightrating>12)", - "maxspeed:conditional": "110 @ (wet); 90 @ (weightrating>7.5); 90 @ (trailer); 90 @ (articulated); 75 @ (weightrating>12)", - "minspeed": "40" - } - } - ], - "LV": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "parking lot", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:truck_bus": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv:conditional": "80 @ (weightrating>7.5)", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv:conditional": "80 @ (weightrating>7.5)", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (Dec-Feb)", - "maxspeed:conditional": "90 @ (Dec-Feb); 90 @ (trailer)", - "maxspeed:hgv": "110", - "maxspeed:hgv:conditional": "90 @ (Dec-Feb); 90 @ (weightrating>7.5)", - "maxspeed:truck_bus": "60" - } - } - ], - "MC": [ - { - "tags": { - "maxspeed": "50" - } - } - ], - "MD": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "5" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat": "40" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:coach": "90", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "90", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:truck_bus": "60", - "minspeed": "60" - } - } - ], - "MH": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": {} - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph" - } - } - ], - "MK": [ - { - "name": "urban school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "60", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "60", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "60", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "60", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "50", - "minspeed": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:goods:conditional": "90 @ (weightrating>3.5); 80 @ (weightrating>7.5)", - "maxspeed:hazmat": "60", - "maxspeed:school_bus": "70", - "maxspeed:truck_bus": "50", - "minspeed": "60" - } - } - ], - "MM": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "20 mph @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph" - } - } - ], - "MN": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:school_bus": "50" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "60 @ (trailer)", - "maxspeed:school_bus": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "60 @ (trailer)", - "maxspeed:school_bus": "50" - } - }, - { - "name": "long road", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:school_bus": "50" - } - } - ], - "MO": [ - { - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5); 50 @ (seats>=10)", - "maxspeed:tricycle": "50" - } - } - ], - "MS": [ - { - "name": "urban", - "tags": { - "maxspeed": "20 mph", - "maxspeed:bus": "15 mph", - "maxspeed:goods": "15 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "25 mph", - "maxspeed:goods": "25 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph", - "maxspeed:bus": "25 mph", - "maxspeed:goods": "25 mph" - } - } - ], - "MT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "40", - "maxspeed:goods": "40", - "maxspeed:goods:conditional": "30 @ (weightrating>3)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:goods": "60", - "maxspeed:goods:conditional": "40 @ (weightrating>3)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:goods": "60", - "maxspeed:goods:conditional": "40 @ (weightrating>3)" - } - } - ], - "MU": [ - { - "name": "Mauritius: C road", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Mauritius: B road", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Mauritius: A road", - "tags": { - "maxspeed": "80", - "maxspeed:conditional": "60 @ (weightrating>3.5); 60 @ (trailer)", - "maxspeed:goods": "60", - "maxspeed:motorcycle": "60" - } - }, - { - "name": "Mauritius: M road", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:conditional": "70 @ (weightrating>3.5); 60 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:motorcycle": "80" - } - } - ], - "MW": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "Malawi: rural highway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - } - ], - "MX": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "95", - "maxspeed:bus:conditional": "80 @ (sunset-sunrise)", - "maxspeed:conditional": "90 @ (sunset-sunrise)", - "maxspeed:goods:conditional": "80 @ (axles>=3); 70 @ (axles>=3 AND sunset-sunrise)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "95", - "maxspeed:bus:conditional": "80 @ (sunset-sunrise)", - "maxspeed:conditional": "90 @ (sunset-sunrise)", - "maxspeed:goods:conditional": "80 @ (axles>=3); 70 @ (axles>=3 AND sunset-sunrise)" - } - } - ], - "MZ": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:motorcycle": "50" - } - }, - { - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:motorcycle": "90", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:motorcycle": "90", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70" - } - } - ], - "NA": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "100", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:goods:conditional": "80 @ (weightrating>9)" - } - } - ], - "NE": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:goods:conditional": "90 @ (weightrating>9)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "90", - "maxspeed:goods:conditional": "90 @ (weightrating>9)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - }, - { - "name": "Niger: national road", - "tags": { - "maxspeed": "90", - "maxspeed:goods:conditional": "90 @ (weightrating>9)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - }, - { - "name": "Niger: autoroute", - "tags": { - "maxspeed": "110", - "maxspeed:goods:conditional": "90 @ (weightrating>9)", - "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" - } - } - ], - "NG": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hgv": "45" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:hgv": "50", - "maxspeed:motorcycle": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:hgv": "50", - "maxspeed:motorcycle": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:hgv": "60" - } - } - ], - "NI": [ - { - "name": "urban", - "tags": { - "maxspeed": "45" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Nicaragua: Carretera", - "tags": { - "maxspeed": "100" - } - } - ], - "NL": [ - { - "name": "living street", - "tags": { - "maxspeed": "15" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "name": "rural motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "90 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", - "minspeed": "50" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "90 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "80", - "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", - "minspeed": "60" - } - } - ], - "NL-BQ1": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - } - ], - "NL-BQ2": [ - { - "name": "urban", - "tags": { - "maxspeed": "20" - } - }, - { - "tags": { - "maxspeed": "40" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40" - } - } - ], - "NL-BQ3": [ - { - "name": "urban", - "tags": { - "maxspeed": "30" - } - }, - { - "tags": { - "maxspeed": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50" - } - } - ], - "NO": [ - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", - "maxspeed:hgv": "80" - } - } - ], - "NP": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "hilly road", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "50", - "maxspeed:goods": "50", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "50" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:goods": "70", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:goods": "70", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "50" - } - } - ], - "NR": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "tags": { - "maxspeed": "50" - } - } - ], - "NZ": [ - { - "name": "living street", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:school_bus:conditional": "80 @ (weightrating>2)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:school_bus:conditional": "80 @ (weightrating>2)" - } - } - ], - "PA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "40", - "maxspeed:hazmat": "40" - } - }, - { - "name": "Panama: Avenida", - "tags": { - "maxspeed": "60", - "maxspeed:hazmat": "40" - } - }, - { - "name": "Panama: Avenida de dos carriles", - "tags": { - "maxspeed": "80", - "maxspeed:hazmat": "80", - "maxspeed:lanes": "80|50" - } - }, - { - "name": "Panama: Carretera multicarril en zona urbana", - "tags": { - "maxspeed": "80", - "maxspeed:hazmat": "80", - "maxspeed:lanes": "80|60|50" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:hazmat": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:hazmat": "80" - } - } - ], - "PE": [ - { - "name": "school zone", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Per\u00fa: Avenida", - "tags": { - "maxspeed": "60" - } - }, - { - "name": "Per\u00fa: V\u00eda Expresa", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "Per\u00fa: Carretera", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:motorhome": "90", - "maxspeed:school_bus": "70" - } - } - ], - "PG": [ - { - "name": "school zone", - "tags": { - "maxspeed": "25" - } - }, - { - "name": "playground zone", - "tags": { - "maxspeed": "25" - } - }, - { - "name": "village", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "100" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100" - } - } - ], - "PH": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "Philippines: Barangay road", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30" - } - }, - { - "tags": { - "maxspeed": "40", - "maxspeed:bus": "30", - "maxspeed:hgv": "30", - "maxspeed:tricycle": "30" - } - }, - { - "name": "Philippines: Provincial road", - "tags": { - "maxspeed": "40", - "maxspeed:bus": "30", - "maxspeed:hgv": "30", - "maxspeed:tricycle": "30" - } - }, - { - "name": "Philippines: Through street", - "tags": { - "maxspeed": "40", - "maxspeed:bus": "30", - "maxspeed:hgv": "30", - "maxspeed:tricycle": "30" - } - }, - { - "name": "Philippines: National tertiary road", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "50", - "maxspeed:hgv": "50", - "maxspeed:tricycle": "50" - } - }, - { - "name": "Philippines: National secondary road", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "50", - "maxspeed:hgv": "50", - "maxspeed:tricycle": "50" - } - }, - { - "name": "Philippines: National primary road", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "50", - "maxspeed:hgv": "50", - "maxspeed:tricycle": "50" - } - } - ], - "PK": [ - { - "name": "urban unpaved road", - "tags": { - "maxspeed": "40", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "minspeed": "50" - } - }, - { - "name": "urban road with 4 or more lanes in each direction", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "urban road with 3 lanes in each direction", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "urban road with 2 lanes in each direction", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "55", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "tags": { - "maxspeed": "70", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural road with 2 lanes in each direction", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural road with 3 lanes in each direction", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural road with 4 or more lanes in each direction", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:conditional": "50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "maxspeed:tricycle": "30" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "70", - "maxspeed:bus:conditional": "65 @ (seats>=15)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", - "maxspeed:motorcycle": "70", - "minspeed": "65" - } - } - ], - "PL": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" - } - }, - { - "name": "motorroad with single carriageway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" - } - }, - { - "name": "motorroad with dual carriageway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "140", - "maxspeed:bus": "80", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", - "minspeed": "40" - } - } - ], - "PN": [ - { - "tags": { - "maxspeed": "30 mph" - } - } - ], - "PR": [ - { - "name": "urban school zone", - "tags": { - "maxspeed:bus": "15 mph", - "maxspeed:conditional": "15 mph @ (06:00-19:00)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:bus": "15 mph", - "maxspeed:conditional": "15 mph @ (weightrating>10000 lb)", - "maxspeed:hazmat": "15 mph" - } - }, - { - "tags": { - "maxspeed": "45 mph", - "maxspeed:bus": "35 mph", - "maxspeed:conditional": "35 mph @ (weightrating>10000 lb)", - "maxspeed:hazmat": "30 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "45 mph", - "maxspeed:bus": "35 mph", - "maxspeed:conditional": "35 mph @ (weightrating>10000 lb)", - "maxspeed:hazmat": "30 mph" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed:bus": "15 mph", - "maxspeed:conditional": "25 mph @ (06:00-19:00); 15 mph @ (weightrating>10000 lb)", - "maxspeed:hazmat": "30 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:bus": "55 mph", - "maxspeed:conditional": "55 mph @ (weightrating>10000 lb)", - "maxspeed:hazmat": "30 mph" - } - } - ], - "PS": [ - { - "name": "Palestine: \u0634\u0627\u0631\u0639\u0645\u062e\u062a\u0644", - "tags": { - "maxspeed": "25" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "100 @ (seats>=12)", - "maxspeed:goods:conditional": "90 @ (weightrating>12)" - } - } - ], - "PT": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hgv:conditional": "40 @ (trailer)" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "80" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "90" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "110", - "maxspeed:goods:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (trailer)", - "maxspeed:tricycle": "100", - "minspeed": "50" - } - } - ], - "PY": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "40", - "maxspeed:hazmat": "40", - "maxspeed:hgv": "40", - "maxspeed:motorcycle": "40" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:hazmat": "90", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:hazmat": "90", - "maxspeed:hgv": "90", - "maxspeed:motorcycle": "90" - } - } - ], - "RO": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hazmat": "40" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 70 @ (weightrating>7.5); 60 @ (weightrating>7.5 AND trailer); 60 @ (weightrating>7.5 AND articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 70 @ (weightrating>7.5); 60 @ (weightrating>7.5 AND trailer); 60 @ (weightrating>7.5 AND articulated)" - } - }, - { - "name": "Romania: drumurile expres sau pe cele na\u021bionale europene", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (trailer)", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated); 80 @ (weightrating>7.5); 70 @ (weightrating>7.5 AND trailer); 70 @ (weightrating>7.5 AND articulated)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "110", - "maxspeed:bus:conditional": "100 @ (trailer)", - "maxspeed:conditional": "120 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "110", - "maxspeed:hgv:conditional": "100 @ (trailer); 100 @ (articulated); 90 @ (weightrating>7.5); 80 @ (weightrating>7.5 AND trailer); 80 @ (weightrating>7.5 AND articulated)", - "minspeed": "50" - } - } - ], - "RS": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (articulated)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (articulated)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:hgv": "100", - "maxspeed:hgv:conditional": "90 @ (weightrating>7.5); 90 @ (trailer)", - "maxspeed:school_bus": "90", - "minspeed": "50" - } - } - ], - "RU": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:school_bus": "60", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "90", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:hgv": "70", - "maxspeed:minibus": "90", - "maxspeed:school_bus": "60", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:coach": "90", - "maxspeed:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:minibus": "90", - "maxspeed:school_bus": "60", - "maxspeed:truck_bus": "60" - } - } - ], - "RW": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:goods": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:goods": "60" - } - } - ], - "SA": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:hgv": "30" - } - }, - { - "tags": { - "maxspeed": "120", - "maxspeed:hgv": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "120", - "maxspeed:hgv": "70" - } - } - ], - "SD": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - } - ], - "SE": [ - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "walk" - } - }, - { - "name": "cycle street", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus:conditional": "60 @ (articulated)", - "maxspeed:coach": "100", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (articulated)" - } - }, - { - "tags": { - "maxspeed": "70", - "maxspeed:bus:conditional": "60 @ (articulated)", - "maxspeed:coach": "100", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (articulated)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70", - "maxspeed:bus:conditional": "60 @ (articulated)", - "maxspeed:coach": "100", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (articulated)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (articulated)", - "maxspeed:coach": "100", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (articulated)", - "minspeed": "40" - } - } - ], - "SG": [ - { - "name": "school zone", - "tags": { - "maxspeed": "40" - } - }, - { - "name": "Singapore: silver zone", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "50" - } - } - ], - "SH": [ - { - "tags": { - "maxspeed": "30 mph" - } - } - ], - "SI": [ - { - "name": "living street", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "10" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:truck_bus": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "trunk", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (articulated); 80 @ (trailer)", - "maxspeed:coach": "100", - "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5); 70 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "90 @ (weightrating>3.5); 80 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", - "maxspeed:truck_bus": "50" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "80 @ (articulated); 80 @ (trailer)", - "maxspeed:coach": "100", - "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5); 70 @ (weightrating>3.5 AND trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "90 @ (weightrating>3.5); 80 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", - "maxspeed:truck_bus": "50", - "minspeed": "60" - } - } - ], - "SK": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "90" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "90" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "90" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "100 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "100", - "minspeed": "80" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:conditional": "100 @ (weightrating>3.5); 90 @ (trailer)", - "maxspeed:hgv": "100", - "minspeed": "80" - } - } - ], - "SN": [ - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "70 @ (seats>=15)", - "maxspeed:conditional": "85 @ (trailerweight>0.75)", - "maxspeed:hgv": "85", - "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)" - } - }, - { - "name": "Senegal: National road", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "70 @ (seats>=15)", - "maxspeed:conditional": "85 @ (trailerweight>0.75)", - "maxspeed:hgv": "85", - "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)", - "maxspeed:motorcycle": "100" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "70 @ (seats>=15)", - "maxspeed:conditional": "85 @ (trailerweight>0.75)", - "maxspeed:hgv": "85", - "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)", - "maxspeed:motorcycle": "100" - } - } - ], - "SS": [ - { - "name": "urban", - "tags": { - "maxspeed": "45" - } - } - ], - "SV": [ - { - "name": "roundabout", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "40", - "maxspeed:hgv": "40" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:hgv": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:hgv": "70" - } - } - ], - "TD": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:conditional": "40 @ (weightrating>3.5)" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", - "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" - } - } - ], - "TH": [ - { - "name": "urban", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "45 @ (trailer)", - "maxspeed:goods:conditional": "60 @ (weight>2.2)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "60", - "maxspeed:tricycle": "45" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "55 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weight>2.2)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "70", - "maxspeed:tricycle": "55" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:conditional": "55 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weight>2.2)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "90", - "maxspeed:school_bus": "70", - "maxspeed:tricycle": "55" - } - }, - { - "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e02\u0e19\u0e4d\u0e32\u0e19", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "45 @ (trailer)", - "maxspeed:goods:conditional": "60 @ (weight>2.2)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "60", - "maxspeed:tricycle": "45" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods:conditional": "90 @ (weight>2.2)", - "maxspeed:minibus": "100", - "maxspeed:motorcycle": "100", - "maxspeed:school_bus": "80", - "maxspeed:tricycle": "65" - } - }, - { - "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 on bridge or in tunnel", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods:conditional": "80 @ (weight>2.2)", - "maxspeed:minibus": "100", - "maxspeed:school_bus": "80", - "motorcycle": "no", - "tricycle": "no" - } - }, - { - "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 not on bridge or in tunnel", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods:conditional": "90 @ (weight>2.2)", - "maxspeed:minibus": "110", - "maxspeed:school_bus": "90", - "motorcycle": "no", - "tricycle": "no" - } - }, - { - "name": "Thailand: motorway that is not a \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "90", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods:conditional": "90 @ (weight>2.2)", - "maxspeed:minibus": "100", - "maxspeed:motorcycle": "110", - "maxspeed:school_bus": "80", - "maxspeed:tricycle": "65" - } - } - ], - "TL": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "70 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "80 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "110", - "maxspeed:goods:conditional": "90 @ (trailer)", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "80 @ (trailer)", - "minspeed": "40" - } - } - ], - "TN": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:tricycle": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>10); 70 @ (weightrating>12); 60 @ (weightrating>19)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12); 60 @ (weightrating>19)", - "maxspeed:hazmat:conditional": "70 @ (weightrating>3.5); 60 @ (weightrating>12)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (weightrating>10); 70 @ (weightrating>12); 60 @ (weightrating>19)", - "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12); 60 @ (weightrating>19)", - "maxspeed:hazmat:conditional": "70 @ (weightrating>3.5); 60 @ (weightrating>12)", - "maxspeed:tricycle": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "110", - "maxspeed:bus:conditional": "100 @ (weightrating>10); 90 @ (weightrating>12); 80 @ (weightrating>19)", - "maxspeed:conditional": "100 @ (weightrating>3.5); 90 @ (weightrating>12); 80 @ (weightrating>19)", - "maxspeed:hazmat:conditional": "90 @ (weightrating>3.5); 80 @ (weightrating>12)", - "maxspeed:tricycle": "70", - "minspeed": "60" - } - } - ], - "TR": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:conditional": "40 @ (trailer)", - "maxspeed:hazmat": "30" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70", - "maxspeed:tricycle:conditional": "60 @ (trailer)", - "minspeed": "15" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:bus:conditional": "70 @ (trailer)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hazmat": "50", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:motorcycle:conditional": "70 @ (trailer)", - "maxspeed:tricycle": "70", - "maxspeed:tricycle:conditional": "60 @ (trailer)", - "minspeed": "15" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "90", - "maxspeed:bus:conditional": "80 @ (trailer)", - "maxspeed:conditional": "100 @ (trailer)", - "maxspeed:goods": "100", - "maxspeed:goods:conditional": "90 @ (trailer)", - "maxspeed:hazmat": "60", - "maxspeed:hgv": "85", - "maxspeed:hgv:conditional": "75 @ (trailer)", - "maxspeed:motorcycle": "90", - "maxspeed:motorcycle:conditional": "80 @ (trailer)", - "maxspeed:tricycle": "80", - "maxspeed:tricycle:conditional": "70 @ (trailer)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "90 @ (trailer)", - "maxspeed:conditional": "110 @ (trailer)", - "maxspeed:goods": "110", - "maxspeed:goods:conditional": "100 @ (trailer)", - "maxspeed:hazmat": "70", - "maxspeed:hgv": "90", - "maxspeed:hgv:conditional": "0 @ (trailer)", - "maxspeed:motorcycle": "100", - "maxspeed:motorcycle:conditional": "90 @ (trailer)", - "maxspeed:tricycle": "80", - "maxspeed:tricycle:conditional": "70 @ (trailer)", - "minspeed": "40" - } - } - ], - "TT": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "65", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods": "65" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "65", - "maxspeed:conditional": "65 @ (trailer)", - "maxspeed:goods": "65" - } - } - ], - "TV": [ - { - "tags": { - "maxspeed": "40" - } - } - ], - "TW": [ - { - "name": "without centerline", - "tags": { - "maxspeed": "30" - } - }, - { - "tags": { - "maxspeed": "50" - } - } - ], - "TZ": [ - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "110", - "maxspeed:conditional": "80 @ (weightrating>3.5)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "110", - "maxspeed:conditional": "80 @ (weightrating>3.5)" - } - } - ], - "UA": [ - { - "name": "living street", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "pedestrian zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "90 @ (seats>=18)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus:conditional": "90 @ (seats>=18)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "dual carriageway", - "tags": { - "maxspeed": "110", - "maxspeed:bus:conditional": "90 @ (seats>=18)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "80", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus:conditional": "90 @ (seats>=18)", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "80", - "maxspeed:truck_bus": "60" - } - } - ], - "UG": [ - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer)", - "maxspeed:goods": "60" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer)", - "maxspeed:goods": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:conditional": "60 @ (trailer)", - "maxspeed:goods": "60" - } - }, - { - "name": "rural road with asphalt or concrete surface", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods": "80" - } - } - ], - "US": [ - { - "tags": {} - } - ], - "US-AK": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - } - ], - "US-AL": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "tags": { - "maxspeed": "45 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "45 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "35 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "name": "Alabama: rural highway", - "tags": { - "maxspeed": "55 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "name": "Alabama: rural highway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph", - "maxspeed:hazmat": "55 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:hazmat": "55 mph" - } - } - ], - "US-AR": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" - } - }, - { - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "55 mph @ (caravan)", - "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "55 mph @ (caravan)", - "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" - } - } - ], - "US-AZ": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "65 mph" - } - } - ], - "US-CA": [ - { - "name": "California: alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (trailer)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (trailer)" - } - }, - { - "name": "rural road with 1 lane in each direction", - "tags": { - "maxspeed": "65 mph", - "maxspeed:bus:conditional": "55 mph @ (articulated); 55 mph @ (agricultural)", - "maxspeed:conditional": "55 mph @ (trailer)", - "maxspeed:hazmat": "55 mph", - "maxspeed:hgv:conditional": "55 mph @ (articulated)", - "maxspeed:school_bus": "55 mph" - } - } - ], - "US-CO": [ - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural winding mountain road", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "rural open mountain road", - "tags": { - "maxspeed": "40 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "expressway", - "tags": { - "maxspeed": "65 mph" - } - } - ], - "US-CT": [ - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:school_bus": "50 mph" - } - } - ], - "US-DC": [ - { - "name": "playground zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "District of Columbia: alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "tags": { - "maxspeed": "20 mph", - "maxspeed:horse": "8 mph" - } - } - ], - "US-DE": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural single carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-FL": [ - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-GA": [ - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "35 mph", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed:school_bus": "55 mph" - } - } - ], - "US-HI": [ - { - "tags": {} - } - ], - "US-IA": [ - { - "name": "business district", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "Iowa: suburban district", - "tags": { - "maxspeed": "45 mph" - } - }, - { - "name": "Iowa: Institutional road", - "tags": { - "maxspeed": "45 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "50 mph @ (sunset-sunrise)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-ID": [ - { - "name": "residential district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "Idaho: State highway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "75 mph", - "maxspeed:hgv:conditional": "65 mph @ (axles>=5 AND weightrating>26000 lb)" - } - } - ], - "US-IL": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:bus": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:bus": "55 mph" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph", - "maxspeed:bus": "65 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "70 mph" - } - } - ], - "US-IN": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "55 mph @ (length>85 ft)", - "maxspeed:school_bus": "40 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (length>85 ft)", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (length>85 ft)", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "60 mph", - "maxspeed:conditional": "55 mph @ (length>85 ft)", - "maxspeed:school_bus": "40 mph" - } - }, - { - "name": "rural US interstate highway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:bus": "70 mph", - "maxspeed:conditional": "65 mph @ (weightrating>50000 lb); 55 mph @ (length>85 ft)", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>50000 lb); 55 mph @ (length>85 ft)", - "maxspeed:school_bus": "60 mph" - } - } - ], - "US-KS": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "55 mph @ (caravan)" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "55 mph @ (caravan)" - } - }, - { - "name": "Kansas: state highway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "55 mph @ (caravan)" - } - }, - { - "name": "dual carriageway", - "tags": { - "maxspeed": "75 mph", - "maxspeed:conditional": "55 mph @ (caravan)" - } - } - ], - "US-KY": [ - { - "name": "parking lot", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "65 mph" - } - } - ], - "US-LA": [ - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "70 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-MA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "40 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "40 mph" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "50 mph" - } - } - ], - "US-MD": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "single carriageway in residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "dual carriageway in residential district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-ME": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "tags": { - "maxspeed": "45 mph", - "maxspeed:school_bus": "45 mph" - } - } - ], - "US-MI": [ - { - "name": "trailer park", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "unpaved road", - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", - "maxspeed:school_bus": "65 mph", - "minspeed": "55 mph" - } - } - ], - "US-MN": [ - { - "name": "alley", - "tags": { - "maxspeed": "10 mph" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban US interstate highway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "expressway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural US interstate highway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-MO": [ - { - "name": "lettered road with 2 lanes", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "urban expressway", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "urban US interstate highway", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "rural expressway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "70 mph" - } - }, - { - "name": "rural US interstate highway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-MS": [ - { - "tags": { - "maxspeed": "65 mph", - "minspeed": "30 mph" - } - } - ], - "US-MT": [ - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv": "65 mph" - } - }, - { - "tags": { - "maxspeed": "70 mph", - "maxspeed:conditional": "65 mph @ ((sunset+01:30)-(sunrise-01:30))", - "maxspeed:hgv": "65 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70 mph", - "maxspeed:conditional": "65 mph @ ((sunset+01:30)-(sunrise-01:30))", - "maxspeed:hgv": "65 mph" - } - }, - { - "name": "urban US interstate highway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:hgv": "65 mph" - } - }, - { - "name": "rural US interstate highway", - "tags": { - "maxspeed": "80 mph", - "maxspeed:hgv": "70 mph" - } - } - ], - "US-NC": [ - { - "name": "urban", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-ND": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "70 mph" - } - }, - { - "name": "US interstate highway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "75 mph" - } - } - ], - "US-NE": [ - { - "name": "business district", - "tags": { - "maxspeed": "20 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "50 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "Nebraska: State highway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "Nebraska: State expressway or super-two highway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph", - "maxspeed:conditional": "50 mph @ (caravan)", - "minspeed": "40 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "75 mph", - "maxspeed:conditional": "50 mph @ (caravan)" - } - } - ], - "US-NH": [ - { - "name": "urban school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "urban residential district", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural residential district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "45 mph @ (caravan)", - "minspeed": "45 mph" - } - } - ], - "US-NJ": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "urban business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "suburban business district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "suburban residential district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph" - } - } - ], - "US-NM": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-NV": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "tags": { - "maxspeed": "80 mph", - "maxspeed:school_bus": "55 mph" - } - } - ], - "US-NY": [ - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>10000 lb)" - } - } - ], - "US-OH": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban motorway", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "urban expressway without traffic lights", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "Ohio: Urban state route", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "rural expressway", - "tags": { - "maxspeed": "60 mph" - } - }, - { - "name": "rural expressway without traffic lights", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural motorway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-OK": [ - { - "name": "school zone", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "55 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "35 mph", - "maxspeed:school_bus": "55 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed:school_bus": "65 mph" - } - } - ], - "US-OR": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "school zone", - "tags": { - "maxspeed": "25 mph", - "maxspeed:conditional": "20 mph @ (07:00-17:00)" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "20 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - }, - { - "name": "public park", - "tags": { - "maxspeed": "25 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "65 mph", - "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", - "maxspeed:school_bus": "55 mph", - "maxspeed:truck_bus": "55 mph" - } - } - ], - "US-PA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban road which is not numbered", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-RI": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "50 mph", - "maxspeed:conditional": "45 mph @ ((sunset+00:30)-(sunrise-00:30))" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph", - "maxspeed:conditional": "45 mph @ ((sunset+00:30)-(sunrise-00:30))" - } - } - ], - "US-SC": [ - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "40 mph" - } - } - ], - "US-SD": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "65 mph", - "maxspeed:conditional": "45 mph @ (caravan)" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "80 mph", - "maxspeed:conditional": "55 mph @ (caravan)", - "minspeed": "40 mph" - } - } - ], - "US-TN": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "Tennessee: Rural state road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "70 mph", - "minspeed": "55 mph" - } - }, - { - "name": "US interstate highway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "70 mph", - "minspeed": "55 mph" - } - } - ], - "US-TX": [ - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph", - "maxspeed:school_bus": "50 mph" - } - }, - { - "tags": { - "maxspeed": "60 mph", - "maxspeed:school_bus": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "60 mph", - "maxspeed:school_bus": "50 mph" - } - }, - { - "name": "rural numbered road", - "tags": { - "maxspeed": "70 mph", - "maxspeed:school_bus": "60 mph" - } - } - ], - "US-UT": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-VA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "35 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "rural unpaved road", - "tags": { - "maxspeed": "35 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "Virginia: Rustic road", - "tags": { - "maxspeed": "35 mph", - "maxspeed:school_bus": "45 mph" - } - }, - { - "name": "Virginia: State primary highway", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "60 mph" - } - }, - { - "name": "rural road with 2 or more lanes in each direction", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "60 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "60 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "55 mph", - "maxspeed:school_bus": "60 mph" - } - } - ], - "US-VT": [ - { - "tags": { - "maxspeed": "50 mph" - } - } - ], - "US-WA": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "50 mph" - } - }, - { - "name": "Washington: State highway", - "tags": { - "maxspeed": "60 mph", - "maxspeed:conditional": "60 mph @ (weightrating>10000 lb); 60 mph @ (trailer); 60 mph @ (articulated)" - } - } - ], - "US-WI": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "alley", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "Wisconsin: semiurban or outlying district", - "tags": { - "maxspeed": "35 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "Wisconsin: Rustic road", - "tags": { - "maxspeed": "40 mph" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "70 mph" - } - } - ], - "US-WV": [ - { - "name": "school zone", - "tags": { - "maxspeed": "15 mph" - } - }, - { - "name": "business district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "55 mph" - } - } - ], - "US-WY": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20 mph" - } - }, - { - "name": "residential district", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "30 mph" - } - }, - { - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "65 mph" - } - }, - { - "name": "rural road without asphalt or concrete surface", - "tags": { - "maxspeed": "55 mph" - } - }, - { - "name": "Wyoming: State highway", - "tags": { - "maxspeed": "70 mph" - } - }, - { - "name": "US interstate highway", - "tags": { - "maxspeed": "75 mph" - } - } - ], - "UY": [ - { - "name": "school zone", - "tags": { - "maxspeed": "20" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "45" - } - }, - { - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:coach": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "80", - "maxspeed:coach": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:hazmat": "80", - "maxspeed:hgv": "80" - } - } - ], - "UZ": [ - { - "name": "urban residential district", - "tags": { - "maxspeed": "30" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "70" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "60", - "maxspeed:truck_bus": "60" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:coach": "90", - "maxspeed:conditional": "80 @ (trailer)", - "maxspeed:goods:conditional": "70 @ (trailer)", - "maxspeed:hgv": "80", - "maxspeed:hgv:conditional": "70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "80", - "maxspeed:school_bus": "60", - "maxspeed:truck_bus": "60" - } - } - ], - "VE": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "70", - "maxspeed:conditional": "50 @ (sunset-sunrise)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "70", - "maxspeed:conditional": "50 @ (sunset-sunrise)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "90", - "maxspeed:lanes": "90|70" - } - } - ], - "VI": [ - { - "name": "urban", - "tags": { - "maxspeed": "20 mph", - "maxspeed:bus": "10 mph", - "maxspeed:hgv": "10 mph" - } - }, - { - "tags": { - "maxspeed": "35 mph", - "maxspeed:bus": "30 mph", - "maxspeed:hgv": "30 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "35 mph", - "maxspeed:bus": "30 mph", - "maxspeed:hgv": "30 mph" - } - } - ], - "VN": [ - { - "name": "urban single carriageway", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "urban one-way road with 1 lane", - "tags": { - "maxspeed": "50" - } - }, - { - "name": "urban dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "60" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:coach": "70", - "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "60" - } - }, - { - "name": "rural single carriageway", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:coach": "70", - "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "60" - } - }, - { - "name": "rural single carriageway", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:coach": "70", - "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "60" - } - }, - { - "name": "rural one-way road with 1 lane", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "60", - "maxspeed:coach": "70", - "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", - "maxspeed:minibus": "80", - "maxspeed:motorcycle": "60" - } - }, - { - "name": "rural dual carriageway with 2 or more lanes in each direction", - "tags": { - "maxspeed": "90", - "maxspeed:bus": "70", - "maxspeed:coach": "80", - "maxspeed:conditional": "80 @ (seats>=31); 60 @ (trailer)", - "maxspeed:goods": "90", - "maxspeed:goods:conditional": "80 @ (weightcapacity>3.5); 70 @ (trailer)", - "maxspeed:minibus": "90", - "maxspeed:motorcycle": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "minspeed": "50" - } - } - ], - "WS": [ - { - "name": "urban", - "tags": { - "maxspeed": "25 mph" - } - }, - { - "tags": { - "maxspeed": "35 mph" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "35 mph" - } - } - ], - "XK": [ - { - "name": "urban school zone", - "tags": { - "maxspeed:conditional": "30 @ (07:00-20:00)" - } - }, - { - "name": "urban", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "80", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "name": "rural school zone", - "tags": { - "maxspeed": "50", - "maxspeed:bus": "80", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "name": "rural dual carriageway", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "80", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "80", - "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorroad", - "tags": { - "maxspeed": "110", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (weightrating>12)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "85", - "maxspeed:goods:conditional": "80 @ (trailer); 70 @ (weightrating>12)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "130", - "maxspeed:bus": "100", - "maxspeed:bus:conditional": "80 @ (weightrating>12)", - "maxspeed:conditional": "80 @ (caravan)", - "maxspeed:goods": "85", - "maxspeed:goods:conditional": "80 @ (trailer); 70 @ (weightrating>12)", - "maxspeed:hazmat": "70", - "maxspeed:school_bus": "70", - "minspeed": "60" - } - } - ], - "ZA": [ - { - "name": "urban", - "tags": { - "maxspeed": "60", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)" - } - }, - { - "name": "motorway", - "tags": { - "maxspeed": "120", - "maxspeed:bus": "100", - "maxspeed:hgv:conditional": "80 @ (weightrating>9)", - "tricycle": "no" - } - } - ], - "ZM": [ - { - "name": "urban", - "tags": { - "maxspeed": "40" - } - }, - { - "tags": { - "maxspeed": "100", - "maxspeed:hgv": "80" - } - }, - { - "name": "rural", - "tags": { - "maxspeed": "100", - "maxspeed:hgv": "80" - } - } - ] - }, - "warnings": [ - "Transnistria: Unknown country / subdivision", - "Transnistria: Unknown country / subdivision", - "Transnistria: Unknown country / subdivision", - "Transnistria: Unknown country / subdivision", - "Transnistria: Unknown country / subdivision", - "Somaliland: Unknown country / subdivision", - "Somaliland: Unknown country / subdivision", - "Somaliland: Unknown country / subdivision", - "Sint Maarten: Unknown country / subdivision", - "Sint Maarten: Unknown country / subdivision", - "Sint Maarten: Unknown country / subdivision", - "CA-NS: Unable to map 'urban public park'", - "MN: Unable to map 'long road'", - "NP: Unable to map 'hilly road'", - "PG: Unable to map 'village'", - "US-CO: Unable to map 'rural winding mountain road'", - "US-CO: Unable to map 'rural open mountain road'", - "US-IA: Unable to map 'public park'", - "US-IA: Unable to map 'Iowa: Institutional road'", - "US-MI: Unable to map 'trailer park'", - "US-MI: Unable to map 'public park'", - "US-ND: Unable to map 'public park'", - "US-NJ: Unable to map 'suburban business district'", - "US-NJ: Unable to map 'suburban residential district'", - "US-OH: Unable to map 'urban expressway without traffic lights'", - "US-OH: Unable to map 'public park'", - "US-OH: Unable to map 'rural expressway without traffic lights'", - "US-OK: Unable to map 'public park'", - "US-OR: Unable to map 'public park'", - "US-WI: Unable to map 'Wisconsin: semiurban or outlying district'" + "meta": { + "license": "Creative Commons Attribution-ShareAlike 2.0 license", + "licenseUrl": "https://wiki.openstreetmap.org/wiki/Wiki_content_license", + "revisionId": "2951812", + "source": "https://wiki.openstreetmap.org/wiki/Default_speed_limits", + "timestamp": "2026-02-04T12:09:41+00:00" + }, + "roadTypesByName": { + "Alabama: rural highway": { + "filter": "{rural} and ref~\"(US|AL|I).*\"", + "fuzzyFilter": "{rural} and highway~trunk|trunk_link|primary|primary_link", + "relationFilter": "type=route and route=road and network~\"US:(AL|US)(:.*)?\"" + }, + "Alabama: rural highway with 2 or more lanes in each direction": { + "filter": "{Alabama: rural highway} and {road with 2 or more lanes in each direction}" + }, + "Albania: Rrug\u00eb interurbane kryesore": { + "filter": "{rural} and {dual carriageway} and lanes>=2" + }, + "Alberta: Provincial highway": { + "filter": "ref" + }, + "Alberta: Urban provincial highway": { + "filter": "{urban} and ref" + }, + "Alberta: Urban road": { + "filter": "{urban} and !ref" + }, + "Andorra: Carretera general": { + "filter": "ref~CG.*", + "fuzzyFilter": "highway=primary" + }, + "Andorra: Carretera secund\u00e0ria": { + "filter": "ref~CS.*", + "fuzzyFilter": "highway=secondary" + }, + "Argentina: Avenida": { + "filter": "{urban} and highway~trunk|trunk_link|primary|primary_link|secondary|secondary_link" + }, + "Argentina: Calle": { + "filter": "{urban} and highway~tertiary|tertiary_link|unclassified|residential|living_street|pedestrian|service|track" + }, + "Argentina: Semiautopista": { + "filter": "highway~trunk|trunk_link and {motorroad}" + }, + "Benin: Route nationale": { + "filter": "ref~\"RN(IE)?.*\"", + "relationFilter": "type=route and route=road and name~\"RN(IE)?.*\"" + }, + "Brasil: Estrada rural": { + "fuzzyFilter": "{rural} and {unpaved road}" + }, + "Brasil: Rodovia rural de pista dupla": { + "fuzzyFilter": "{rural} and !{unpaved road} and {dual carriageway}" + }, + "Brasil: Rodovia rural de pista simples": { + "fuzzyFilter": "{rural} and !{unpaved road} and {single carriageway}" + }, + "Brasil: Via urbana arterial": { + "fuzzyFilter": "{urban} and highway~trunk|trunk_link|primary|primary_link|secondary|secondary_link" + }, + "Brasil: Via urbana coletora": { + "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link" + }, + "Brasil: Via urbana de tr\u00e2nsito r\u00e1pido": { + "fuzzyFilter": "{urban} and (highway~motorway|motorway_link)" + }, + "Brasil: Via urbana local": { + "fuzzyFilter": "{urban} and highway~service|residential|unclassified" + }, + "California: alley": { + "filter": "highway=service and (!width or width<=7.6)" + }, + "Colorado: freeway or expressway with 2 or more lanes in each direction": { + "filter": "({motorway} or {expressway}) and {road with 2 or more lanes in each direction}" + }, + "Croatia:brza cesta": { + "filter": "highway~trunk|trunk_link and ref~B.*" + }, + "District of Columbia: alley": { + "filter": "highway=service and (!width or width<9.144)" + }, + "Ecuador: Via perimetral": { + "fuzzyFilter": "{urban} and highway~trunk|trunk_link and ref~E.*" + }, + "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0633\u0631\u064a\u0639\u0629 \u0623\u0648 \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629 \u0627\u0644\u062a\u0649 \u062a\u0631\u0628\u0637 \u0627\u0644\u0645\u062d\u0627\u0641\u0638\u0627\u062a \u060c \u0648\u0627\u0644\u062a\u0649 \u062a\u062a\u0628\u0639 \u0627\u0644\u0645\u062d\u0644\u064a\u0627\u062a \u0623\u0648 \u0627\u0644\u0647\u064a\u0626\u0629 \u0627\u0644\u0639\u0627\u0645\u0629 \u0644\u0644\u0637\u0631\u0642 \u0648\u0627\u0644\u0643\u0628\u0627\u0631\u0649\"": { + "fuzzyFilter": "{rural} and highway~primary|primary_link|secondary|secondary_link" + }, + "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0635\u062d\u0631\u0627\u0648\u064a\u0629\"": { + "fuzzyFilter": "{rural} and highway~trunk|trunk_link|motorway|motorway_link" + }, + "Flanders:Jaagpad": { + "filter": "designation=towpath" + }, + "Guatemala: Arteria principal": { + "filter": "{urban} and ((!{oneway} and lanes>=3 and width>=10.5) or ({oneway} and lanes>=2 and width>=7))", + "fuzzyFilter": "{urban} and highway~primary|primary_link" + }, + "Guatemala: Arteria secundaria": { + "filter": "{urban} and ((!{oneway} and lanes>=3 and width>=9) or ({oneway} and lanes>=2 and width>=6))", + "fuzzyFilter": "{urban} and highway~secondary|secondary_link" + }, + "Guatemala: Camino": { + "filter": "{unpaved road}" + }, + "Guatemala: Carretera principal": { + "filter": "{rural} and lanes>=2 and width>=7 and {has shoulder}", + "fuzzyFilter": "highway~primary|primary_link" + }, + "Guatemala: Carretera secundaria": { + "filter": "{rural} and lanes>=2 and width>=5.5", + "fuzzyFilter": "highway~secondary|secondary_link|tertiary|tertiary_link|unclassified" + }, + "Guatemala: V\u00eda local": { + "filter": "{urban} and width>=5", + "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|unclassified" + }, + "Guatemala: V\u00eda residencial": { + "filter": "{urban} and width>=3 and width<5.5", + "fuzzyFilter": "{urban} and highway~service|residential" + }, + "Guatemala: V\u00eda r\u00e1pida": { + "filter": "{rural} and {has shoulder} and (({oneway} and lanes>=2 and width>=7) or (!{oneway} and lanes>=4 and width>=14))", + "fuzzyFilter": "highway~trunk|trunk_link" + }, + "Guatemala: V\u00eda r\u00e1pida urbana": { + "filter": "{urban} and {has shoulder} and (({oneway} and lanes>=2 and width>=7) or (!{oneway} and lanes>=4 and width>=14))", + "fuzzyFilter": "{urban} and highway~trunk|trunk_link" + }, + "Guyana: Timehri Field / Linden Highway": { + "fuzzyFilter": "{rural} and highway~primary|primary_link" + }, + "Idaho: State highway": { + "filter": "ref~\"(ID|SH|US).*\"", + "relationFilter": "type=route and route=road and network~\"US:(ID|US)(:.*)?\"" + }, + "Iowa: suburban district": { + "filter": "{urban} and !{school zone} and !{residential district} and !{business district}" + }, + "Ireland: Local road": { + "filter": "ref~L.* or (highway~tertiary|unclassified and (!ref or ref~L.*))" + }, + "Ireland: National primary road": { + "filter": "ref~N[1-9] or ref~N[1234][0-9] or (highway~trunk|trunk_link and (!ref or ref~N.*))" + }, + "Ireland: National road": { + "filter": "ref~N.* or (highway~trunk|trunk_link|primary|primary_link and (!ref or ref~N.*))" + }, + "Ireland: National secondary road": { + "filter": "ref~N[56789][0-9] or (highway~primary|primary_link and (!ref or ref~N.*))" + }, + "Ireland: Regional road": { + "filter": "ref~R.* or (highway~secondary|secondary_link and (!ref or ref~R.*))" + }, + "Italy: Autostrada": { + "filter": "{motorway}" + }, + "Italy: Strada extraurbana local": { + "filter": "{rural}" + }, + "Italy: Strada extraurbana principale": { + "filter": "{rural} and highway~trunk|trunk_link and {dual carriageway} and lanes>=2 and {has shoulder}" + }, + "Italy: Strada extraurbana secondaria": { + "filter": "{rural} and {single carriageway} and lanes>=2" + }, + "Kansas: state highway": { + "filter": "ref~\"(KS|US).*\"", + "relationFilter": "type=route and route=road and network~\"US:(KS|US)(:.*)?\"" + }, + "Malawi: rural highway": { + "filter": "{rural} and surface~asphalt|concrete and width>5.5" + }, + "Mauritius: A road": { + "filter": "ref~A.*", + "fuzzyFilter": "highway~primary|primary_link" + }, + "Mauritius: B road": { + "filter": "ref~B.*", + "fuzzyFilter": "highway~secondary|secondary_link" + }, + "Mauritius: C road": { + "filter": "ref~C.*", + "fuzzyFilter": "highway~tertiary|tertiary_link" + }, + "Mauritius: M road": { + "filter": "ref~M.*" + }, + "Mexico: avenida primaria sin acceso controlado": { + "filter": "highway~primary|primary_link and expressway!=yes" + }, + "Mexico: calle secundaria o calle terciaria": { + "filter": "highway~secondary|secondary_link|tertiary|tertiary_link|unclassified" + }, + "Mexico: carretera o autopista de jurisdicci\u00f3n federal": { + "filter": "ref~MEX.*" + }, + "Mexico: carril central de una avenida de acceso controlado": { + "filter": "highway~primary|primary_link and expressway=yes" + }, + "Mexico: zona de hospital, asilo, albergue o casa hogar": { + "filter": "highway~living_street|service|pedestrian|residential" + }, + "Mexico: zona o entorno escolar": { + "filter": "{school zone}" + }, + "Mexico: zona o entorno escolar en una v\u00eda secundaria o calle terciaria": { + "filter": "{Mexico: calle secundaria o calle terciaria} and {school zone}" + }, + "Nebraska: State expressway or super-two highway": { + "filter": "{expressway} and {Nebraska: State highway}" + }, + "Nebraska: State highway": { + "filter": "ref~\"(NE|US).*\"", + "relationFilter": "type=route and route=road and network=US:NE" + }, + "Newfoundland and Labrador: Trans-Canada highway": { + "filter": "ref=1" + }, + "Nicaragua: Carretera": { + "fuzzyFilter": "{rural} and (name~Carretera.* or highway~trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link)" + }, + "Niger: autoroute": { + "filter": "highway~motorway|motorway_link" + }, + "Niger: national road": { + "filter": "ref~N.*" + }, + "Ohio: Urban state route": { + "filter": "ref~SR.* and {urban}", + "relationFilter": "type=route and route=road and network=US:OH" + }, + "Palestine: \u0634\u0627\u0631\u0639\u0645\u062e\u062a\u0644": { + "fuzzyFilter": "{urban} and (highway~living_street|service|pedestrian or highway=residential and {has no sidewalk})" + }, + "Panama: Avenida": { + "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and (!lanes or ((!{oneway} and lanes<4) or ({oneway} and lanes<2)))" + }, + "Panama: Avenida de dos carriles": { + "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and ((!{oneway} and lanes=4) or ({oneway} and lanes=2))" + }, + "Panama: Carretera multicarril en zona urbana": { + "fuzzyFilter": "{urban} and highway~tertiary|tertiary_link|secondary|secondary_link|primary|primary_link|trunk|trunk_link and ((!{oneway} and lanes>4) or ({oneway} and lanes>2))" + }, + "Per\u00fa: Avenida": { + "filter": "name~Avenida.*" + }, + "Per\u00fa: Carretera": { + "filter": "ref~PE.*", + "relationFilter": "type=route and route=road and ref~PE.*" + }, + "Per\u00fa: V\u00eda Expresa": { + "filter": "name~\"V\u00eda Expresa.*\"" + }, + "Philippines: Barangay road": { + "filter": "designation=barangay_road or designation=barangay", + "fuzzyFilter": "{rural} and highway~unclassified|track" + }, + "Philippines: National road": { + "filter": "ref~[1-9][0-9]? or ref~[1-9][0-9][0-9] or designation~national_tertiary_road|national_secondary_road|national_primary_road" + }, + "Philippines: Provincial road": { + "filter": "designation=provincial_road" + }, + "Philippines: Through street": { + "filter": "{urban} and (ref or designation~provincial_road|national_tertiary_road|national_secondary_road|national_primary_road)" + }, + "Quebec: Autoroute": { + "filter": "(ref >= 1 and ref <= 99) or (ref >= 400 and ref <= 999)" + }, + "Romania: drumurile expres sau pe cele na\u021bionale europene": { + "filter": "ref~DN.*", + "relationFilter": "type=route and route=road and network=RO:DN" + }, + "Senegal: National road": { + "filter": "ref~N.*" + }, + "Singapore: silver zone": { + "filter": "hazard=elderly or silver_zone=yes or hazard=silver_zone" + }, + "Spain: Traves\u00eda": { + "fuzzyFilter": "{urban} and ref" + }, + "Tennessee: Rural state road with 2 or more lanes in each direction": { + "filter": "{Tennessee: State route} and {rural} and {road with 2 or more lanes in each direction}" + }, + "Tennessee: State route": { + "filter": "ref~\"(TN|SR|US).*\"", + "relationFilter": "type=route and route=road and network~\"US:(TN|US)(:.*)?\"" + }, + "Thailand: motorway that is not a \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29": { + "filter": "{motorway} and ref" + }, + "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e02\u0e19\u0e4d\u0e32\u0e19": { + "filter": "{frontage road}" + }, + "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 not on bridge or in tunnel": { + "filter": "{motorway} and !ref and (!bridge or bridge=no) and (!tunnel or tunnel=no)" + }, + "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 on bridge or in tunnel": { + "filter": "{motorway} and !ref and ((bridge and bridge!=no) or (tunnel and tunnel!=no))" + }, + "US interstate highway": { + "filter": "ref~I.* and {motorway}", + "relationFilter": "type=route and route=road and network=US:I" + }, + "US interstate highway with 2 or more lanes in each direction": { + "filter": "{US interstate highway} and ((lanes>=2 and {oneway}) or (lanes>=4 and !{oneway}))" + }, + "United Kingdom: restricted road": { + "filter": "lit=yes or maxspeed:type=GB:nsl_restricted", + "fuzzyFilter": "{urban} and (!lit or lit!=no) and !maxspeed:type" + }, + "Virginia: Rustic road": { + "filter": "ref~R.*" + }, + "Virginia: State primary highway": { + "filter": "ref~\"(US|VA).*\"", + "relationFilter": "type=route and route=road and network~\"US:(VA|US)(:.*)?\"" + }, + "Washington: State highway": { + "filter": "ref~\"(US|WA).*\"", + "relationFilter": "type=route and route=road and network~\"US:(WA|US)(:.*)?\"" + }, + "Wisconsin: Rustic road": { + "filter": "ref~R.*" + }, + "Wyoming: State highway": { + "filter": "ref~WYO?.*", + "relationFilter": "type=route and route=road and network~\"US:(WY|US)(:.*)?\"" + }, + "alley": { + "filter": "highway=service and service=alley" + }, + "business district": { + "filter": "abutters~retail|commercial" + }, + "cycle street": { + "filter": "bicycle_road=yes or cyclestreet=yes" + }, + "dual carriageway": { + "filter": "dual_carriageway=yes or maxspeed:type~\".*nsl_dual\"", + "fuzzyFilter": "oneway~yes|-1 and junction!~roundabout|circular and dual_carriageway!=no" + }, + "dual carriageway in residential district": { + "filter": "{dual carriageway} and {residential district}" + }, + "dual carriageway with 2 or more lanes in each direction": { + "filter": "{dual carriageway} and lanes>=2" + }, + "expressway": { + "filter": "expressway=yes" + }, + "footway": { + "filter": "highway=footway" + }, + "frontage road": { + "filter": "frontage_road=yes or side_road=yes" + }, + "has no sidewalk": { + "filter": "sidewalk~no|none or sidewalk:both~no|none or (sidewalk:left~no|none and sidewalk:right~no|none)" + }, + "has shoulder": { + "filter": "shoulder~yes|both|left|right or shoulder:left=yes or shoulder:right=yes or shoulder:both=yes" + }, + "has sidewalk": { + "filter": "sidewalk~yes|both|left|right|separate or sidewalk:left~yes|separate or sidewalk:right~yes|separate or sidewalk:both~yes|separate" + }, + "lettered road with 2 lanes": { + "filter": "ref~[A-Z] and lanes=2" + }, + "living street": { + "filter": "highway=living_street or living_street=yes" + }, + "motorroad": { + "filter": "motorroad=yes" + }, + "motorroad with 2 lanes in each direction": { + "filter": "{motorroad} and {road with 2 or more lanes in each direction}" + }, + "motorroad with dual carriageway": { + "filter": "{motorroad} and {dual carriageway}" + }, + "motorroad with single carriageway": { + "filter": "{motorroad} and {single carriageway}" + }, + "motorway": { + "filter": "highway~motorway|motorway_link" + }, + "motorway with 2 lanes in each direction": { + "filter": "{motorway} and {road with 2 or more lanes in each direction}" + }, + "numbered road": { + "filter": "ref", + "relationFilter": "type=route and route=road and ref" + }, + "oneway": { + "filter": "oneway~yes|-1 or junction~roundabout|circular" + }, + "parking lot": { + "filter": "highway=service and service=parking_aisle" + }, + "pedestrian zone": { + "filter": "highway=pedestrian" + }, + "playground zone": { + "filter": "hazard=children or playground_zone=yes or hazard=playground_zone or restriction=playground_zone or maxspeed:variable=playground_zone" + }, + "public park": { + "filter": "abutters=park" + }, + "residential district": { + "filter": "abutters=residential", + "fuzzyFilter": "highway~living_street|residential" + }, + "road with 1 lane in each direction": { + "filter": "!lanes or ((!{oneway} and lanes=2) or ({oneway} and lanes=1))" + }, + "road with 2 lanes in each direction": { + "filter": "((!{oneway} and lanes=4) or ({oneway} and lanes=2))" + }, + "road with 2 or more lanes in each direction": { + "filter": "((!{oneway} and lanes>=4) or ({oneway} and lanes>=2))" + }, + "road with 3 lanes in each direction": { + "filter": "((!{oneway} and lanes=6) or ({oneway} and lanes=3))" + }, + "road with 4 or more lanes in each direction": { + "filter": "((!{oneway} and lanes>=8) or ({oneway} and lanes>=4))" + }, + "road with asphalt or concrete surface": { + "filter": "surface~asphalt|concrete or (!surface and tracktype=grade1)" + }, + "road without asphalt or concrete surface": { + "filter": "(surface and surface!~asphalt|concrete) or (!surface and tracktype and tracktype!=grade1)" + }, + "roundabout": { + "filter": "junction=roundabout" + }, + "rural": { + "filter": "source:maxspeed~\".*(rural|motorway|motorroad)\" or maxspeed:type~\".*(rural|motorway|motorroad|nsl_single|nsl_dual)\" or zone:maxspeed~\".*(rural|motorway|motorroad)\" or zone:traffic~\".*(rural|motorway|motorroad)\" or maxspeed~\".*(rural|motorway|motorroad)\" or HFCS~.*Rural.* or rural=yes", + "fuzzyFilter": "lit=no or {has no sidewalk}" + }, + "rural US interstate highway": { + "filter": "{rural} and {US interstate highway}" + }, + "rural business district": { + "filter": "{rural} and {business district}" + }, + "rural dual carriageway": { + "filter": "{rural} and {dual carriageway}" + }, + "rural dual carriageway with 2 or more lanes in each direction": { + "filter": "{rural} and {dual carriageway with 2 or more lanes in each direction}" + }, + "rural expressway": { + "filter": "{rural} and {expressway}" + }, + "rural motorroad": { + "filter": "{rural} and {motorroad}" + }, + "rural motorroad with single carriageway": { + "filter": "{rural} and {motorroad} and {single carriageway}" + }, + "rural motorway": { + "filter": "{rural} and {motorway}" + }, + "rural numbered road": { + "filter": "{rural} and {numbered road}" + }, + "rural one-way road with 1 lane": { + "filter": "{rural} and {oneway} and lanes=1" + }, + "rural playground zone": { + "filter": "{rural} and {playground zone}" + }, + "rural residential district": { + "filter": "{rural} and {residential district}" + }, + "rural road with 1 lane in each direction": { + "filter": "{rural} and {road with 1 lane in each direction}" + }, + "rural road with 2 lanes in each direction": { + "filter": "{rural} and {road with 2 lanes in each direction}" + }, + "rural road with 2 or more lanes in each direction": { + "filter": "{rural} and {road with 2 or more lanes in each direction}" + }, + "rural road with 3 lanes in each direction": { + "filter": "{rural} and {road with 3 lanes in each direction}" + }, + "rural road with 4 or more lanes in each direction": { + "filter": "{rural} and {road with 4 or more lanes in each direction}" + }, + "rural road with asphalt or concrete surface": { + "filter": "{rural} and {road with asphalt or concrete surface}" + }, + "rural road without asphalt or concrete surface": { + "filter": "{rural} and {road without asphalt or concrete surface}" + }, + "rural school zone": { + "filter": "{rural} and {school zone}" + }, + "rural single carriageway": { + "filter": "{rural} and {single carriageway}" + }, + "rural single carriageway with 2 or more lanes in each direction": { + "filter": "{rural} and {single carriageway} and lanes>=4" + }, + "rural unpaved road": { + "filter": "{rural} and {unpaved road}" + }, + "school zone": { + "filter": "school_zone=yes or hazard=school_zone or restriction=school_zone or maxspeed:variable=school_zone" + }, + "service road": { + "filter": "highway=service" + }, + "single carriageway": { + "filter": "dual_carriageway=no or maxspeed:type~\".*nsl_single\"", + "fuzzyFilter": "oneway!~yes|-1 or junction~roundabout|circular or dual_carriageway=no" + }, + "single carriageway in residential district": { + "filter": "{single carriageway} and {residential district}" + }, + "trailer park": { + "filter": "abutters=trailer_park" + }, + "trunk": { + "filter": "highway~trunk|trunk_link" + }, + "unpaved road": { + "filter": "surface~unpaved|ground|gravel|dirt|grass|compacted|sand|fine_gravel|earth|pebblestone|mud|clay|soil|rock or (!surface and tracktype and tracktype!=grade1)" + }, + "urban": { + "filter": "source:maxspeed~.*urban or maxspeed:type~.*urban or zone:maxspeed~.*urban or zone:traffic~.*urban or maxspeed~.*urban or HFCS~.*Urban.* or rural=no", + "fuzzyFilter": "highway~living_street|residential or lit=yes or {has sidewalk}" + }, + "urban US interstate highway": { + "filter": "{urban} and {US interstate highway}" + }, + "urban and without centerline": { + "filter": "{urban} and {without centerline}" + }, + "urban business district": { + "filter": "{urban} and {business district}" + }, + "urban dual carriageway": { + "filter": "{urban} and {dual carriageway}" + }, + "urban dual carriageway with 2 or more lanes in each direction": { + "filter": "{urban} and {dual carriageway with 2 or more lanes in each direction}" + }, + "urban expressway": { + "filter": "{urban} and {expressway}" + }, + "urban motorroad": { + "filter": "{urban} and {motorroad}" + }, + "urban motorway": { + "filter": "{urban} and {motorway}" + }, + "urban motorway with paved shoulders": { + "filter": "{urban} and {motorway} and {has shoulder}" + }, + "urban one-way road with 1 lane": { + "filter": "{urban} and {oneway} and lanes<2" + }, + "urban playground zone": { + "filter": "{urban} and {playground zone}" + }, + "urban public park": { + "filter": "{urban} and {public park}" + }, + "urban residential district": { + "filter": "{urban} and {residential district}" + }, + "urban road which is not numbered": { + "filter": "{urban} and !{numbered road}" + }, + "urban road with 2 lanes in each direction": { + "filter": "{urban} and {road with 2 lanes in each direction}" + }, + "urban road with 2 or more lanes in each direction": { + "filter": "{urban} and {road with 2 or more lanes in each direction}" + }, + "urban road with 3 lanes in each direction": { + "filter": "{urban} and {road with 3 lanes in each direction}" + }, + "urban road with 4 or more lanes in each direction": { + "filter": "{urban} and {road with 4 or more lanes in each direction}" + }, + "urban road without sidewalk": { + "filter": "{urban} and {has no sidewalk}" + }, + "urban school zone": { + "filter": "{urban} and {school zone}" + }, + "urban single carriageway": { + "filter": "{urban} and {single carriageway}" + }, + "urban unpaved road": { + "filter": "{urban} and {unpaved road}" + }, + "without centerline": { + "filter": "lanes<2 or lane_markings=no" + } + }, + "speedLimitsByCountryCode": { + "AD": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Andorra: Carretera secund\u00e0ria", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Andorra: Carretera general", + "tags": { + "maxspeed": "90" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "120" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120" + } + } + ], + "AE": [ + { + "name": "service road", + "tags": { + "maxspeed": "25" + } + }, + { + "name": "urban single carriageway", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban dual carriageway", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "80", + "maxspeed:hgv": "80", + "maxspeed:school_bus": "80" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:hgv": "80", + "maxspeed:school_bus": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:hgv": "80", + "maxspeed:school_bus": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "80", + "maxspeed:hgv": "80", + "maxspeed:school_bus": "80", + "minspeed": "60" + } + } + ], + "AF": [ + { + "tags": { + "maxspeed": "90" + } + } + ], + "AG": [ + { + "name": "urban", + "tags": { + "maxspeed": "20 mph", + "maxspeed:bus": "15 mph", + "maxspeed:goods": "15 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "25 mph", + "maxspeed:goods": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "25 mph", + "maxspeed:goods": "25 mph" + } + } + ], + "AL": [ + { + "name": "urban", + "tags": { + "maxspeed": "40", + "maxspeed:bus:conditional": "35 @ (weightrating>8)", + "maxspeed:hazmat": "30", + "maxspeed:hgv:conditional": "35 @ (trailer); 35 @ (articulated)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "70 @ (weightrating>8)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "70 @ (weightrating>8)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "Albania: Rrug\u00eb interurbane kryesore", + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "70 @ (weightrating>8)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 60 @ (weightrating>12)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "90 @ (weightrating>8)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated); 80 @ (weightrating>12)", + "maxspeed:truck_bus:conditional": "80 @ (weightrating>5)" + } + } + ], + "AM": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:coach": "90", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60" + } + } + ], + "AO": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "50", + "maxspeed:bus:conditional": "40 @ (trailer)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "60", + "maxspeed:goods:conditional": "50 @ (trailer)", + "maxspeed:hgv": "50", + "maxspeed:hgv:conditional": "50 @ (articulated); 40 @ (trailer)", + "maxspeed:motorcycle": "60", + "maxspeed:motorcycle:conditional": "50 @ (trailer)", + "maxspeed:tricycle": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "90", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "90", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "100", + "maxspeed:motorcycle:conditional": "80 @ (trailer)", + "maxspeed:tricycle": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "110", + "maxspeed:goods:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "90 @ (articulated); 80 @ (trailer)", + "maxspeed:motorcycle": "120", + "maxspeed:motorcycle:conditional": "100 @ (trailer)", + "maxspeed:tricycle": "100", + "minspeed": "40" + } + } + ], + "AR": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "Argentina: Calle", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "Argentina: Avenida", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "110", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80", + "maxspeed:motorcycle": "110", + "maxspeed:motorhome": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "110", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80", + "maxspeed:motorcycle": "110", + "maxspeed:motorhome": "90" + } + }, + { + "name": "Argentina: Semiautopista", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "110", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80", + "maxspeed:motorcycle": "120", + "maxspeed:motorhome": "90", + "minspeed": "40" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "110", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80", + "maxspeed:motorcycle": "130", + "maxspeed:motorhome": "100", + "minspeed": "65" + } + } + ], + "AS": [ + { + "name": "urban", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "25 mph" + } + } + ], + "AT": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", + "maxspeed:hgv": "70" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", + "maxspeed:hgv": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 70 @ (weightrating>3.5)", + "maxspeed:hgv": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (articulated)", + "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (articulated)", + "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + } + ], + "AU": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100" + } + } + ], + "AU-ACT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + } + ], + "AU-NSW": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "100 @ (weightrating>4.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "100 @ (weightrating>4.5)" + } + } + ], + "AU-NT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + } + ], + "AU-QLD": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (trailer); 90 @ (articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (trailer); 90 @ (articulated)" + } + } + ], + "AU-SA": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + } + ], + "AU-TAS": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "80" + } + } + ], + "AU-VIC": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)", + "maxspeed:hgv:conditional": "100 @ (articulated AND weightrating>22)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12)", + "maxspeed:hgv:conditional": "100 @ (articulated AND weightrating>22)" + } + } + ], + "AU-WA": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12); 100 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "100 @ (weightrating>5)", + "maxspeed:conditional": "100 @ (weightrating>12); 100 @ (trailer)" + } + } + ], + "AW": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + } + ], + "AZ": [ + { + "name": "service road", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:truck_bus": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:goods": "90", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:goods": "90", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:coach": "90", + "maxspeed:goods": "110", + "maxspeed:hgv": "90", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "50", + "minspeed": "50" + } + } + ], + "BA": [ + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70", + "minspeed": "40" + } + } + ], + "BB": [ + { + "tags": { + "maxspeed": "60", + "maxspeed:bus": "50", + "maxspeed:coach": "50", + "maxspeed:conditional": "50 @ (weightrating>3); 30 @ (trailer)", + "maxspeed:hgv": "50", + "maxspeed:minibus": "50" + } + } + ], + "BE-BRU": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30" + } + }, + { + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural single carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + } + ], + "BE-VLG": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural single carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + }, + { + "name": "Flanders:Jaagpad", + "tags": { + "maxspeed": "30" + } + } + ], + "BE-WAL": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural single carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5)" + } + } + ], + "BF": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>10)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>10)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + } + ], + "BG": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "50", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "50", + "maxspeed:motorcycle": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated)", + "maxspeed:motorcycle": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated)", + "maxspeed:motorcycle": "80" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", + "maxspeed:hazmat": "90", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "90", + "minspeed": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "140", + "maxspeed:bus": "100", + "maxspeed:conditional": "100 @ (weightrating>3.5); 100 @ (trailer)", + "maxspeed:hazmat": "90", + "maxspeed:hgv": "100", + "maxspeed:motorcycle": "100", + "minspeed": "50" + } + } + ], + "BH": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "50", + "maxspeed:goods": "50", + "maxspeed:motorcycle": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:goods": "80", + "maxspeed:motorcycle": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:goods": "80", + "maxspeed:motorcycle": "80" + } + } + ], + "BI": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "40" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:conditional": "50 @ (weightrating>10)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:conditional": "50 @ (weightrating>10)" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "70", + "maxspeed:conditional": "90 @ (weightrating>10)" + } + } + ], + "BJ": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat:conditional": "50 @ (weightrating>12)" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (weightrating>10)" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "90", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (weightrating>10)" + } + }, + { + "name": "Benin: Route nationale", + "tags": { + "maxspeed": "90", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (weightrating>10)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv:conditional": "90 @ (weightrating>10)" + } + } + ], + "BN": [ + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "75 @ (trailer)", + "maxspeed:conditional": "75 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "75 @ (trailer)", + "maxspeed:conditional": "75 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" + } + } + ], + "BO": [ + { + "name": "school zone", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "70" + } + } + ], + "BR": [ + { + "name": "Brasil: Via urbana local", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "Brasil: Via urbana coletora", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "Brasil: Via urbana arterial", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Brasil: Via urbana de tr\u00e2nsito r\u00e1pido", + "tags": { + "maxspeed": "80" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:goods": "100", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "100", + "maxspeed:motorhome": "90" + } + }, + { + "name": "Brasil: Rodovia rural de pista simples", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:goods": "100", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "100", + "maxspeed:motorhome": "90" + } + }, + { + "name": "Brasil: Rodovia rural de pista dupla", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:goods": "110", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "110", + "maxspeed:motorhome": "90" + } + }, + { + "name": "Brasil: Estrada rural", + "tags": { + "maxspeed": "60" + } + } + ], + "BS": [ + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "45 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "45 mph" + } + } + ], + "BS-NP": [ + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "30 mph" + } + } + ], + "BT": [ + { + "name": "urban", + "tags": { + "maxspeed": "30" + } + }, + { + "tags": { + "maxspeed": "50", + "maxspeed:bus": "35", + "maxspeed:goods": "50", + "maxspeed:goods:conditional": "35 @ (weightrating>3)", + "maxspeed:motorcycle": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "35", + "maxspeed:goods": "50", + "maxspeed:goods:conditional": "35 @ (weightrating>3)", + "maxspeed:motorcycle": "50" + } + } + ], + "BW": [ + { + "name": "living street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "60 @ (trailer)", + "maxspeed:school_bus": "80" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "60 @ (trailer)", + "maxspeed:school_bus": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "100 @ (length>10); 100 @ (seats>=15); 80 @ (trailer); 80 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "60 @ (trailer)", + "maxspeed:school_bus": "80" + } + } + ], + "BY": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + } + ], + "CA-AB": [ + { + "name": "playground zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "Alberta: Urban road", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "Alberta: Urban provincial highway", + "tags": { + "maxspeed": "80" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "Alberta: Provincial highway", + "tags": { + "maxspeed": "100" + } + } + ], + "CA-BC": [ + { + "name": "playground zone", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "30 @ (dawn-dusk)" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "30 @ (Mo-Fr 08:00-17:00; PH,SH off)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + } + ], + "CA-MB": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + } + ], + "CA-NB": [ + { + "name": "urban school zone", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "30 @ (07:30-16:00)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "50 @ (07:30-16:00)" + } + } + ], + "CA-NL": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "50 @ (07:00-17:00)" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Newfoundland and Labrador: Trans-Canada highway", + "tags": { + "maxspeed": "100" + } + } + ], + "CA-NS": [ + { + "name": "urban school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "urban public park", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "50" + } + } + ], + "CA-NT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + } + ], + "CA-NU": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + } + ], + "CA-ON": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + } + ], + "CA-PE": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "90 @ (sunset-sunrise)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "90 @ (sunset-sunrise)" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "60 @ (Sep-Jun Mo-Fr 08:00-16:00)" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "80" + } + } + ], + "CA-QC": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "50 @ (Sep-Jun Mo-Fr 08:00-16:00)" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "Quebec: Autoroute", + "tags": { + "maxspeed": "100", + "minspeed": "60" + } + } + ], + "CA-SK": [ + { + "tags": { + "maxspeed": "80" + } + } + ], + "CA-YT": [ + { + "name": "urban playground zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "rural playground zone", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban school zone", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "30 @ (Mo-Fr 08:00-16:30)" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "40 @ (Mo-Fr 08:00-16:30)" + } + }, + { + "tags": { + "maxspeed": "50" + } + } + ], + "CD": [ + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "75" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "75" + } + }, + { + "name": "dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>7.5)", + "minspeed": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "90 @ (weightrating>7.5)", + "minspeed": "60" + } + } + ], + "CF": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + } + ], + "CG": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + } + ], + "CH": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)", + "maxspeed:motorhome": "100", + "minspeed": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated)", + "maxspeed:motorhome": "100", + "minspeed": "80" + } + } + ], + "CI": [ + { + "name": "urban", + "tags": { + "maxspeed": "70", + "maxspeed:bus": "50", + "maxspeed:hgv": "50" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>16)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>16)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>16)" + } + } + ], + "CL": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:hgv": "100", + "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", + "maxspeed:school_bus": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:hgv": "100", + "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", + "maxspeed:school_bus": "90" + } + }, + { + "name": "rural road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:coach": "100", + "maxspeed:hgv": "100", + "maxspeed:hgv:conditional": "90 @ (weightrating>3.86)", + "maxspeed:school_bus": "90" + } + } + ], + "CM": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12); 60 @ (trailerweight>0.75)" + } + } + ], + "CN": [ + { + "name": "urban and without centerline", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "without centerline", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:goods": "100", + "maxspeed:motorcycle": "80", + "maxspeed:motorhome": "100", + "minspeed": "60" + } + } + ], + "CO": [ + { + "name": "residential district", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:goods": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:goods": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "80", + "maxspeed:goods": "80" + } + } + ], + "CR": [ + { + "name": "roundabout", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + } + ], + "CU": [ + { + "name": "service road", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban school zone", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "60", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "60", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "80 @ (trailer)" + } + } + ], + "CV": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hgv": "50", + "maxspeed:hgv:conditional": "40 @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (weightrating>3.5 AND trailer); 80 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:conditional": "100 @ (weightrating>3.5); 100 @ (trailer); 90 @ (weightrating>3.5 AND trailer)", + "maxspeed:goods": "110", + "maxspeed:goods:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (trailer)", + "maxspeed:tricycle": "100", + "minspeed": "60" + } + } + ], + "CW": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "80", + "minspeed": "40" + } + } + ], + "CY": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "50" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "64" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "64" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "80" + } + } + ], + "CZ": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban motorroad", + "tags": { + "maxspeed": "80", + "minspeed": "65" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "80", + "minspeed": "65" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural motorroad with single carriageway", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "minspeed": "80" + } + }, + { + "name": "rural motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "110", + "maxspeed:conditional": "80 @ (weightrating>3.5)", + "minspeed": "80" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "130", + "maxspeed:conditional": "80 @ (weightrating>3.5)", + "minspeed": "80" + } + } + ], + "DE": [ + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "footway", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", + "maxspeed:motorhome": "80", + "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", + "maxspeed:motorhome": "80", + "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed:advisory": "130", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", + "maxspeed:motorhome": "80", + "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" + } + }, + { + "name": "rural road with 2 or more lanes in each direction", + "tags": { + "maxspeed:advisory": "130", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "60 @ (weightrating>7.5); 60 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5); 60 @ (weightrating>7.5)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "60 @ (weightrating>7.5)", + "maxspeed:motorhome": "80", + "maxspeed:motorhome:conditional": "60 @ (weightrating>7.5)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed:advisory": "130", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "60 @ (trailer)", + "maxspeed:coach": "100", + "maxspeed:coach:conditional": "60 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "60 @ (trailers>=2)", + "maxspeed:motorcycle:advisory": "130", + "maxspeed:motorcycle:conditional": "60 @ (trailer)", + "maxspeed:motorhome:advisory": "130", + "maxspeed:motorhome:conditional": "80 @ (trailer)", + "minspeed": "60" + } + } + ], + "DK": [ + { + "name": "living street", + "tags": { + "maxspeed": "15" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "130", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:hgv": "80", + "minspeed": "50" + } + } + ], + "DO": [ + { + "name": "school zone", + "tags": { + "maxspeed": "35", + "maxspeed:conditional": "25 @ (06:00-18:00)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35" + } + }, + { + "tags": { + "maxspeed": "60", + "maxspeed:bus": "50", + "maxspeed:conditional": "50 @ (weightcapacity>2); 50 @ (emptyweight>2)", + "maxspeed:hazmat": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "50", + "maxspeed:conditional": "50 @ (weightcapacity>2); 50 @ (emptyweight>2)", + "maxspeed:hazmat": "50" + } + } + ], + "EC": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "40" + } + }, + { + "name": "Ecuador: Via perimetral", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "50 @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "50 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:hgv": "70", + "maxspeed:hgv:conditional": "50 @ (trailer)" + } + } + ], + "EE": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:truck_bus": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:truck_bus": "60" + } + } + ], + "EG": [ + { + "name": "residential district", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "60 @ (articulated); 60 @ (trailer)", + "maxspeed:goods": "70" + } + }, + { + "name": "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0633\u0631\u064a\u0639\u0629 \u0623\u0648 \u0627\u0644\u0631\u0626\u064a\u0633\u064a\u0629 \u0627\u0644\u062a\u0649 \u062a\u0631\u0628\u0637 \u0627\u0644\u0645\u062d\u0627\u0641\u0638\u0627\u062a \u060c \u0648\u0627\u0644\u062a\u0649 \u062a\u062a\u0628\u0639 \u0627\u0644\u0645\u062d\u0644\u064a\u0627\u062a \u0623\u0648 \u0627\u0644\u0647\u064a\u0626\u0629 \u0627\u0644\u0639\u0627\u0645\u0629 \u0644\u0644\u0637\u0631\u0642 \u0648\u0627\u0644\u0643\u0628\u0627\u0631\u0649\"", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "60 @ (articulated); 60 @ (trailer)", + "maxspeed:goods": "70" + } + }, + { + "name": "Egypt: \"\u0627\u0644\u0637\u0631\u0642 \u0627\u0644\u0635\u062d\u0631\u0627\u0648\u064a\u0629\"", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:conditional": "70 @ (articulated); 70 @ (trailer)", + "maxspeed:goods": "80" + } + } + ], + "ES": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban road without sidewalk", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "unpaved road", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "80", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70", + "minspeed": "60" + } + }, + { + "name": "urban road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "40" + } + }, + { + "name": "Spain: Traves\u00eda", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "40" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30", + "maxspeed:tricycle": "70" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (trailer); 80 @ (articulated)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (trailer); 80 @ (articulated)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80", + "maxspeed:tricycle": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "80", + "maxspeed:conditional": "90 @ (trailer); 90 @ (articulated)", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "90", + "maxspeed:minibus": "100", + "maxspeed:motorhome:conditional": "90 @ (weightrating>3.5)", + "maxspeed:school_bus": "90", + "maxspeed:tricycle": "70", + "minspeed": "60" + } + } + ], + "FI": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:coach:conditional": "80 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "100 @ (trailer)", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:coach:conditional": "80 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "100 @ (trailer)", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:coach:conditional": "80 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:hgv": "80", + "maxspeed:motorcycle:conditional": "100 @ (trailer)", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + } + ], + "FJ": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", + "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", + "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer); 50 @ (trailerweight>2.035)", + "maxspeed:goods:conditional": "60 @ (weight>2.035); 50 @ (weight>2.035 AND trailer); 60 @ (weight>2.035 AND trailerweight>2.035); 40 @ (weight>3.050 AND trailer); 60 @ (weight>3.050 AND trailerweight>1.525); 25 @ (trailers>=2)" + } + } + ], + "FM": [ + { + "tags": { + "maxspeed": "25 mph" + } + } + ], + "FM-KSA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "25 mph" + } + } + ], + "FM-PNI": [ + { + "tags": { + "maxspeed": "25 mph" + } + } + ], + "FM-TRK": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph", + "maxspeed:conditional": "15 mph @ (Mo-Fr 08:00-15:30)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "25 mph" + } + } + ], + "FM-YAP": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph", + "maxspeed:conditional": "15 mph @ (Mo-Fr 08:00-15:30)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "25 mph" + } + } + ], + "FR": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hazmat": "70", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv": "70" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "rural road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (wet); 60 @ (trailer AND weightrating>12); 60 @ (articulated AND weightrating>12)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "100 @ (weightrating>3.5); 90 @ (weightrating>10)", + "maxspeed:conditional": "100 @ (wet); 90 @ (weightrating>3.5); 80 @ (weightrating>12)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "110 @ (weightrating>3.5); 90 @ (weightrating>10)", + "maxspeed:coach": "100", + "maxspeed:conditional": "110 @ (wet); 90 @ (weightrating>3.5)", + "maxspeed:hazmat:conditional": "80 @ (weightrating>12)", + "maxspeed:hgv": "90" + } + } + ], + "GA": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + } + ], + "GB": [ + { + "name": "United Kingdom: restricted road", + "tags": { + "maxspeed": "30 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "name": "dual carriageway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "60 mph", + "maxspeed:conditional": "60 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "60 mph", + "maxspeed:motorhome": "70 mph", + "maxspeed:motorhome:conditional": "60 mph @ (emptyweight>3.05)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "70 mph", + "maxspeed:bus:conditional": "60 mph @ (length>12)", + "maxspeed:conditional": "60 mph @ (trailer); 40 mph @ (trailers>=2)", + "maxspeed:hgv": "70 mph", + "maxspeed:hgv:conditional": "60 mph @ (weightrating>7.5); 60 mph @ (articulated)", + "maxspeed:motorhome": "70 mph" + } + } + ], + "GB-SCT": [ + { + "name": "United Kingdom: restricted road", + "tags": { + "maxspeed": "30 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "50 mph", + "maxspeed:conditional": "50 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (emptyweight>3.05)" + } + }, + { + "name": "dual carriageway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "60 mph", + "maxspeed:conditional": "60 mph @ (trailer); 20 mph @ (trailers>=2)", + "maxspeed:hgv": "60 mph", + "maxspeed:hgv:conditional": "50 mph @ (weightrating>7.5)", + "maxspeed:motorhome": "70 mph", + "maxspeed:motorhome:conditional": "60 mph @ (emptyweight>3.05)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "70 mph", + "maxspeed:bus:conditional": "60 mph @ (length>12)", + "maxspeed:conditional": "60 mph @ (trailer); 40 mph @ (trailers>=2)", + "maxspeed:hgv": "70 mph", + "maxspeed:hgv:conditional": "60 mph @ (weightrating>7.5); 60 mph @ (articulated)", + "maxspeed:motorhome": "70 mph" + } + } + ], + "GD": [ + { + "name": "urban", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "35 mph", + "maxspeed:goods": "35 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "35 mph", + "maxspeed:goods": "35 mph" + } + } + ], + "GE": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer); 70 @ (trailerweight>0.75); 80 @ (weightrating>3.5 AND trailerweight>0.75)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:truck_bus": "60" + } + } + ], + "GG": [ + { + "tags": { + "maxspeed": "35 mph", + "maxspeed:conditional": "25 mph @ (articulated); 25 mph @ (emptyweight>2); 20 mph @ (trailer)", + "maxspeed:motorhome": "20 mph" + } + } + ], + "GH": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:hgv": "75", + "maxspeed:hgv:conditional": "80 @ (empty)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:hgv": "75", + "maxspeed:hgv:conditional": "80 @ (empty)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:hgv": "75", + "maxspeed:hgv:conditional": "80 @ (empty)" + } + } + ], + "GQ": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + } + ], + "GR": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:truck_bus": "40" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "90 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "85 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (articulated); 70 @ (trailer)", + "maxspeed:motorcycle": "110", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer); 90 @ (trailerweight>0.75)", + "maxspeed:goods": "100", + "maxspeed:goods:conditional": "85 @ (trailer); 80 @ (trailerweight>0.75)", + "maxspeed:hgv": "85", + "maxspeed:hgv:conditional": "80 @ (articulated); 80 @ (trailer)", + "maxspeed:motorcycle": "130", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "50" + } + } + ], + "GT": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30", + "maxspeed:conditional": "20 @ (trailer); 20 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: V\u00eda residencial", + "tags": { + "maxspeed": "30", + "maxspeed:conditional": "20 @ (trailer); 20 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: V\u00eda local", + "tags": { + "maxspeed": "40", + "maxspeed:conditional": "30 @ (trailer); 30 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: Camino", + "tags": { + "maxspeed": "40", + "maxspeed:conditional": "30 @ (trailer); 30 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: Arteria secundaria", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "40 @ (trailer); 40 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: Arteria principal", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: V\u00eda r\u00e1pida urbana", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "70 @ (trailer); 70 @ (weightrating>3.5)", + "minspeed": "60" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "minspeed": "60" + } + }, + { + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: Carretera secundaria", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: Carretera principal", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "60 @ (trailer); 60 @ (weightrating>3.5)" + } + }, + { + "name": "Guatemala: V\u00eda r\u00e1pida", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "70 @ (trailer); 70 @ (weightrating>3.5)", + "minspeed": "60" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "minspeed": "60" + } + } + ], + "GU": [ + { + "tags": { + "maxspeed": "45 mph" + } + } + ], + "GY": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "40 mph", + "maxspeed:hgv": "40 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "40 mph", + "maxspeed:hgv": "40 mph" + } + }, + { + "name": "Guyana: Timehri Field / Linden Highway", + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "40 mph", + "maxspeed:conditional": "40 mph @ (emptyweight>1.270)", + "maxspeed:hgv": "40 mph" + } + } + ], + "HK": [ + { + "tags": { + "maxspeed": "50", + "maxspeed:bus": "70", + "maxspeed:hgv:conditional": "70 @ (weightrating>5.5)", + "maxspeed:minibus": "80" + } + } + ], + "HR": [ + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80" + } + }, + { + "name": "Croatia:brza cesta", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80", + "minspeed": "60" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer); 80 @ (weightrating>3.5)", + "maxspeed:school_bus": "80", + "minspeed": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:conditional": "90 @ (trailer); 90 @ (weightrating>3.5)", + "maxspeed:school_bus": "80", + "minspeed": "80" + } + } + ], + "HT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + } + ], + "HU": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "70", + "maxspeed:hgv": "70", + "maxspeed:tricycle": "40" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:tricycle": "40" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:tricycle": "40" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:hgv": "70", + "minspeed": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "80", + "minspeed": "60" + } + } + ], + "ID": [ + { + "name": "residential district", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "80", + "minspeed": "60" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "100", + "minspeed": "60" + } + } + ], + "IE": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "80" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "80" + } + }, + { + "name": "Ireland: National road", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)", + "maxspeed:hgv": "90" + } + } + ], + "IL": [ + { + "name": "living street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "90", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>12)", + "maxspeed:minibus": "110" + } + } + ], + "IM": [ + { + "name": "living street", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:bus": "40 mph", + "maxspeed:conditional": "20 mph @ (trailers>=2); 40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", + "maxspeed:goods": "60 mph", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:minibus": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "40 mph", + "maxspeed:conditional": "40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", + "maxspeed:goods": "60 mph", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:minibus": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60 mph", + "maxspeed:bus": "40 mph", + "maxspeed:conditional": "40 mph @ (trailer); 40 mph @ (weightrating>32.52 AND articulated); 20 mph @ (trailers>=2)", + "maxspeed:goods": "60 mph", + "maxspeed:hgv": "50 mph", + "maxspeed:hgv:conditional": "40 mph @ (weightrating>7.5)", + "maxspeed:minibus": "50 mph", + "maxspeed:motorhome": "60 mph", + "maxspeed:motorhome:conditional": "50 mph @ (weightrating>3.5)" + } + } + ], + "IN": [ + { + "name": "urban", + "tags": { + "maxspeed": "70", + "maxspeed:bus": "60", + "maxspeed:goods": "60", + "maxspeed:motorcycle": "60", + "maxspeed:tricycle": "50" + } + }, + { + "tags": { + "maxspeed": "70", + "maxspeed:bus": "60", + "maxspeed:goods": "60", + "maxspeed:motorcycle": "60", + "maxspeed:tricycle": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70", + "maxspeed:bus": "60", + "maxspeed:goods": "60", + "maxspeed:motorcycle": "60", + "maxspeed:tricycle": "50" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:goods": "80", + "maxspeed:motorcycle": "80", + "maxspeed:tricycle": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:goods": "80", + "maxspeed:motorcycle": "80", + "tricycle": "no" + } + } + ], + "IS": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "parking lot", + "tags": { + "maxspeed": "15" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "90" + } + } + ], + "IT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "30" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "Italy: Strada extraurbana local", + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "Italy: Strada extraurbana secondaria", + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "Italy: Strada extraurbana principale", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "80 @ (weightrating>8); 70 @ (articulated)", + "maxspeed:conditional": "90 @ (wet); 80 @ (weightrating>3.5); 70 @ (weightrating>12)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>12); 70 @ (trailer); 70 @ (articulated)", + "maxspeed:truck_bus:conditional": "70 @ (weightrating>5)" + } + }, + { + "name": "Italy: Autostrada", + "tags": { + "maxspeed": "130", + "maxspeed:bus:conditional": "100 @ (weightrating>8); 80 @ (articulated)", + "maxspeed:conditional": "110 @ (wet); 100 @ (weightrating>3.5); 100 @ (weightrating>12)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>12); 80 @ (trailer); 80 @ (articulated)", + "maxspeed:truck_bus:conditional": "80 @ (weightrating>5)" + } + } + ], + "JE": [ + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "30 mph", + "maxspeed:conditional": "30 mph @ (trailer)", + "maxspeed:hgv": "30 mph" + } + } + ], + "JP": [ + { + "tags": { + "maxspeed": "60", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (weightrating>8)", + "maxspeed:hgv": "80", + "maxspeed:tricycle": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (weightrating>8)", + "maxspeed:hgv": "80", + "maxspeed:tricycle": "80" + } + } + ], + "KE": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "65 @ (trailer)", + "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "65 @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "65 @ (trailer)", + "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "65 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "65 @ (trailer)", + "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "65 @ (trailer)" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "65 @ (trailer)", + "maxspeed:conditional": "80 @ (articulated); 65 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "65 @ (trailer)" + } + } + ], + "KG": [ + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:coach": "90", + "maxspeed:conditional": "90 @ (weightrating>3.5); 70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60" + } + } + ], + "KH": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:hazmat": "40" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:tricycle": "70" + } + } + ], + "KI": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "40", + "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "30 @ (emptyweight>5); 30 @ (trailer)" + } + } + ], + "KN": [ + { + "name": "urban", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "30 mph", + "maxspeed:goods": "30 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "30 mph", + "maxspeed:goods": "30 mph" + } + } + ], + "KR": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "80", + "minspeed": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "80", + "minspeed": "50" + } + }, + { + "name": "motorroad with 2 lanes in each direction", + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "90", + "minspeed": "50" + } + }, + { + "name": "motorway with 2 lanes in each direction", + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "90", + "minspeed": "50" + } + } + ], + "KZ": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:truck_bus": "60" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "140", + "maxspeed:bus": "90", + "maxspeed:coach": "110", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:goods": "110", + "maxspeed:hgv": "90", + "maxspeed:minibus": "110", + "maxspeed:school_bus": "90", + "maxspeed:truck_bus": "60" + } + } + ], + "LI": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + } + ], + "LR": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "45 mph", + "maxspeed:motorcycle": "40 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "45 mph", + "maxspeed:motorcycle": "40 mph" + } + }, + { + "name": "rural residential district", + "tags": { + "maxspeed": "35 mph" + } + } + ], + "LS": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (articulated AND weightrating>9)", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + } + ], + "LT": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "parking lot", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "110 @ (Nov-Mar); 90 @ (trailer); 90 @ (weightrating>3.5)", + "maxspeed:hgv": "90", + "minspeed": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "110 @ (Nov-Mar); 90 @ (weightrating>3.5); 90 @ (trailer)", + "maxspeed:hgv": "90", + "minspeed": "60" + } + } + ], + "LU": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "75", + "maxspeed:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "75", + "maxspeed:conditional": "75 @ (weightrating>7.5); 75 @ (trailer); 75 @ (articulated)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "75 @ (weightrating>12)", + "maxspeed:conditional": "110 @ (wet); 90 @ (weightrating>7.5); 90 @ (trailer); 90 @ (articulated); 75 @ (weightrating>12)", + "minspeed": "40" + } + } + ], + "LV": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "parking lot", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:truck_bus": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv:conditional": "80 @ (weightrating>7.5)", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv:conditional": "80 @ (weightrating>7.5)", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (Dec-Feb)", + "maxspeed:conditional": "90 @ (Dec-Feb); 90 @ (trailer)", + "maxspeed:hgv": "110", + "maxspeed:hgv:conditional": "90 @ (Dec-Feb); 90 @ (weightrating>7.5)", + "maxspeed:truck_bus": "60" + } + } + ], + "MC": [ + { + "tags": { + "maxspeed": "50" + } + } + ], + "MD": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "5" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "40" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:coach": "90", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "90", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:truck_bus": "60", + "minspeed": "60" + } + } + ], + "MH": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": {} + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph" + } + } + ], + "MK": [ + { + "name": "urban school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "60", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "60", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "60", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "60", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "50", + "minspeed": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "100", + "maxspeed:goods:conditional": "90 @ (weightrating>3.5); 80 @ (weightrating>7.5)", + "maxspeed:hazmat": "60", + "maxspeed:school_bus": "70", + "maxspeed:truck_bus": "50", + "minspeed": "60" + } + } + ], + "MM": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "20 mph @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph" + } + } + ], + "MN": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:school_bus": "50" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "60 @ (trailer)", + "maxspeed:motorcycle": "60", + "maxspeed:school_bus": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "60 @ (trailer)", + "maxspeed:motorcycle": "60", + "maxspeed:school_bus": "50" + } + }, + { + "name": "Mongolia: \u0442\u0443\u0443\u0448 \u0437\u0430\u043c\u0434", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "50" + } + } + ], + "MO": [ + { + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "50 @ (trailer); 50 @ (weightrating>3.5); 50 @ (seats>=10)", + "maxspeed:tricycle": "50" + } + } + ], + "MS": [ + { + "name": "urban", + "tags": { + "maxspeed": "20 mph", + "maxspeed:bus": "15 mph", + "maxspeed:goods": "15 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "25 mph", + "maxspeed:goods": "25 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph", + "maxspeed:bus": "25 mph", + "maxspeed:goods": "25 mph" + } + } + ], + "MT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "40", + "maxspeed:goods": "40", + "maxspeed:goods:conditional": "30 @ (weightrating>3)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:goods": "60", + "maxspeed:goods:conditional": "40 @ (weightrating>3)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:goods": "60", + "maxspeed:goods:conditional": "40 @ (weightrating>3)" + } + } + ], + "MU": [ + { + "name": "Mauritius: C road", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Mauritius: B road", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Mauritius: A road", + "tags": { + "maxspeed": "80", + "maxspeed:conditional": "60 @ (weightrating>3.5); 60 @ (trailer)", + "maxspeed:goods": "60", + "maxspeed:motorcycle": "60" + } + }, + { + "name": "Mauritius: M road", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:conditional": "70 @ (weightrating>3.5); 60 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:motorcycle": "80" + } + } + ], + "MW": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "Malawi: rural highway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (seats>=28); 80 @ (articulated)", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + } + ], + "MX": [ + { + "name": "Mexico: zona de hospital, asilo, albergue o casa hogar", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "Mexico: zona o entorno escolar en una v\u00eda secundaria o calle terciaria", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "Mexico: zona o entorno escolar", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "Mexico: calle secundaria o calle terciaria", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "Mexico: avenida primaria sin acceso controlado", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "Mexico: carril central de una avenida de acceso controlado", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "Mexico: carretera o autopista de jurisdicci\u00f3n federal", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "110", + "maxspeed:bus:conditional": "95 @ (seats>=30); 95 @ (wheels>=6)", + "maxspeed:goods": "80" + } + } + ], + "MZ": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:motorcycle": "50" + } + }, + { + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "100", + "maxspeed:motorcycle": "90", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "100", + "maxspeed:motorcycle": "90", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70" + } + } + ], + "NA": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "100", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:goods:conditional": "80 @ (weightrating>9)" + } + } + ], + "NE": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:goods:conditional": "90 @ (weightrating>9)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "90", + "maxspeed:goods:conditional": "90 @ (weightrating>9)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + }, + { + "name": "Niger: national road", + "tags": { + "maxspeed": "90", + "maxspeed:goods:conditional": "90 @ (weightrating>9)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + }, + { + "name": "Niger: autoroute", + "tags": { + "maxspeed": "110", + "maxspeed:goods:conditional": "90 @ (weightrating>9)", + "maxspeed:hazmat:conditional": "60 @ (weightrating>12)" + } + } + ], + "NG": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hgv": "45" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:hgv": "50", + "maxspeed:motorcycle": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:hgv": "50", + "maxspeed:motorcycle": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:hgv": "60" + } + } + ], + "NI": [ + { + "name": "urban", + "tags": { + "maxspeed": "45" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Nicaragua: Carretera", + "tags": { + "maxspeed": "100" + } + } + ], + "NL": [ + { + "name": "living street", + "tags": { + "maxspeed": "15" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:conditional": "90 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", + "minspeed": "50" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:conditional": "90 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "80", + "maxspeed:motorhome:conditional": "80 @ (weightrating>3.5)", + "minspeed": "60" + } + } + ], + "NL-BQ1": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + } + ], + "NL-BQ2": [ + { + "name": "urban", + "tags": { + "maxspeed": "20" + } + }, + { + "tags": { + "maxspeed": "40" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40" + } + } + ], + "NL-BQ3": [ + { + "name": "urban", + "tags": { + "maxspeed": "30" + } + }, + { + "tags": { + "maxspeed": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50" + } + } + ], + "NO": [ + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "80 @ (weightrating>3.5)", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (weightrating>7.5); 80 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weightrating>3.5)", + "maxspeed:hgv": "80" + } + } + ], + "NP": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "hilly road", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "50", + "maxspeed:goods": "50", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "50" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:goods": "70", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:goods": "70", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "50" + } + } + ], + "NR": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "tags": { + "maxspeed": "50" + } + } + ], + "NZ": [ + { + "name": "living street", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", + "maxspeed:school_bus:conditional": "80 @ (weightrating>2)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:conditional": "90 @ (weightrating>3.5); 90 @ (trailer)", + "maxspeed:school_bus:conditional": "80 @ (weightrating>2)" + } + } + ], + "PA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "40", + "maxspeed:hazmat": "40" + } + }, + { + "name": "Panama: Avenida", + "tags": { + "maxspeed": "60", + "maxspeed:hazmat": "40" + } + }, + { + "name": "Panama: Avenida de dos carriles", + "tags": { + "maxspeed": "80", + "maxspeed:hazmat": "80", + "maxspeed:lanes": "80|50" + } + }, + { + "name": "Panama: Carretera multicarril en zona urbana", + "tags": { + "maxspeed": "80", + "maxspeed:hazmat": "80", + "maxspeed:lanes": "80|60|50" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:hazmat": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:hazmat": "80" + } + } + ], + "PE": [ + { + "name": "school zone", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Per\u00fa: Avenida", + "tags": { + "maxspeed": "60" + } + }, + { + "name": "Per\u00fa: V\u00eda Expresa", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "Per\u00fa: Carretera", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:motorhome": "90", + "maxspeed:school_bus": "70" + } + } + ], + "PG": [ + { + "name": "school zone", + "tags": { + "maxspeed": "25" + } + }, + { + "name": "playground zone", + "tags": { + "maxspeed": "25" + } + }, + { + "name": "village", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "100" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100" + } + } + ], + "PH": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "Philippines: Barangay road", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30" + } + }, + { + "tags": { + "maxspeed": "40", + "maxspeed:bus": "30", + "maxspeed:hgv": "30", + "maxspeed:tricycle": "30" + } + }, + { + "name": "Philippines: Provincial road", + "tags": { + "maxspeed": "40", + "maxspeed:bus": "30", + "maxspeed:hgv": "30", + "maxspeed:tricycle": "30" + } + }, + { + "name": "Philippines: Through street", + "tags": { + "maxspeed": "40", + "maxspeed:bus": "30", + "maxspeed:hgv": "30", + "maxspeed:tricycle": "30" + } + }, + { + "name": "Philippines: National road", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "50", + "maxspeed:hgv": "50", + "maxspeed:tricycle": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "100", + "minspeed": "60" + } + } + ], + "PK": [ + { + "name": "urban unpaved road", + "tags": { + "maxspeed": "40", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "minspeed": "50" + } + }, + { + "name": "urban road with 4 or more lanes in each direction", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "urban road with 3 lanes in each direction", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "urban road with 2 lanes in each direction", + "tags": { + "maxspeed": "70", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "55", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "tags": { + "maxspeed": "70", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural road with 2 lanes in each direction", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural road with 3 lanes in each direction", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural road with 4 or more lanes in each direction", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:conditional": "50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "maxspeed:tricycle": "30" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "70", + "maxspeed:bus:conditional": "65 @ (seats>=15)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "50 @ (weightrating>2.268); 25 @ (trailer); 25 @ (weightrating>10.2)", + "maxspeed:motorcycle": "70", + "minspeed": "65" + } + } + ], + "PL": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:conditional": "70 @ (weightrating>3.5); 70 @ (trailer)" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" + } + }, + { + "name": "motorroad with single carriageway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" + } + }, + { + "name": "motorroad with dual carriageway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "140", + "maxspeed:bus": "80", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (trailer)" + } + } + ], + "PN": [ + { + "tags": { + "maxspeed": "30 mph" + } + } + ], + "PR": [ + { + "name": "urban school zone", + "tags": { + "maxspeed:bus": "15 mph", + "maxspeed:conditional": "15 mph @ (06:00-19:00)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph", + "maxspeed:bus": "15 mph", + "maxspeed:conditional": "15 mph @ (weightrating>10000 lb)", + "maxspeed:hazmat": "15 mph" + } + }, + { + "tags": { + "maxspeed": "45 mph", + "maxspeed:bus": "35 mph", + "maxspeed:conditional": "35 mph @ (weightrating>10000 lb)", + "maxspeed:hazmat": "30 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "45 mph", + "maxspeed:bus": "35 mph", + "maxspeed:conditional": "35 mph @ (weightrating>10000 lb)", + "maxspeed:hazmat": "30 mph" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed:bus": "15 mph", + "maxspeed:conditional": "25 mph @ (06:00-19:00); 15 mph @ (weightrating>10000 lb)", + "maxspeed:hazmat": "30 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:bus": "55 mph", + "maxspeed:conditional": "55 mph @ (weightrating>10000 lb)", + "maxspeed:hazmat": "30 mph" + } + } + ], + "PS": [ + { + "name": "Palestine: \u0634\u0627\u0631\u0639\u0645\u062e\u062a\u0644", + "tags": { + "maxspeed": "25" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "100 @ (seats>=12)", + "maxspeed:goods:conditional": "90 @ (weightrating>12)" + } + } + ], + "PT": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hgv:conditional": "40 @ (trailer)" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "80" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "90" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "110", + "maxspeed:goods:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (trailer)", + "maxspeed:tricycle": "100", + "minspeed": "50" + } + } + ], + "PY": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "40", + "maxspeed:hazmat": "40", + "maxspeed:hgv": "40", + "maxspeed:motorcycle": "40" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:hazmat": "90", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:hazmat": "90", + "maxspeed:hgv": "90", + "maxspeed:motorcycle": "90" + } + } + ], + "RO": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hazmat": "40" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 70 @ (weightrating>7.5); 60 @ (weightrating>7.5 AND trailer); 60 @ (weightrating>7.5 AND articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer); 70 @ (articulated); 70 @ (weightrating>7.5); 60 @ (weightrating>7.5 AND trailer); 60 @ (weightrating>7.5 AND articulated)" + } + }, + { + "name": "Romania: drumurile expres sau pe cele na\u021bionale europene", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (trailer); 80 @ (articulated); 80 @ (weightrating>7.5); 70 @ (weightrating>7.5 AND trailer); 70 @ (weightrating>7.5 AND articulated)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "110", + "maxspeed:bus:conditional": "100 @ (trailer)", + "maxspeed:conditional": "120 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "110", + "maxspeed:hgv:conditional": "100 @ (trailer); 100 @ (articulated); 90 @ (weightrating>7.5); 80 @ (weightrating>7.5 AND trailer); 80 @ (weightrating>7.5 AND articulated)", + "minspeed": "50" + } + } + ], + "RS": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (articulated)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (weightrating>7.5); 70 @ (trailer)", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (articulated)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:hgv": "100", + "maxspeed:hgv:conditional": "90 @ (weightrating>7.5); 90 @ (trailer)", + "maxspeed:school_bus": "90", + "minspeed": "50" + } + } + ], + "RU": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "90", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:hgv": "70", + "maxspeed:minibus": "90", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:coach": "90", + "maxspeed:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:minibus": "90", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "60" + } + } + ], + "RW": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:goods": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:goods": "60" + } + } + ], + "SA": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:hgv": "30" + } + }, + { + "tags": { + "maxspeed": "120", + "maxspeed:hgv": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "120", + "maxspeed:hgv": "70" + } + } + ], + "SD": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + } + ], + "SE": [ + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "walk" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus:conditional": "60 @ (articulated)", + "maxspeed:coach": "100", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (articulated)" + } + }, + { + "tags": { + "maxspeed": "70", + "maxspeed:bus:conditional": "60 @ (articulated)", + "maxspeed:coach": "100", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (articulated)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70", + "maxspeed:bus:conditional": "60 @ (articulated)", + "maxspeed:coach": "100", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (articulated)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (articulated)", + "maxspeed:coach": "100", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (articulated)", + "minspeed": "40" + } + } + ], + "SG": [ + { + "name": "school zone", + "tags": { + "maxspeed": "40" + } + }, + { + "name": "Singapore: silver zone", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "50" + } + } + ], + "SH": [ + { + "tags": { + "maxspeed": "30 mph" + } + } + ], + "SI": [ + { + "name": "living street", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "10" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:truck_bus": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (weightrating>3.5 AND trailer); 70 @ (articulated)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 80 @ (caravan); 70 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "trunk", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (articulated); 80 @ (trailer)", + "maxspeed:coach": "100", + "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5); 70 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "90 @ (weightrating>3.5); 80 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", + "maxspeed:truck_bus": "50" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "80 @ (articulated); 80 @ (trailer)", + "maxspeed:coach": "100", + "maxspeed:conditional": "100 @ (trailer); 80 @ (weightrating>3.5); 70 @ (weightrating>3.5 AND trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "90 @ (weightrating>3.5); 80 @ (trailer); 80 @ (weightrating>3.5 AND trailer)", + "maxspeed:truck_bus": "50", + "minspeed": "60" + } + } + ], + "SK": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "cycle street", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "130", + "maxspeed:bus:conditional": "10090 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (trailerweight>0.75)", + "maxspeed:hgv": "90", + "minspeed": "80" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus:conditional": "10090 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (trailerweight>0.75)", + "maxspeed:hgv": "90", + "minspeed": "80" + } + } + ], + "SN": [ + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "70 @ (seats>=15)", + "maxspeed:conditional": "85 @ (trailerweight>0.75)", + "maxspeed:hgv": "85", + "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)" + } + }, + { + "name": "Senegal: National road", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "70 @ (seats>=15)", + "maxspeed:conditional": "85 @ (trailerweight>0.75)", + "maxspeed:hgv": "85", + "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)", + "maxspeed:motorcycle": "100" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "70 @ (seats>=15)", + "maxspeed:conditional": "85 @ (trailerweight>0.75)", + "maxspeed:hgv": "85", + "maxspeed:hgv:conditional": "75 @ (weightrating>10); 65 @ (weightrating>16); 50 @ (weightrating>22)", + "maxspeed:motorcycle": "100" + } + } + ], + "SS": [ + { + "name": "urban", + "tags": { + "maxspeed": "45" + } + } + ], + "SV": [ + { + "name": "roundabout", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "40", + "maxspeed:hgv": "40" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:hgv": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:hgv": "70" + } + } + ], + "TD": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:conditional": "40 @ (weightrating>3.5)" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)", + "maxspeed:conditional": "90 @ (weightrating>3.5); 60 @ (weightrating>12.5); 60 @ (trailerweight>0.75)" + } + } + ], + "TH": [ + { + "name": "urban", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "45 @ (trailer)", + "maxspeed:goods:conditional": "60 @ (weight>2.2)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "60", + "maxspeed:tricycle": "45" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "55 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weight>2.2)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "70", + "maxspeed:tricycle": "55" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:conditional": "55 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weight>2.2)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "90", + "maxspeed:school_bus": "70", + "maxspeed:tricycle": "55" + } + }, + { + "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e02\u0e19\u0e4d\u0e32\u0e19", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "45 @ (trailer)", + "maxspeed:goods:conditional": "60 @ (weight>2.2)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "60", + "maxspeed:tricycle": "45" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods:conditional": "90 @ (weight>2.2)", + "maxspeed:minibus": "100", + "maxspeed:motorcycle": "100", + "maxspeed:school_bus": "80", + "maxspeed:tricycle": "65" + } + }, + { + "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 on bridge or in tunnel", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods:conditional": "80 @ (weight>2.2)", + "maxspeed:minibus": "100", + "maxspeed:school_bus": "80", + "motorcycle": "no", + "tricycle": "no" + } + }, + { + "name": "Thailand: \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29 not on bridge or in tunnel", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods:conditional": "90 @ (weight>2.2)", + "maxspeed:minibus": "110", + "maxspeed:school_bus": "90", + "motorcycle": "no", + "tricycle": "no" + } + }, + { + "name": "Thailand: motorway that is not a \u0e17\u0e4d\u0e32\u0e07\u0e1e\u0e34\u0e40\u0e28\u0e29", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "90", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods:conditional": "90 @ (weight>2.2)", + "maxspeed:minibus": "100", + "maxspeed:motorcycle": "110", + "maxspeed:school_bus": "80", + "maxspeed:tricycle": "65" + } + } + ], + "TL": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "70 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "80 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "110", + "maxspeed:goods:conditional": "90 @ (trailer)", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "80 @ (trailer)", + "minspeed": "40" + } + } + ], + "TN": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:tricycle": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>10); 70 @ (weightrating>12); 60 @ (weightrating>19)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12); 60 @ (weightrating>19)", + "maxspeed:hazmat:conditional": "70 @ (weightrating>3.5); 60 @ (weightrating>12)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (weightrating>10); 70 @ (weightrating>12); 60 @ (weightrating>19)", + "maxspeed:conditional": "80 @ (weightrating>3.5); 70 @ (weightrating>12); 60 @ (weightrating>19)", + "maxspeed:hazmat:conditional": "70 @ (weightrating>3.5); 60 @ (weightrating>12)", + "maxspeed:tricycle": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "110", + "maxspeed:bus:conditional": "100 @ (weightrating>10); 90 @ (weightrating>12); 80 @ (weightrating>19)", + "maxspeed:conditional": "100 @ (weightrating>3.5); 90 @ (weightrating>12); 80 @ (weightrating>19)", + "maxspeed:hazmat:conditional": "90 @ (weightrating>3.5); 80 @ (weightrating>12)", + "maxspeed:tricycle": "70", + "minspeed": "60" + } + } + ], + "TR": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:conditional": "40 @ (trailer)", + "maxspeed:hazmat": "30" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70", + "maxspeed:tricycle:conditional": "60 @ (trailer)", + "minspeed": "15" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:bus:conditional": "70 @ (trailer)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hazmat": "50", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:motorcycle:conditional": "70 @ (trailer)", + "maxspeed:tricycle": "70", + "maxspeed:tricycle:conditional": "60 @ (trailer)", + "minspeed": "15" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "90", + "maxspeed:bus:conditional": "80 @ (trailer)", + "maxspeed:conditional": "100 @ (trailer)", + "maxspeed:goods": "100", + "maxspeed:goods:conditional": "90 @ (trailer)", + "maxspeed:hazmat": "60", + "maxspeed:hgv": "85", + "maxspeed:hgv:conditional": "75 @ (trailer)", + "maxspeed:motorcycle": "90", + "maxspeed:motorcycle:conditional": "80 @ (trailer)", + "maxspeed:tricycle": "80", + "maxspeed:tricycle:conditional": "70 @ (trailer)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "90 @ (trailer)", + "maxspeed:conditional": "110 @ (trailer)", + "maxspeed:goods": "110", + "maxspeed:goods:conditional": "100 @ (trailer)", + "maxspeed:hazmat": "70", + "maxspeed:hgv": "90", + "maxspeed:hgv:conditional": "0 @ (trailer)", + "maxspeed:motorcycle": "100", + "maxspeed:motorcycle:conditional": "90 @ (trailer)", + "maxspeed:tricycle": "80", + "maxspeed:tricycle:conditional": "70 @ (trailer)", + "minspeed": "40" + } + } + ], + "TT": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "65", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods": "65" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "65", + "maxspeed:conditional": "65 @ (trailer)", + "maxspeed:goods": "65" + } + } + ], + "TV": [ + { + "tags": { + "maxspeed": "40" + } + } + ], + "TW": [ + { + "name": "without centerline", + "tags": { + "maxspeed": "30" + } + }, + { + "tags": { + "maxspeed": "50" + } + } + ], + "TZ": [ + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "110", + "maxspeed:conditional": "80 @ (weightrating>3.5)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "110", + "maxspeed:conditional": "80 @ (weightrating>3.5)" + } + } + ], + "UA": [ + { + "name": "living street", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "pedestrian zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "90 @ (seats>=18)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus:conditional": "90 @ (seats>=18)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "dual carriageway", + "tags": { + "maxspeed": "110", + "maxspeed:bus:conditional": "90 @ (seats>=18)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus:conditional": "90 @ (seats>=18)", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "80", + "maxspeed:truck_bus": "60" + } + } + ], + "UG": [ + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer)", + "maxspeed:goods": "60" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer)", + "maxspeed:goods": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:conditional": "60 @ (trailer)", + "maxspeed:goods": "60" + } + }, + { + "name": "rural road with asphalt or concrete surface", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods": "80" + } + } + ], + "US": [ + { + "tags": {} + } + ], + "US-AK": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + } + ], + "US-AL": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "tags": { + "maxspeed": "45 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "45 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "35 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "name": "Alabama: rural highway", + "tags": { + "maxspeed": "55 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "name": "Alabama: rural highway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph", + "maxspeed:hazmat": "55 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:hazmat": "55 mph" + } + } + ], + "US-AR": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" + } + }, + { + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "55 mph @ (caravan)", + "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "55 mph @ (caravan)", + "maxspeed:hgv:conditional": "50 mph @ (weightcapacity>1.5 st); 30 mph @ (weightrating>64000 lb)" + } + } + ], + "US-AZ": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "65 mph" + } + } + ], + "US-CA": [ + { + "name": "California: alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (trailer)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (trailer)" + } + }, + { + "name": "rural road with 1 lane in each direction", + "tags": { + "maxspeed": "65 mph", + "maxspeed:bus:conditional": "55 mph @ (articulated); 55 mph @ (agricultural)", + "maxspeed:conditional": "55 mph @ (trailer)", + "maxspeed:hazmat": "55 mph", + "maxspeed:hgv:conditional": "55 mph @ (articulated)", + "maxspeed:school_bus": "55 mph" + } + } + ], + "US-CO": [ + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural winding mountain road", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "rural open mountain road", + "tags": { + "maxspeed": "40 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "expressway", + "tags": { + "maxspeed": "65 mph" + } + } + ], + "US-CT": [ + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:school_bus": "50 mph" + } + } + ], + "US-DC": [ + { + "name": "playground zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "District of Columbia: alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "tags": { + "maxspeed": "20 mph", + "maxspeed:horse": "8 mph" + } + } + ], + "US-DE": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural single carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-FL": [ + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-GA": [ + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "35 mph", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed:school_bus": "55 mph" + } + } + ], + "US-HI": [ + { + "tags": {} + } + ], + "US-IA": [ + { + "name": "business district", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "Iowa: suburban district", + "tags": { + "maxspeed": "45 mph" + } + }, + { + "name": "Iowa: Institutional road", + "tags": { + "maxspeed": "45 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "50 mph @ (sunset-sunrise)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-ID": [ + { + "name": "residential district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "Idaho: State highway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "75 mph", + "maxspeed:hgv:conditional": "65 mph @ (axles>=5 AND weightrating>26000 lb)" + } + } + ], + "US-IL": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:bus": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:bus": "55 mph" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph", + "maxspeed:bus": "65 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "70 mph" + } + } + ], + "US-IN": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "55 mph @ (length>85 ft)", + "maxspeed:school_bus": "40 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (length>85 ft)", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (length>85 ft)", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "60 mph", + "maxspeed:conditional": "55 mph @ (length>85 ft)", + "maxspeed:school_bus": "40 mph" + } + }, + { + "name": "rural US interstate highway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:bus": "70 mph", + "maxspeed:conditional": "65 mph @ (weightrating>50000 lb); 55 mph @ (length>85 ft)", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>50000 lb); 55 mph @ (length>85 ft)", + "maxspeed:school_bus": "60 mph" + } + } + ], + "US-KS": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "55 mph @ (caravan)" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "55 mph @ (caravan)" + } + }, + { + "name": "Kansas: state highway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "55 mph @ (caravan)" + } + }, + { + "name": "dual carriageway", + "tags": { + "maxspeed": "75 mph", + "maxspeed:conditional": "55 mph @ (caravan)" + } + } + ], + "US-KY": [ + { + "name": "parking lot", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "65 mph" + } + } + ], + "US-LA": [ + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "70 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-MA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "40 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "40 mph" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "50 mph" + } + } + ], + "US-MD": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "single carriageway in residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "dual carriageway in residential district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-ME": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "tags": { + "maxspeed": "45 mph", + "maxspeed:school_bus": "45 mph" + } + } + ], + "US-MI": [ + { + "name": "trailer park", + "tags": { + "maxspeed": "15 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:hgv:conditional": "65 mph @ (weightrating>10000 lb); 65 mph @ (trailer); 65 mph @ (articulated)", + "maxspeed:school_bus": "65 mph", + "minspeed": "55 mph" + } + } + ], + "US-MN": [ + { + "name": "alley", + "tags": { + "maxspeed": "10 mph" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban US interstate highway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "expressway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural US interstate highway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-MO": [ + { + "name": "lettered road with 2 lanes", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "urban expressway", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "urban US interstate highway", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "rural expressway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "70 mph" + } + }, + { + "name": "rural US interstate highway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-MS": [ + { + "tags": { + "maxspeed": "65 mph", + "minspeed": "30 mph" + } + } + ], + "US-MT": [ + { + "name": "urban", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv": "65 mph" + } + }, + { + "tags": { + "maxspeed": "70 mph", + "maxspeed:conditional": "65 mph @ ((sunset+01:30)-(sunrise-01:30))", + "maxspeed:hgv": "65 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70 mph", + "maxspeed:conditional": "65 mph @ ((sunset+01:30)-(sunrise-01:30))", + "maxspeed:hgv": "65 mph" + } + }, + { + "name": "urban US interstate highway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:hgv": "65 mph" + } + }, + { + "name": "rural US interstate highway", + "tags": { + "maxspeed": "80 mph", + "maxspeed:hgv": "70 mph" + } + } + ], + "US-NC": [ + { + "name": "urban", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-ND": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "70 mph" + } + }, + { + "name": "US interstate highway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "75 mph" + } + } + ], + "US-NE": [ + { + "name": "business district", + "tags": { + "maxspeed": "20 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "50 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "Nebraska: State highway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "Nebraska: State expressway or super-two highway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph", + "maxspeed:conditional": "50 mph @ (caravan)", + "minspeed": "40 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "75 mph", + "maxspeed:conditional": "50 mph @ (caravan)" + } + } + ], + "US-NH": [ + { + "name": "urban school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "urban residential district", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural residential district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "45 mph @ (caravan)", + "minspeed": "45 mph" + } + } + ], + "US-NJ": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "urban business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "suburban business district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "suburban residential district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph" + } + } + ], + "US-NM": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-NV": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "tags": { + "maxspeed": "80 mph", + "maxspeed:school_bus": "55 mph" + } + } + ], + "US-NY": [ + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>10000 lb)" + } + } + ], + "US-OH": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban motorway", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "urban expressway without traffic lights", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "Ohio: Urban state route", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "rural expressway", + "tags": { + "maxspeed": "60 mph" + } + }, + { + "name": "rural expressway without traffic lights", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural motorway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-OK": [ + { + "name": "school zone", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "55 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "35 mph", + "maxspeed:school_bus": "55 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed:school_bus": "65 mph" + } + } + ], + "US-OR": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "school zone", + "tags": { + "maxspeed": "25 mph", + "maxspeed:conditional": "20 mph @ (07:00-17:00)" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "20 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + }, + { + "name": "public park", + "tags": { + "maxspeed": "25 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "65 mph", + "maxspeed:hgv:conditional": "55 mph @ (weightrating>8000 lb AND articulated); 55 mph @ (weightrating>10000 lb)", + "maxspeed:school_bus": "55 mph", + "maxspeed:truck_bus": "55 mph" + } + } + ], + "US-PA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban road which is not numbered", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-RI": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "50 mph", + "maxspeed:conditional": "45 mph @ ((sunset+00:30)-(sunrise-00:30))" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph", + "maxspeed:conditional": "45 mph @ ((sunset+00:30)-(sunrise-00:30))" + } + } + ], + "US-SC": [ + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "40 mph" + } + } + ], + "US-SD": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "65 mph", + "maxspeed:conditional": "45 mph @ (caravan)" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "80 mph", + "maxspeed:conditional": "55 mph @ (caravan)", + "minspeed": "40 mph" + } + } + ], + "US-TN": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "Tennessee: Rural state road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "70 mph", + "minspeed": "55 mph" + } + }, + { + "name": "US interstate highway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "70 mph", + "minspeed": "55 mph" + } + } + ], + "US-TX": [ + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph", + "maxspeed:school_bus": "50 mph" + } + }, + { + "tags": { + "maxspeed": "60 mph", + "maxspeed:school_bus": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "60 mph", + "maxspeed:school_bus": "50 mph" + } + }, + { + "name": "rural numbered road", + "tags": { + "maxspeed": "70 mph", + "maxspeed:school_bus": "60 mph" + } + } + ], + "US-UT": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-VA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "35 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "rural unpaved road", + "tags": { + "maxspeed": "35 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "Virginia: Rustic road", + "tags": { + "maxspeed": "35 mph", + "maxspeed:school_bus": "45 mph" + } + }, + { + "name": "Virginia: State primary highway", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "60 mph" + } + }, + { + "name": "rural road with 2 or more lanes in each direction", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "60 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "60 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "55 mph", + "maxspeed:school_bus": "60 mph" + } + } + ], + "US-VT": [ + { + "tags": { + "maxspeed": "50 mph" + } + } + ], + "US-WA": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "50 mph" + } + }, + { + "name": "Washington: State highway", + "tags": { + "maxspeed": "60 mph", + "maxspeed:conditional": "60 mph @ (weightrating>10000 lb); 60 mph @ (trailer); 60 mph @ (articulated)" + } + } + ], + "US-WI": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "alley", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "Wisconsin: semiurban or outlying district", + "tags": { + "maxspeed": "35 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "Wisconsin: Rustic road", + "tags": { + "maxspeed": "40 mph" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "70 mph" + } + } + ], + "US-WV": [ + { + "name": "school zone", + "tags": { + "maxspeed": "15 mph" + } + }, + { + "name": "business district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "55 mph" + } + } + ], + "US-WY": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20 mph" + } + }, + { + "name": "residential district", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "30 mph" + } + }, + { + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "65 mph" + } + }, + { + "name": "rural road without asphalt or concrete surface", + "tags": { + "maxspeed": "55 mph" + } + }, + { + "name": "Wyoming: State highway", + "tags": { + "maxspeed": "70 mph" + } + }, + { + "name": "US interstate highway", + "tags": { + "maxspeed": "75 mph" + } + } + ], + "UY": [ + { + "name": "school zone", + "tags": { + "maxspeed": "20" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "45" + } + }, + { + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:coach": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "80", + "maxspeed:coach": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:hazmat": "80", + "maxspeed:hgv": "80" + } + } + ], + "UZ": [ + { + "name": "urban residential district", + "tags": { + "maxspeed": "30" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "70" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:coach": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "60" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:coach": "90", + "maxspeed:conditional": "80 @ (trailer)", + "maxspeed:goods:conditional": "70 @ (trailer)", + "maxspeed:hgv": "80", + "maxspeed:hgv:conditional": "70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "80", + "maxspeed:school_bus": "60", + "maxspeed:truck_bus": "60" + } + } + ], + "VE": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "70", + "maxspeed:conditional": "50 @ (sunset-sunrise)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "70", + "maxspeed:conditional": "50 @ (sunset-sunrise)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "90", + "maxspeed:lanes": "90|70" + } + } + ], + "VI": [ + { + "name": "urban", + "tags": { + "maxspeed": "20 mph", + "maxspeed:bus": "10 mph", + "maxspeed:hgv": "10 mph" + } + }, + { + "tags": { + "maxspeed": "35 mph", + "maxspeed:bus": "30 mph", + "maxspeed:hgv": "30 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "35 mph", + "maxspeed:bus": "30 mph", + "maxspeed:hgv": "30 mph" + } + } + ], + "VN": [ + { + "name": "urban single carriageway", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "urban one-way road with 1 lane", + "tags": { + "maxspeed": "50" + } + }, + { + "name": "urban dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "60" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:coach": "70", + "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "60" + } + }, + { + "name": "rural single carriageway", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:coach": "70", + "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "60" + } + }, + { + "name": "rural single carriageway", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:coach": "70", + "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "60" + } + }, + { + "name": "rural one-way road with 1 lane", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "60", + "maxspeed:coach": "70", + "maxspeed:conditional": "70 @ (seats>=31); 50 @ (trailer)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (weightcapacity>3.5); 60 @ (trailer)", + "maxspeed:minibus": "80", + "maxspeed:motorcycle": "60" + } + }, + { + "name": "rural dual carriageway with 2 or more lanes in each direction", + "tags": { + "maxspeed": "90", + "maxspeed:bus": "70", + "maxspeed:coach": "80", + "maxspeed:conditional": "80 @ (seats>=31); 60 @ (trailer)", + "maxspeed:goods": "90", + "maxspeed:goods:conditional": "80 @ (weightcapacity>3.5); 70 @ (trailer)", + "maxspeed:minibus": "90", + "maxspeed:motorcycle": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "minspeed": "50" + } + } + ], + "WS": [ + { + "name": "urban", + "tags": { + "maxspeed": "25 mph" + } + }, + { + "tags": { + "maxspeed": "35 mph" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "35 mph" + } + } + ], + "XK": [ + { + "name": "urban school zone", + "tags": { + "maxspeed:conditional": "30 @ (07:00-20:00)" + } + }, + { + "name": "urban", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "80", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "name": "rural school zone", + "tags": { + "maxspeed": "50", + "maxspeed:bus": "80", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "name": "rural dual carriageway", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "80", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "80", + "maxspeed:goods:conditional": "70 @ (trailer); 70 @ (weightrating>7.5)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorroad", + "tags": { + "maxspeed": "110", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (weightrating>12)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "85", + "maxspeed:goods:conditional": "80 @ (trailer); 70 @ (weightrating>12)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "130", + "maxspeed:bus": "100", + "maxspeed:bus:conditional": "80 @ (weightrating>12)", + "maxspeed:conditional": "80 @ (caravan)", + "maxspeed:goods": "85", + "maxspeed:goods:conditional": "80 @ (trailer); 70 @ (weightrating>12)", + "maxspeed:hazmat": "70", + "maxspeed:school_bus": "70", + "minspeed": "60" + } + } + ], + "ZA": [ + { + "name": "urban", + "tags": { + "maxspeed": "60", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)" + } + }, + { + "name": "motorway", + "tags": { + "maxspeed": "120", + "maxspeed:bus": "100", + "maxspeed:hgv:conditional": "80 @ (weightrating>9)", + "tricycle": "no" + } + } + ], + "ZM": [ + { + "name": "urban", + "tags": { + "maxspeed": "40" + } + }, + { + "tags": { + "maxspeed": "100", + "maxspeed:hgv": "80" + } + }, + { + "name": "rural", + "tags": { + "maxspeed": "100", + "maxspeed:hgv": "80" + } + } ] -} + }, + "warnings": [ + "the Turkish Republic of Northern Cyprus: Unknown country / subdivision", + "the Turkish Republic of Northern Cyprus: Unknown country / subdivision", + "the Turkish Republic of Northern Cyprus: Unknown country / subdivision", + "the Turkish Republic of Northern Cyprus: Unknown country / subdivision", + "Transnistria: Unknown country / subdivision", + "Transnistria: Unknown country / subdivision", + "Transnistria: Unknown country / subdivision", + "Transnistria: Unknown country / subdivision", + "Transnistria: Unknown country / subdivision", + "Somaliland: Unknown country / subdivision", + "Somaliland: Unknown country / subdivision", + "Somaliland: Unknown country / subdivision", + "Sint Maarten: Unknown country / subdivision", + "Sint Maarten: Unknown country / subdivision", + "Sint Maarten: Unknown country / subdivision", + "MN: Unable to map 'Mongolia: \u0442\u0443\u0443\u0448 \u0437\u0430\u043c\u0434'", + "NP: Unable to map 'hilly road'", + "PG: Unable to map 'village'", + "US-CO: Unable to map 'rural winding mountain road'", + "US-CO: Unable to map 'rural open mountain road'", + "US-IA: Unable to map 'Iowa: Institutional road'", + "US-NJ: Unable to map 'suburban business district'", + "US-NJ: Unable to map 'suburban residential district'", + "US-OH: Unable to map 'urban expressway without traffic lights'", + "US-OH: Unable to map 'rural expressway without traffic lights'", + "US-WI: Unable to map 'Wisconsin: semiurban or outlying district'" + ] +} \ No newline at end of file diff --git a/core/src/main/resources/com/graphhopper/util/ar.txt b/core/src/main/resources/com/graphhopper/util/ar.txt index 0928f49d1ff..e48bb107c1d 100644 --- a/core/src/main/resources/com/graphhopper/util/ar.txt +++ b/core/src/main/resources/com/graphhopper/util/ar.txt @@ -1,7 +1,7 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=استمر -continue_onto=استمر في %1$s +continue_onto= finish=النهاية keep_left=احفظ الشمال keep_right=احفظ اليمين @@ -26,29 +26,36 @@ m_abbr=متر mi_abbr=ميل ft_abbr=قدم road=طريق -off_bike=انزل الدراجة +off_bike=انزل من الدراجة cycleway=طريق دائري way=طريق small_way=طريق صغير -paved=مرصوف -unpaved=غير مرصوف +paved=طريق معبد او طريق مرصوف +unpaved=طريق غير معبد او طريق غير مرصوف stopover=توقف %1$s roundabout_enter=أدخل الدوران roundabout_exit=في الدوران ، أتخذ مخرج %1$s roundabout_exit_onto=في الدوران ، أتخذ مخرج %1$s من خلال %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s اجمالى صعود web.total_descend=%1$s اجمالى نزول web.way_contains_ford=انتبه ، هناك مخاضة على الطريق web.way_contains_ferry=استخدم العبارة -web.way_contains_private=طريق خاص web.way_contains_toll=طريق برسم عبور web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=انتقل الى %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=اعادة تنشيط web.server_status=الحالة web.zoom_in=تكبير web.zoom_out=تصغير +web.zoom_to_route= web.drag_to_reorder=اسحب لاعادة الترتيب web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=شاحنة web.staticlink=رابط ثابت web.motorcycle=دراجة نارية web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ast.txt b/core/src/main/resources/com/graphhopper/util/ast.txt index 01843e4d458..eddbd46045a 100644 --- a/core/src/main/resources/com/graphhopper/util/ast.txt +++ b/core/src/main/resources/com/graphhopper/util/ast.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=sigue continue_onto=sigue per %1$s @@ -36,19 +36,26 @@ stopover=pasando per %1$s roundabout_enter=Entra na rotonda roundabout_exit=Na rotonda, toma la salida %1$s roundabout_exit_onto=Na rotonda, toma la salida %1$s haza %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s d'ascensu total web.total_descend=%1$s de descensu total web.way_contains_ford=hai un vau nel camín web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=cambia a %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Refrescar la páxina web.server_status=Estáu web.zoom_in=Averar web.zoom_out=Alloñar +web.zoom_to_route= web.drag_to_reorder=Abasnar pa cambiar l'orde web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Camión web.staticlink=enllaz estáticu web.motorcycle=Motocicleta web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/az.txt b/core/src/main/resources/com/graphhopper/util/az.txt index fd174c7d325..99e69062f35 100644 --- a/core/src/main/resources/com/graphhopper/util/az.txt +++ b/core/src/main/resources/com/graphhopper/util/az.txt @@ -1,11 +1,11 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=Davam edin continue_onto=Davam edin - %1$s finish=Məntəqəyə çatdınız keep_left=Sol tərəfi tutaraq hərəkət edin keep_right=Sağ tərəfi tutaraq hərəkət edin -turn_onto=%1$s - %2$s +turn_onto=%1$s - %2$s turn_left=Sola dönün turn_right=Sağa dönün turn_slight_left=Yavaşca sola dönün @@ -36,21 +36,28 @@ stopover=dayanacaq %1$s roundabout_enter=dairəvi yola dönün roundabout_exit=%1$s çıxışdan çıxın roundabout_exit_onto=%1$s çıxışdan %2$s çıxın +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s yüksəliş web.total_descend=%1$s eniş web.way_contains_ford=Yolda keçid var web.way_contains_ferry=Bərəyə minin -web.way_contains_private=özəl yol web.way_contains_toll=ödənişli yol -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_crosses_border=Marşrut dövlət sərhədini keçir +web.way_contains=Маrşrut %1$s ehtiva edir +web.way_contains_restrictions=Giriş məhdudiyyətləri ola bilən marşrut +web.tracks=asfaltlanmamış torpaq yollar +web.steps=pilləkənlər +web.footways=piyada yolları +web.steep_sections=dik hissələr +web.private_sections=özəl sahələr +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=Marşrut potensial təhlükəli magistral yolları ehtiva edir +web.get_off_bike_for=Velosipeddən enib, %1$s hərəkət edin pt_transfer_to=%1$s keçin web.start_label=Başlanğıc web.intermediate_label=Ara nöqtə @@ -60,30 +67,34 @@ web.set_intermediate=Ara nöqtə olaraq təyin edin web.set_end=Bitmə məntəqəsi olaraq təyin edin web.center_map=Xəritənin mərkəzini bura təyin edin web.show_coords=Koordinatları göstər -web.query_osm= +web.query_osm=OSM sorğusu web.route=Marşrut -web.add_to_route= +web.add_to_route=Məkan əlavə et web.delete_from_route=Marşrutdan sil -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=İstifadəçi modeli pəncərəsini aç +web.draw_areas_enabled=Xəritədə sahələri çəkmək və dəyişdirmək +web.help_custom_model=Yardım +web.apply_custom_model=Tətbiq et +web.custom_model_enabled=Fərdi Rejim Aktivləşdirildi +web.settings=Tənzimləmələr +web.settings_close=Bağla +web.exclude_motorway_example=Magistral Yolları İzləməyin +web.exclude_disneyland_paris_example=Disneyland Paris-i İzləməyin +web.simple_electric_car_example=Elektrik Avtomobili +web.avoid_tunnels_bridges_example=Körpülər və Tunellərdən Qaçın +web.limit_speed_example=Sürət Limiti +web.cargo_bike_example=Yük Velosipedi +web.prefer_bike_network=Velosiped Marşrutlarını Üstün Tutun +web.exclude_area_example=Ərazini İstisna Et +web.combined_example=Birləşdirilmiş Nümunə +web.examples_custom_model=Nümunələr web.marker=İşarələyici web.gh_offline_info=GraphHopper APİ ilə bağlantı qurulmadı? web.refresh_button=Səhifəni yeniləyin web.server_status=Server statusu web.zoom_in=Yaxınlaşdır web.zoom_out=Uzaqlaşdır +web.zoom_to_route=Bütün marşrutu göstər web.drag_to_reorder=Yol nöqtəsini köçürün web.route_timed_out=Marşrutun hesablanma vaxtı keçdi web.route_request_failed=Marşrut qurulması baş tutmadı @@ -93,13 +104,13 @@ web.searching_location_failed=Məkan axtarış baş tutmadı web.via_hint=vasitəsilə web.from_hint=-dan web.gpx_export_button=GPX ixrac -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=GPX ixrac +web.settings_gpx_export_trk=Track ilə +web.settings_gpx_export_rte=Marşrut ilə +web.settings_gpx_export_wpt=Yol nöqtəsi ilə +web.hide_button=Gizlət +web.details_button=Ətraflı web.to_hint=-dək web.route_info=məsafə - %1$s vaxt - %2$s web.search_button=Axtarış @@ -107,13 +118,13 @@ web.more_button=daha web.pt_route_info=Təxmini çatma vaxtı %1$s transfer - %2$s (%3$s) web.pt_route_info_walking=Piyada olaraq təxmini çatma vaxtı %1$s ( %2$s) web.locations_not_found=Marşrut qurulmağı mümkün deyil. Yer müəyyən edilməyib. -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.search_with_nominatim=Nominatim ilə axtarış +web.powered_by=Təmin edən +web.info=Məlumat +web.feedback=Rəy +web.imprint=İz +web.privacy=Gizlilik +web.terms=Şərtlər web.bike=Velosiped web.racingbike=Yarış velosipedi web.mtb=Dağ velosipedi @@ -125,36 +136,122 @@ web.bus=Avtobus web.truck=Yük maşını web.staticlink=Daimi keçid web.motorcycle=Motosiklet -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= -navigate.accept_risks_after_warning= +web.scooter=Skuter +web.car_settings=Marşrut seçimləri (Avtomobil) +web.car_settings_car=Standart +web.car_settings_car_avoid_ferry=Bərələrdən qaç +web.car_settings_car_avoid_motorway=Magistrallardan qaç +web.car_settings_car_avoid_toll=Ödənişli yollar və bərələrdən qaç +web.truck_settings=Marşrut seçimləri (Yük maşını) +web.truck_settings_truck=Böyük yük maşını +web.truck_settings_small_truck=Çatdırılma furgonu +web.foot_settings=Marşrut seçimləri (Piyada) +web.foot_settings_foot=Standart +web.foot_settings_hike=Gəzinti +web.racingbike_settings=Marşrut seçimləri (Yol velosipedi) +web.racingbike_settings_racingbike=Standart +web.racingbike_settings_ecargobike=E-Yük Velosipedi +web.back_to_map=Geri +web.distance_unit=Məsafələr %1$s ilə göstərilir +web.waiting_for_gps=GPS siqnalı gözlənilir... +web.elevation=Hündürlük +web.slope=Maillik +web.towerslope=Qüllə maililiyi +web.country=Ölkə +web.surface=Səth +web.road_environment=Yol mühiti +web.road_access=Yola giriş +web.road_class=Yol kateqoriyası +web.max_speed=Maks. sürət +web.average_speed=Orta sürət +web.track_type=Yol növü +web.toll=Ödənişli +web.next=Növbəti +web.back=Geri +web.as_start=Başlanğıc nöqtəsi kimi +web.as_destination=Təyinat nöqtəsi kimi +web.poi_removal_words=ərazi, ətraf, burada, içində, yerli, yaxınlıqda, bu +web.poi_nearby=%1$s yaxınlıqda +web.poi_in=%2$s vasitəsilə %1$s +web.poi_airports=hava limanları, hava limanı +web.poi_atm=bankomat, pul +web.poi_banks=banklar, bank +web.poi_bureau_de_change=valyuta mübadiləsi, pul dəyişmə, mübadilə məntəqəsi, mübadilə məntəqəsi +web.poi_bus_stops=avtobus dayanacaqları, avtobus dayanacağı, avtobus +web.poi_bicycle=velosiped mağazası, velosiped təmiri, velosiped dükanı +web.poi_bicycle_rental=velosiped icarəsi, velosiped icarəsi +web.poi_cafe=kafe, kafe, kafe, qəhvəxana, bistro +web.poi_car_rental=avtomobil icarəsi, karşerinq, karşerinq, icarəyə götürülmüş maşınlar, nəqliyyat icarəsi +web.poi_car_repair=avtomobil təmiri, avtomobil təmiri, avtomobil təmiri, avtomobil təmiri +web.poi_charging_station=şarj stansiyaları, enerji doldurma məntəqələri, şarj cihazları, şarj cihazları +web.poi_cinema=kinoteatr, kinoteatr, teatr, kinoteatr, kino film +web.poi_cuisine_american=amerikan mətbəxi, amerikan +web.poi_cuisine_african=afrika mətbəxi, afrikalı +web.poi_cuisine_arab=ərəb mətbəxi, ərəb +web.poi_cuisine_asian=asiyalı mətbəxi, asiyalı +web.poi_cuisine_chinese=çin mətbəxi, çin +web.poi_cuisine_greek=yunan mətbəxi, yunan +web.poi_cuisine_indian=hind mətbəxi, hind +web.poi_cuisine_italian=italyan mətbəxi, italyan +web.poi_cuisine_japanese=yapon mətbəxi, yapon +web.poi_cuisine_mexican=meksika mətbəxi, meksikalı +web.poi_cuisine_polish=polyak mətbəxi, polyak +web.poi_cuisine_russian=rus mətbəxi, rus +web.poi_cuisine_turkish=türk mətbəxi, türk +web.poi_diy=ev təmiri mağazaları, ev təmiri mağazası, ev təmiri mağazası, ev əşyaları, ev əşyaları mağazası, ev əşyaları mağazası, əl işləri, əl işləri, əl işləri mağazası, əl işləri mağazası, taxta materialları +web.poi_dentist=diş həkimi, diş cərrahı +web.poi_doctor=həkimlər, həkim, həkim-mütəxəssis +web.poi_education=təhsil +web.poi_fast_food=fast food, evə aparılacaq yemək +web.poi_food_burger=burger yemək, burger, gamburger yemək, gamburger +web.poi_food_kebab=kabab yemək, kabab +web.poi_food_pizza=pizza yemək, pizza +web.poi_food_sandwich=sendviç yemək, sendviç, tost, tost yemək +web.poi_food_sushi=sushi yemək, sushi +web.poi_food_chicken=toyuq yemək, toyuq +web.poi_gas_station=yanacaq stansiyaları, yanacaq stansiyası, yanacaq stansiyaları, yanacaq stansiyası +web.poi_hospitals=xəstəxanalar, xəstəxana +web.poi_hotels=otellər, otel +web.poi_leisure=əyləncə +web.poi_museums=muzeylər, muzey +web.poi_parking=parking, park yeri +web.poi_parks=parklar, park +web.poi_pharmacies=apteklər, aptek +web.poi_playgrounds=uşaq meydançalari, uşaq meydançası +web.poi_public_transit=ictimai nəqliyyat +web.poi_police=polis +web.poi_post=poçt, poçt ofisi, markalar +web.poi_post_box=poçt qutusu, poçt qutusu, məktub qutusu, məktub qutusu +web.poi_railway_station=dəmiryolu stansiyaları, dəmiryolu stansiyası, qatarlar, qatar +web.poi_recycling=təkrar emal +web.poi_restaurants=restoranlar, restoran, yemək, yeməyə getmək +web.poi_schools=məktəblər, məktəb +web.poi_shopping=mağazalar, dükan, alış-veriş +web.poi_shop_bakery=çörəkxana, çörəkçi +web.poi_shop_butcher=ət dükanı, ət dükanı, ət mağazası, ət bazarı, ətçi +web.poi_super_markets=supermarketlər, supermarket, supermarket +web.poi_swim=üzmək, çimərlik +web.poi_toilets=tualetlər, tualet +web.poi_tourism=turizm +web.poi_townhall=şəhər iclası, şəhər meriyası, bələdiyyə, bələdiyyə binası, şura, şura binası +web.poi_transit_stops=dayanacaq, stansiya, ictimai nəqliyyat, ictimai nəqliyyat +web.poi_viewpoint=baxış nöqtəsi, mənzərə, baxış, baxış nöqtəsi, görünüş +web.poi_water=su +web.poi_wifi=Wi-Fi, WLAN, internet +navigate.accept_risks_after_warning=Mən başa düşürəm və razıyam navigate.for_km=%1$s kilometrdə navigate.for_mi=%1$s mildə -navigate.full_screen_for_navigation= +navigate.full_screen_for_navigation=Tam ekran rejimini aktiv et navigate.in_km_singular=1 kilometrdə navigate.in_km=%1$s kilometrdə navigate.in_m=%1$s metrdə navigate.in_mi_singular=1 mildə navigate.in_mi=%1$s mildə navigate.in_ft=%1$s futda -navigate.reroute= -navigate.start_navigation= +navigate.reroute=marşrutu dəyiş +navigate.start_navigation=Naviqasiya navigate.then=sonra -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= -navigate.warning= +navigate.thenSign=Sonra +navigate.turn_navigation_settings_title=Addım-addım naviqasiya +navigate.vector_tiles_for_navigation=Vektor xəritni istifadə et +navigate.warning=XƏBƏRDARLIQ: Addım-addım naviqasiya funksiyası hələ sınaq mərhələsindədir. Öz riskinizə istifadə edin, yola diqqət yetirin və sürərkən cihazı toxunmayın! diff --git a/core/src/main/resources/com/graphhopper/util/bg.txt b/core/src/main/resources/com/graphhopper/util/bg.txt index 83adda04e8a..f45919902c2 100644 --- a/core/src/main/resources/com/graphhopper/util/bg.txt +++ b/core/src/main/resources/com/graphhopper/util/bg.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=продължете continue_onto=продължете по %1$s @@ -36,19 +36,26 @@ stopover=отправна точка %1$s roundabout_enter=Влезте в кръговото кръстовище roundabout_exit=На кръговото кръстовище вземете изход %1$s roundabout_exit_onto=На кръговото кръстовище вземете изход %1$s по %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s общо изкачване web.total_descend=%1$s общо слизане web.way_contains_ford=Внимание, на пътя има брод web.way_contains_ferry=вземете ферибота -web.way_contains_private=частен път web.way_contains_toll=път с такса web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=сменете на %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Презареждане на страницата web.server_status=Състояние web.zoom_in=Увеличаване web.zoom_out=Намаляване +web.zoom_to_route= web.drag_to_reorder=Влачете за преподреждане web.route_timed_out=Времето за изчисляване на маршрут изтече web.route_request_failed=Заявката за маршрут е неуспешна @@ -126,6 +137,20 @@ web.truck=Камион web.staticlink=Постоянна препратка web.motorcycle=Мотоциклет web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Разбирам и се съгласявам navigate.for_km=за %1$s километра navigate.for_mi=за %1$s мили diff --git a/core/src/main/resources/com/graphhopper/util/bn_BN.txt b/core/src/main/resources/com/graphhopper/util/bn_BN.txt index b850700365b..8c6d8a13533 100644 --- a/core/src/main/resources/com/graphhopper/util/bn_BN.txt +++ b/core/src/main/resources/com/graphhopper/util/bn_BN.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=সোজা যেতে থাকুন continue_onto=%1$s সড়কে সোজা যেতে থাকুন @@ -36,19 +36,26 @@ stopover= roundabout_enter=গোলচক্কর roundabout_exit=গোলচক্কর হতে %1$s এর দিকে যান roundabout_exit_onto=গোলচক্কর হতে %2$s এ যেতে %1$s এর দিকে যান +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll=রুটে টোল আছে web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink= web.motorcycle= web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ca.txt b/core/src/main/resources/com/graphhopper/util/ca.txt index 4d3d4df3a63..7bf11862e61 100644 --- a/core/src/main/resources/com/graphhopper/util/ca.txt +++ b/core/src/main/resources/com/graphhopper/util/ca.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continua continue_onto=continua per %1$s @@ -13,9 +13,9 @@ turn_slight_right=gira lleugerament a la dreta turn_sharp_left=gira just a l'esquerra turn_sharp_right=gira just a la dreta u_turn=fes la volta -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=%1$s i condueix cap a %2$s +toward_destination_ref_only=%1$s cap a %2$s +toward_destination_with_ref=%1$s i agafa %2$s cap a %3$s unknown=instrucció desconeguda «%1$s» via=passant per hour_abbr=h @@ -36,21 +36,28 @@ stopover=passant per %1$s roundabout_enter=Entra a la rotonda roundabout_exit=A la rotonda, agafa la %1$sa sortida roundabout_exit_onto=A la rotonda, agafa la %1$sa sortida cap a %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s de pujada total web.total_descend=%1$s de baixada total web.way_contains_ford=hi ha un gual en el camí web.way_contains_ferry=Agafa el ferri -web.way_contains_private=camí privat web.way_contains_toll=via de peatge -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_crosses_border=La ruta creua la frontera d'un país +web.way_contains=La ruta inclou %1$s +web.way_contains_restrictions=Hi poden haver restriccions d'accés a la ruta +web.tracks=camí de terra +web.steps=escales +web.footways=vorera +web.steep_sections=tram escarpat +web.private_sections=tram privat +web.restricted_sections= +web.challenging_sections=La ruta inclou tram difícil o perillós +web.dangerous_sections= +web.trunk_roads_warn=La ruta inclou carreteres potencialment perilloses +web.get_off_bike_for=Baixeu de la bicicleta i premeu %1$s pt_transfer_to=canvia a %1$s web.start_label=Punt d'inici web.intermediate_label=Punt intermig @@ -60,46 +67,50 @@ web.set_intermediate=Defineix com a punt intermig web.set_end=Defineix com a punt final web.center_map=Centra el mapa web.show_coords=Mostra les coordenades -web.query_osm= +web.query_osm=Consulta OSM web.route=Ruta -web.add_to_route= +web.add_to_route=Afegeix la ubicació web.delete_from_route=Suprimeix el punt de la ruta -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=Obre model personalitzat +web.draw_areas_enabled=Dibuixa i modifica àrees al mapa +web.help_custom_model=Ajuda +web.apply_custom_model=Aplica +web.custom_model_enabled=Model personalitzat actiu +web.settings=Configuració +web.settings_close=Tanca +web.exclude_motorway_example=Exclou autopistes +web.exclude_disneyland_paris_example=Exclou Disneyland Paris +web.simple_electric_car_example=Cotxe elèctric senzill +web.avoid_tunnels_bridges_example=Evita ponts i túnels +web.limit_speed_example=Límit de velocitat +web.cargo_bike_example=Bicicleta de càrrega +web.prefer_bike_network=Prefereix les rutes en bicicleta +web.exclude_area_example=Àrea exclosa +web.combined_example=Exemple combinat +web.examples_custom_model=Exemples web.marker=Marcador web.gh_offline_info=L'API GraphHopper no té connexió ? web.refresh_button=Actualitza la pàgina web.server_status=Estat web.zoom_in=Apropa web.zoom_out=Allunya +web.zoom_to_route=Mostra tota la ruta web.drag_to_reorder=Arrossega per canviar l'ordre web.route_timed_out=S'ha esgotat el temps de càlcul de la ruta web.route_request_failed=Ha fallat la resolució de la ruta. -web.current_location= -web.searching_location= -web.searching_location_failed= +web.current_location=Ubicació actual +web.searching_location=Cerca d'ubicació +web.searching_location_failed=La cerca d'ubicació ha fallat web.via_hint=Passant per web.from_hint=Des de web.gpx_export_button=Exporta a GPX -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=Exportació GPX +web.settings_gpx_export_trk=Amb pista +web.settings_gpx_export_rte=Amb ruta +web.settings_gpx_export_wpt=Amb waypoints +web.hide_button=Amaga +web.details_button=Detall web.to_hint=Cap a web.route_info=%1$s trigaràs %2$s web.search_button=Cerca @@ -107,13 +118,13 @@ web.more_button=més web.pt_route_info=arriba a les %1$s amb %2$s transbordaments (%3$s) web.pt_route_info_walking=arriba caminant a les %1$s (%2$s) web.locations_not_found=No hi ha cap ruta. El destí no es troba dins l'àrea. -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.search_with_nominatim=Busca amb Nominatim +web.powered_by=Powered by +web.info=Info +web.feedback=Feedback +web.imprint=Emprenta +web.privacy=Protecció de dades +web.terms=Termes web.bike=Bicicleta web.racingbike=Bicicleta de curses web.mtb=Bicicleta de muntanya @@ -125,36 +136,122 @@ web.bus=Autobús web.truck=Camió web.staticlink=Enllaç web.motorcycle=Motocicleta -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= +web.scooter=Scooter +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=Enrera +web.distance_unit=Distancies són en %1$s +web.waiting_for_gps=Esperant senyal GPS ... +web.elevation=Elevació web.slope= web.towerslope= -web.country= -web.surface= +web.country=País +web.surface=Superfície web.road_environment= web.road_access= web.road_class= web.max_speed= web.average_speed= web.track_type= -web.toll= +web.toll=Peatge +web.next=Següent +web.back=Enrera +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm=Caixer +web.poi_banks=Banc +web.poi_bureau_de_change= +web.poi_bus_stops=Parada d'autobús +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets=WC +web.poi_tourism=turisme +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water=font d'aigua +web.poi_wifi=wlan, wifi, internet navigate.accept_risks_after_warning= navigate.for_km=per %1$s quilòmetres navigate.for_mi=per %1$s milles -navigate.full_screen_for_navigation= +navigate.full_screen_for_navigation=Pantalla completa navigate.in_km_singular=En un quilòmetre navigate.in_km=En %1$s quilòmetres navigate.in_m=En %1$s metres navigate.in_mi_singular=En una milla navigate.in_mi=En %1$s milles navigate.in_ft=En %1$s peus -navigate.reroute= -navigate.start_navigation= +navigate.reroute=recalculant +navigate.start_navigation=Navegació navigate.then=després -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= -navigate.warning= +navigate.thenSign=Després +navigate.turn_navigation_settings_title=Navegació pas a pas +navigate.vector_tiles_for_navigation=Utlitza cartografia vectorial +navigate.warning=ADVERTÈNCIA: la funció de navegació pas a pas és molt experimental. Utilitzeu-la sota el vostre propi risc, pareu atenció a la carretera i no toqueu el dispositiu mentre conduïu! diff --git a/core/src/main/resources/com/graphhopper/util/cs_CZ.txt b/core/src/main/resources/com/graphhopper/util/cs_CZ.txt index 2d2b142a396..30de6c7c9af 100644 --- a/core/src/main/resources/com/graphhopper/util/cs_CZ.txt +++ b/core/src/main/resources/com/graphhopper/util/cs_CZ.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=pokračujte continue_onto=pokračujte směr %1$s @@ -36,19 +36,26 @@ stopover=průjezdní bod %1$s roundabout_enter=Vjeďte na kruhový objezd roundabout_exit=Na kruhovém objezdu použijte %1$s. výjezd roundabout_exit_onto=Na kruhovém objezdu použijte %1$s. výjezd, směrem na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=celkové stoupání %1$s web.total_descend=celkové klesání %1$s web.way_contains_ford=Trasa obsahuje brody web.way_contains_ferry=Trasa obsahuje přívozy -web.way_contains_private=Trasa obsahuje soukromé cesty web.way_contains_toll=Trasa obsahuje zpoplatněné úseky web.way_crosses_border=Trasa obsahuje překročení hranic web.way_contains=Trasa obsahuje %1$s +web.way_contains_restrictions= web.tracks=nezpevněné cesty web.steps=schody web.footways=stezky pro pěší web.steep_sections=příkré úseky web.private_sections=soukromé úseky +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for=Je třeba sesednout z kola a %1$s jej tlačit pt_transfer_to=přestupte na %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled=Vlastní model aktivní web.settings=Nastavení web.settings_close=Zavřít web.exclude_motorway_example=Vynechat dálnice +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Rychlostní omezení web.cargo_bike_example=Nákladní kolo web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Obnovit stránku web.server_status=Stav web.zoom_in=Přiblížit web.zoom_out=Oddálit +web.zoom_to_route= web.drag_to_reorder=Přetažením změníte pořadí web.route_timed_out=Časový limit výpočtu trasy překročen web.route_request_failed=Trasa nemohla být vypočítána @@ -126,6 +137,20 @@ web.truck=Nákladní automobil web.staticlink=statický odkaz web.motorcycle=Motocykl web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Zpět web.distance_unit=Vzdálenosti jsou uváděny v %1$s web.waiting_for_gps=Čekání na signál GPS… @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Rozumím a souhlasím navigate.for_km=%1$s kilometrů navigate.for_mi=%1$s mil diff --git a/core/src/main/resources/com/graphhopper/util/da_DK.txt b/core/src/main/resources/com/graphhopper/util/da_DK.txt index 2dc346c9245..a9e1eb901e0 100644 --- a/core/src/main/resources/com/graphhopper/util/da_DK.txt +++ b/core/src/main/resources/com/graphhopper/util/da_DK.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=fortsæt continue_onto=fortsæt på %1$s @@ -36,19 +36,26 @@ stopover=delmål %1$s roundabout_enter=Kør ind i rundskørslen roundabout_exit=I rundkørslen, tag udkørsel %1$s roundabout_exit_onto=I rundskørslen, tag udkørsel %1$s ind på %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s samlet stigning web.total_descend=%1$s samlet fald web.way_contains_ford=der er et vadested undervejs web.way_contains_ferry=Rute med færger -web.way_contains_private=Rute med private veje web.way_contains_toll=Rute med betalingsveje web.way_crosses_border=Rute der krydser landegrænser web.way_contains=Rute inkluderer %1$s +web.way_contains_restrictions= web.tracks=Grusveje uden asfalt web.steps=Trapper web.footways=Fortove web.steep_sections=Stejle sektioner web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for=Føreren skal stige af og cyklen skal skubbes %1$s pt_transfer_to=omstigning til %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example=Eksludér motorvej +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Max hastighed web.cargo_bike_example=Ladcykel web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Genindlæs siden web.server_status=Status web.zoom_in=Zoom ind web.zoom_out=Zoom ud +web.zoom_to_route= web.drag_to_reorder=Træk for at ændre rækkefølgen web.route_timed_out=Ruteberegning fejlede web.route_request_failed=Ukendt fejl @@ -126,6 +137,20 @@ web.truck=Lastbil web.staticlink=statisk link web.motorcycle=Motorcykel web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Tilbage web.distance_unit=Distance er i %1$s web.waiting_for_gps=Venter på GPS signal @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=i %1$s kilometer navigate.for_mi=i %1$s mil diff --git a/core/src/main/resources/com/graphhopper/util/de_DE.txt b/core/src/main/resources/com/graphhopper/util/de_DE.txt index b6b45cf37d8..92371ad2d45 100644 --- a/core/src/main/resources/com/graphhopper/util/de_DE.txt +++ b/core/src/main/resources/com/graphhopper/util/de_DE.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=dem Straßenverlauf folgen continue_onto=dem Straßenverlauf von %1$s folgen @@ -33,24 +33,31 @@ small_way=kleiner Weg paved=befestigt unpaved=unbefestigt stopover=Wegpunkt %1$s -roundabout_enter=In den Kreisverkehr einfahren -roundabout_exit=Im Kreisverkehr Ausfahrt %1$s nehmen -roundabout_exit_onto=Im Kreisverkehr Ausfahrt %1$s auf %2$s nehmen +roundabout_enter=in den Kreisverkehr einfahren +roundabout_exit=im Kreisverkehr Ausfahrt %1$s nehmen +roundabout_exit_onto=im Kreisverkehr Ausfahrt %1$s auf %2$s nehmen +roundabout_exit_now=Kreisverkehr verlassen +roundabout_exit_onto_now=Kreisverkehr auf %1$s verlassen +leave_ferry=Fähre verlassen und %1$s +board_ferry=Achtung, auf Fähre umsteigen (%1$s) web.total_ascend=%1$s Gesamtaufstieg web.total_descend=%1$s Gesamtabstieg web.way_contains_ford=Eine Furt muss überquert werden web.way_contains_ferry=Route erfordert Fährennutzung -web.way_contains_private=Route mit Privatwegen web.way_contains_toll=Route mit mautpflichtigen Straßen web.way_crosses_border=Route überquert Landesgrenzen web.way_contains=Auf der Route sind %1$s +web.way_contains_restrictions=Auf der Route gibt es potentielle Zugangsbeschränkungen web.tracks=unbefestigte Feldwege web.steps=Treppen web.footways=Fußwege web.steep_sections=sehr steile Passagen web.private_sections=private Abschnitte +web.restricted_sections=zugangsbeschränkte Abschnitte +web.challenging_sections=Route enthält herausfordernde oder sogar gefährliche Abschnitte. Erfordert Bergsteigererfahrung und -ausrüstung +web.dangerous_sections=Die Route enthält technisch anspruchsvolle und gefährliche Abschnitte, die äußerste Vorsicht verlangen. Erfordert Bergsteigererfahrung und -ausrüstung (Seil, Eispickel, ...) web.trunk_roads_warn=Route beinhaltet potentiell gefährliche Fernstraßen -web.get_off_bike_for=Das Fahrrad muss für %1$s geschoben werden +web.get_off_bike_for=Vom Fahrrad absteigen und für %1$s schieben pt_transfer_to=umsteigen auf %1$s web.start_label=Start web.intermediate_label=Zwischenziel @@ -72,6 +79,9 @@ web.custom_model_enabled=Custom Model aktiv web.settings=Optionen web.settings_close=Schließen web.exclude_motorway_example=Autobahn vermeiden +web.exclude_disneyland_paris_example= +web.simple_electric_car_example=Elektroauto (vereinfacht) +web.avoid_tunnels_bridges_example=Brücken u. Tunnel vermeiden web.limit_speed_example=Max. Geschwindigkeit web.cargo_bike_example=Lastenfahrrad web.prefer_bike_network=Radnetz bevorzugen @@ -84,6 +94,7 @@ web.refresh_button=Lade Seite neu web.server_status=Status web.zoom_in=Vergrössern web.zoom_out=Verkleinern +web.zoom_to_route=Zeige Gesamtroute web.drag_to_reorder=Zum Ändern der Reihenfolge ziehen web.route_timed_out=Zeitlimit für Routenberechnung überschritten web.route_request_failed=Route konnte nicht berechnet werden @@ -126,6 +137,20 @@ web.truck=LKW web.staticlink=Link web.motorcycle=Motorrad web.scooter=Roller +web.car_settings=Routenoptionen (Auto) +web.car_settings_car=Standard +web.car_settings_car_avoid_ferry=Fähren vermeiden +web.car_settings_car_avoid_motorway=Autobahn vermeiden +web.car_settings_car_avoid_toll=Maut und Fähren vermeiden +web.truck_settings=Routenoptionen (LKW) +web.truck_settings_truck=Großer LKW +web.truck_settings_small_truck=Lieferwagen +web.foot_settings=Routenoptionen (zu Fuß) +web.foot_settings_foot=Standard +web.foot_settings_hike=Wandern +web.racingbike_settings=Routenoptionen (Rennrad) +web.racingbike_settings_racingbike=Rennrad +web.racingbike_settings_ecargobike=E-Lastenrad web.back_to_map=Zurück web.distance_unit=Distanzen sind in %1$s web.waiting_for_gps=Warten auf das GPS Signal... @@ -141,6 +166,78 @@ web.max_speed=Max. Geschwindigkeit web.average_speed=Durchsch. Geschwindigkeit web.track_type=Straßenbefestigung web.toll=Maut +web.next=Weiter +web.back=Zurück +web.as_start=Als Start +web.as_destination=Als Ziel +web.poi_removal_words=der, dem, gebiet, in, karte, lokal, lokale, nähe +web.poi_nearby=%1$s in der Nähe +web.poi_in=%1$s in %2$s +web.poi_airports=Flughäfen, Flughafen +web.poi_atm=Geldautomaten, Geldautomat, Geld abheben, Geld, abheben +web.poi_banks=Banken, Bank +web.poi_bureau_de_change=Wechselstube, Geld wechseln, Geld tauschen +web.poi_bus_stops=Bushaltestellen, Bushaltestelle +web.poi_bicycle=Radläden, Fahrradladen, Radladen, Fahrradreparatur, Fahrradwerkstatt +web.poi_bicycle_rental=fahrradverleih, radverleih, fahrrad verleih, rad verleih +web.poi_cafe=café, cafe, Kaffee, Kaffeehaus, bistro +web.poi_car_rental=Autoverleih, Auto leihen, Auto mieten, car sharing, car share +web.poi_car_repair=Autowerkstatt, Auto Werkstatt, Werkstatt, Autoreparatur, Auto Reparatur +web.poi_charging_station=Ladestation, Ladesäulen, Ladesäule, aufladen +web.poi_cinema=Kinos, Kino, Film +web.poi_cuisine_american=amerikanisch essen, amerikanisch +web.poi_cuisine_african=afrikanisch essen, afrikanisch +web.poi_cuisine_arab=arabisch essen, arabisch, araber +web.poi_cuisine_asian=asiatisch essen, asiatisch, asia, asiate +web.poi_cuisine_chinese=chinesisch essen, chinesisch, china +web.poi_cuisine_greek=griechisch essen, griechisch, grieche +web.poi_cuisine_indian=indisch essen, indisch, indisches Essen, inder +web.poi_cuisine_italian=italienisch essen, italienisch, italienisches Essen, italiener +web.poi_cuisine_japanese=japanisch essen, japanisch, japanisches Essen, japaner +web.poi_cuisine_mexican=mexikanisch essen, mexikanisch, mexikanisches Essen, +web.poi_cuisine_polish=polnisch essen, polnisch, polnisches Essen +web.poi_cuisine_russian=russisch essen, russisch, russisches Essen +web.poi_cuisine_turkish=türkisch essen, türkisch, türkisches Essen +web.poi_diy=Baumärkte, Baumarkt, Heimwerkerbedarf, Heimwerker, Heimwerker Bedarf, Baustoffhandel, Baufachhandel, Heimwerkermarkt, Heimwerker Markt +web.poi_dentist=Zahnärzte, Zahnarzt, Zahnärztin +web.poi_doctor=Ärzte, Arzt, Ärztin, Doktor, Doktorin +web.poi_education=Bildung +web.poi_fast_food=Imbiss, fast food +web.poi_food_burger=burger, hamburger +web.poi_food_kebab=kebab, döner +web.poi_food_pizza=pizza +web.poi_food_sandwich=sandwich, toast +web.poi_food_sushi=sushi +web.poi_food_chicken=hühnchen, grillhuhn, huhn +web.poi_gas_station=Tankstellen, Tankstelle +web.poi_hospitals=Krankenhäuser, Krankenhaus +web.poi_hotels=Hotels, Hotel +web.poi_leisure=Freizeit +web.poi_museums=Museen, Museum +web.poi_parking=Parkplätze, Parkplatz +web.poi_parks=Parks, Park +web.poi_pharmacies=Apotheken, Apotheke +web.poi_playgrounds=Spielplätze, Spielplatz +web.poi_public_transit=ÖPNV, Nahverkehr +web.poi_police=Polizei +web.poi_post=Post, Postamt, Poststelle, Briefmarke, Briefmarken +web.poi_post_box=Briefkästen, Briefkasten +web.poi_railway_station=Bahnhöfe, Bahnhof, Züge, Zug +web.poi_recycling=Abfallcontainer, recycling, pappe container, Pappcontainer, Pappecontainer, Glascontainer, Glasabfall +web.poi_restaurants=Restaurants, Restaurant, Gasthof, Gaststätten, Gaststätte, Essen gehen +web.poi_schools=Schulen, Schule +web.poi_shopping=Einkaufen, Einkauf, Laden, shoppen. shopping, shop +web.poi_shop_bakery=Bäckerei, Bäcker, Backwaren +web.poi_shop_butcher=Fleicherei, Fleischer, Fleisch, Fleischwaren, Metzger, Metzgerei +web.poi_super_markets=Supermärkte, Supermarkt, Einkauf, Einkaufsladen, Einkauf Laden, Essen +web.poi_swim=schwimmen, baden, badesee +web.poi_toilets=Toiletten, Toilette, WC +web.poi_tourism=Tourismus, Fremdenverkehr, Touristik, Sehenswürdigkeit, Sehenswürdigkeiten +web.poi_townhall=Rathaus, Meldeamt, Bürgeramt +web.poi_transit_stops=Haltestellen, Haltestelle, ÖPNV, Nahverkehr +web.poi_viewpoint=Aussicht, Aussichtsturm, Aussichtsplattform +web.poi_water=Wasser, Wasserspender +web.poi_wifi=wlan, wifi, internet navigate.accept_risks_after_warning=Ich verstehe und akzeptiere navigate.for_km=für %1$s Kilometer navigate.for_mi=für %1$s Meilen diff --git a/core/src/main/resources/com/graphhopper/util/el.txt b/core/src/main/resources/com/graphhopper/util/el.txt index e16678a39a8..ac47700dc0a 100644 --- a/core/src/main/resources/com/graphhopper/util/el.txt +++ b/core/src/main/resources/com/graphhopper/util/el.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=συνεχίστε continue_onto=συνεχίστε στην %1$s @@ -13,18 +13,18 @@ turn_slight_right=στρίψτε λοξά δεξιά turn_sharp_left=στρίψτε κλειστά αριστερά turn_sharp_right=στρίψτε κλειστά δεξιά u_turn=κάνετε αναστροφή -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=%1$s και οδηγείστε προς %2$s +toward_destination_ref_only=%1$s προς %2$s +toward_destination_with_ref=%1$s και πάρτε τον δρόμο %2$s προς %3$s unknown=άγνωστη οδηγία '%1$s' via=μέσω -hour_abbr=h -day_abbr=d -min_abbr=min -km_abbr=km -m_abbr=m -mi_abbr=mi -ft_abbr=ft +hour_abbr=ω +day_abbr=μ +min_abbr=λεπ +km_abbr=χλμ +m_abbr=μ +mi_abbr=μι +ft_abbr=πδ road=δρόμος off_bike=κατεβείτε από το ποδήλατο cycleway=ποδηλατόδρομος @@ -36,21 +36,28 @@ stopover=σημείο διαδρομής %1$s roundabout_enter=Μπείτε στον κυκλικό κόμβο roundabout_exit=Στον κυκλικό κόμβο βγείτε στην έξοδο %1$s roundabout_exit_onto=Στον κυκλικό κόμβο βγείτε στην έξοδο %1$s στην %2$s +roundabout_exit_now=βγείτε από τον κυκλικό κόμβο +roundabout_exit_onto_now=βγείτε από τον κυκλικό κόμβο σε %1$s +leave_ferry= +board_ferry= web.total_ascend=%1$s συνολική ανάβαση web.total_descend=%1$s συνολική κατάβαση -web.way_contains_ford=υπάρχει πέρασμα στο δρόμο -web.way_contains_ferry=χρησιμοποιήστε το ferry -web.way_contains_private=ιδιωτικός δρόμος -web.way_contains_toll=δρόμος με διόδια -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_contains_ford=Διαδρομή με περάσματα από ποτάμια +web.way_contains_ferry=Διαδρομή με πορθμείο +web.way_contains_toll=Διαδρομή με διόδια +web.way_crosses_border=Διαδρομή διασχίζει σύνορα χώρας +web.way_contains=Διαδρομή περιλαμβάνει %1$s +web.way_contains_restrictions=Διαδρομή με πιθανούς περιορισμούς πρόσβασης +web.tracks=μη ασφαλτοστρωμένοι χωματόδρομοι +web.steps=σκαλοπάτια +web.footways=μονοπάτια +web.steep_sections=απότομα τμήματα +web.private_sections=ιδιωτικά τμήματα +web.restricted_sections=περιορισμένα τμήματα +web.challenging_sections=Διαδρομή περιλαμβάνει απαιτητικά ή επικίνδυνα τμήματα +web.dangerous_sections= +web.trunk_roads_warn=διαδρομή περιλαμβάνει πιθανά επικίνδυνους αυτοκινητρόδρομους ή χειρότερα +web.get_off_bike_for=Πρέπει να κατεβείτε από το ποδήλατο και να σπρώξετε για %1$s pt_transfer_to=αλλάξτε στο %1$s web.start_label=Αφετηρία web.intermediate_label=Ενδιάμεσο σημείο @@ -60,30 +67,34 @@ web.set_intermediate=Ορισμός ενδιάμεσου σημείου web.set_end=Ορισμός προορισμού web.center_map=Κεντράρισμα χάρτη εδώ web.show_coords=Εμφάνιση συντεταγμένων -web.query_osm= +web.query_osm=Ερώτημα OSM web.route=Διαδρομή -web.add_to_route= +web.add_to_route=Προσθήκη τοποθεσίας web.delete_from_route=Αφαίρεση από διαδρομή -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=Άνοιγμα πλαισίου προσαρμοσμένου μοντέλου +web.draw_areas_enabled=Σχεδίαση και τροποποίηση περιοχών στον χάρτη +web.help_custom_model=Βοήθεια +web.apply_custom_model=Εφαρμογή +web.custom_model_enabled=Προσαρμοσμένο Μοντέλο Ενεργό +web.settings=Ρυθμίσεις +web.settings_close=Κλείσιμο +web.exclude_motorway_example=Αποφυγή αυτοκινητόδρομου +web.exclude_disneyland_paris_example=Αποφυγή Disneyland Παρισιού +web.simple_electric_car_example=Απλό ηλεκτρικό αυτοκίνητο +web.avoid_tunnels_bridges_example=Αποφυγή γεφυρών και σηράγγων +web.limit_speed_example=Περιορισμός ταχύτητας +web.cargo_bike_example=Ποδήλατο μεταφοράς +web.prefer_bike_network=Προτίμησε ποδηλατικές διαδρομές +web.exclude_area_example=Αποφυγή περιοχής +web.combined_example=Συνδυαστικό παράδειγμα +web.examples_custom_model=Παραδείγματα web.marker=Σημάδι -web.gh_offline_info=GraphHopper API offline? +web.gh_offline_info=GraphHopper API offline; web.refresh_button=Ανανέωση σελίδας web.server_status=Κατάσταση web.zoom_in=Μεγέθυνση web.zoom_out=Σμίκρυνση +web.zoom_to_route=Μεγέθυνση στη διαδρομή web.drag_to_reorder=Σύρετε για αναδιάταξη web.route_timed_out=Ο υπολογισμός διαδρομής απέτυχε λόγω εξάντλησης χρόνου web.route_request_failed=Ο υπολογισμός διαδρομής απέτυχε @@ -93,27 +104,27 @@ web.searching_location_failed=Η αναζήτηση τοποθεσίας απέ web.via_hint=Μέσω web.from_hint=Αφετηρία web.gpx_export_button=Εξαγωγή GPX -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=Εξαγγωγή GPX +web.settings_gpx_export_trk=Με ίχνος +web.settings_gpx_export_rte=Με διαδρομή +web.settings_gpx_export_wpt=Με σημεία +web.hide_button=Απόκρυψη +web.details_button=Λεπτομέρειες web.to_hint=Προορισμός -web.route_info=%1$s σε %2$s +web.route_info=%1$s θα πάρει %2$s web.search_button=Αναζήτηση web.more_button=περισσότερα web.pt_route_info=φτάνει στις %1$s με %2$s μεταβιβάσεις (%3$s) web.pt_route_info_walking=φτάνει στις %1$s απλά περπατώντας (%2$s) web.locations_not_found=Η δρομολόγηση δεν είναι δυνατή. Οι τοποθεσίες δεν βρέθηκαν στην περιοχή. -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.search_with_nominatim=Αναζήτηση με Nominatim +web.powered_by=Τροφοδοτείται από +web.info=Πληροφορίες +web.feedback=Ανατροφοδότηση +web.imprint=Αποτύπωμα +web.privacy=Απόρρητο +web.terms=Όροι web.bike=Ποδήλατο web.racingbike=Αγωνιστικό ποδήλατο web.mtb=Ποδήλατο βουνού @@ -125,36 +136,122 @@ web.bus=Λεωφορείο web.truck=Φορτηγό web.staticlink=στατική διεύθυνση web.motorcycle=Μοτοσυκλέτα -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= +web.scooter=Σκούτερ +web.car_settings=Επιλογές διαδρομής (Αυτοκίνητο) +web.car_settings_car=Τυπικό +web.car_settings_car_avoid_ferry=Αποφυγή πορθμείων +web.car_settings_car_avoid_motorway=Αποφυγή αυτοκινητόδρομων +web.car_settings_car_avoid_toll=Αποφυγή διοδίων και πορθμείων +web.truck_settings=Επιλογές διαδρομής (Φορτηγό) +web.truck_settings_truck=Μεγάλο φορτηγό +web.truck_settings_small_truck=Βαν μεταφορών +web.foot_settings=Επιλογές διαδρομής (Πεζός) +web.foot_settings_foot=Τυπικό +web.foot_settings_hike=Πεζοπορία +web.racingbike_settings=Επιλογές διαδρομής (Ποδήλατο) +web.racingbike_settings_racingbike=Τυπικό +web.racingbike_settings_ecargobike=Ηλεκτρικό ποδήλατο cargo +web.back_to_map=Πίσω +web.distance_unit=Οι αποστάσεις είναι σε %1$s +web.waiting_for_gps=Αναμονή για σήμα GPS... +web.elevation=Ανύψωση +web.slope=Πλαγιά +web.towerslope=Towerslope +web.country=Χώρα +web.surface=Επιφάνεια +web.road_environment=Περιβάλλον δρόμου +web.road_access=Πρόσβαση δρόμου +web.road_class=Κλάση δρόμου +web.max_speed=Μέγιστη ταχύτητα +web.average_speed=Μέση ταχύτητα +web.track_type=Τύπος δρόμου +web.toll=Διόδια +web.next=Επόμενο +web.back=Πίσω +web.as_start=Ως αφετηρία +web.as_destination=Ως προορισμό +web.poi_removal_words=περιοχή, γύρω, εδώ, μέσα, τοπικά, τριγύρω, δίπλα, κοντινά, αυτό +web.poi_nearby=%1$s κοντινά +web.poi_in=%1$s σε %2$s +web.poi_airports=αεροδρόμιο, αεροδρόμια, αερολιμένας +web.poi_atm=ατμ, λεφτά, χρήματα +web.poi_banks=τράπεζα, τράπεζες +web.poi_bureau_de_change= +web.poi_bus_stops=στάση λεωφορείου, στάση αστικού, λεωφορείο, αστικό, υπεραστικό, στάση υπεραστικού +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe=καφέ, καφετέρια, καφενείο, μπιστρό, μαγαζί καφέ +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station=σταθμός φόρτισης, φορτιστής, φόρτιση +web.poi_cinema=κινηματογράφος, σινεμά +web.poi_cuisine_american=αμερικάνικο φαγητό, αμερικάνικη κουζίνα +web.poi_cuisine_african=αφρικάνικο φαγητό, αφρικάνικη κουζίνα, αφρικάνικο +web.poi_cuisine_arab=αραβικό φαγητό, αραβική κουζίνα +web.poi_cuisine_asian=ασιατικό φαγητό, ασιατική κουζίνα, ασιατικό +web.poi_cuisine_chinese=κινέζικο, κινέζικο φαγητό, κινέζικη κουζίκα +web.poi_cuisine_greek=ελληνικό φαγητό, ελληνική κουζίνα +web.poi_cuisine_indian=ινδικό, ινδικό φαγητό, ινδική κουζίνα +web.poi_cuisine_italian=ιταλικό, ιταλικό φαγητό, ιταλική κουζίνα +web.poi_cuisine_japanese= +web.poi_cuisine_mexican=μεξικάνικο, μεξικάνικο φαγητό, μεξικάνικη κουζίνα +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist=οδοντίατρος, οδοντιατρείο, χειρούργος οδοντίατρος +web.poi_doctor=γιατρός, ιατρός, ιατρείο +web.poi_education=εκπαίδευση, σχολή +web.poi_fast_food=fast food, φαστ φουντ, γρήγορο φαγητό +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza=πίτσα +web.poi_food_sandwich= +web.poi_food_sushi=σούσι +web.poi_food_chicken=κοτόπουλο +web.poi_gas_station=βενζινάδικο, βενζινάδικα +web.poi_hospitals=νοσοκομείο, νοσοκομεία +web.poi_hotels=ξενοδοχείο, ξενοδοχεία +web.poi_leisure=αναψυχή, άνεση, ψυχαγωγία +web.poi_museums=μουσείο, μουσεία +web.poi_parking=πάρκινγκ, γκαράζ, στάθμευση, χώρος στάθμευσης +web.poi_parks=πάρκο, πάρκα, πράσινο +web.poi_pharmacies=φαρμακείο, φαρμακοποιός, φαρμακεία +web.poi_playgrounds=παιδική χαρά, παιδικές χαρές, παιδότοπος +web.poi_public_transit=δημόσια συγκοινωνία, ΜΜΕ +web.poi_police=αστυνομία, ΑΤ +web.poi_post=ταχυδρομείο, ταχυδρομική υπηρεσία, γραμματόσημα +web.poi_post_box= +web.poi_railway_station=σταθμός τρένου, στάση τρένου, τρένο, τρένα, σιδηρόδρομος, σιδηροδρομικός σταθμός, σιδηροδρομική στάση +web.poi_recycling=ανακύκλωση +web.poi_restaurants=φαγητό, φαί, εστιατόριο, ταβέρνα, φαγάδικο +web.poi_schools=σχολείο, σχολεία +web.poi_shopping=μαγαζί, κατάστημα, ψώνια, αγορά +web.poi_shop_bakery=φούρνος, αρτοποιείο +web.poi_shop_butcher=κρεοπώλης, κρεοπωλείο, αγορά κρέατος, κρέας +web.poi_super_markets=σούπερ μάρκετ, υπεραγορά, αγορά +web.poi_swim=κολύμβηση, κολύμπι, μπάνιο +web.poi_toilets=τουαλέτα, τουαλέτες, WC +web.poi_tourism=τουρισμός, τουριστικό, τουριστικά +web.poi_townhall=δημαρχείο, κοινότητα, γραφείο κοινότητας +web.poi_transit_stops=στάση, σταθμός, δημόσια συγκοινωνία, ΜΜΕ +web.poi_viewpoint= +web.poi_water=νερό, βρύση, πηγή +web.poi_wifi=wifi, wlan, internet navigate.accept_risks_after_warning=Καταλαβαίνω και συμφωνώ -navigate.for_km=Για %1$s χιλιόμετρα +navigate.for_km=για %1$s χιλιόμετρα navigate.for_mi=για %1$s μίλια -navigate.full_screen_for_navigation= +navigate.full_screen_for_navigation=Θέσε σε πλήρη οθόνη navigate.in_km_singular=Σε 1 χιλιόμετρο navigate.in_km=Σε %1$s χιλιόμετρα navigate.in_m=Σε %1$s μέτρα -navigate.in_mi_singular=Σε 1 μίλη +navigate.in_mi_singular=Σε 1 μίλι navigate.in_mi=Σε %1$s μίλια navigate.in_ft=Σε %1$s πόδια -navigate.reroute= -navigate.start_navigation= +navigate.reroute=επαναδρομολόγηση +navigate.start_navigation=Πλοήγηση navigate.then=τότε -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= -navigate.warning=ΠΡΟΣΟΧΗ: Αυτή η εφαρμογή είναι πειραματική! Χρησιμοποιήστε την με δική σας ευθύνη! +navigate.thenSign=Τότε +navigate.turn_navigation_settings_title=Πλοήγηγη στροφή-στροφή +navigate.vector_tiles_for_navigation=Χρήση διανυσματικών πλακιδίων +navigate.warning=ΠΡΟΣΟΧΗ: Η πλοήγηση στροφή-στροφή είναι άκρως πειραματική! Χρησιμοποιήστε την με δική σας ευθύνη, προσέξτε το δρόμο και μην αγγίζετε τη συσκευή ενώ οδηγείτε! diff --git a/core/src/main/resources/com/graphhopper/util/en_US.txt b/core/src/main/resources/com/graphhopper/util/en_US.txt index 555efa6dc0d..4fa3d073f65 100644 --- a/core/src/main/resources/com/graphhopper/util/en_US.txt +++ b/core/src/main/resources/com/graphhopper/util/en_US.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continue continue_onto=continue onto %1$s @@ -33,36 +33,43 @@ small_way=small way paved=paved unpaved=unpaved stopover=waypoint %1$s -roundabout_enter=Enter roundabout -roundabout_exit=At roundabout, take exit %1$s -roundabout_exit_onto=At roundabout, take exit %1$s onto %2$s +roundabout_enter=enter roundabout +roundabout_exit=at roundabout, take exit %1$s +roundabout_exit_onto=at roundabout, take exit %1$s onto %2$s +roundabout_exit_now=exit the roundabout +roundabout_exit_onto_now=exit the roundabout onto %1$s +leave_ferry=leave ferry and %1$s +board_ferry=Attention, take ferry (%1$s) web.total_ascend=%1$s total ascent web.total_descend=%1$s total descent web.way_contains_ford=Route includes fords web.way_contains_ferry=Route includes ferries -web.way_contains_private=Route with private roads web.way_contains_toll=Route has tolls web.way_crosses_border=Route crosses a country border web.way_contains=Route includes %1$s +web.way_contains_restrictions=Route with potential access restrictions web.tracks=unpaved dirt roads web.steps=steps web.footways=footways web.steep_sections=steep sections web.private_sections=private sections +web.restricted_sections=restricted sections +web.challenging_sections=Route includes difficult or even dangerous sections. You need mountaineering experience and equipment +web.dangerous_sections=Route includes technically challenging and dangerous sections that requires extreme caution. You need mountaineering experience and equipment (rope, ice axe, ...) web.trunk_roads_warn=Route includes potentially dangerous trunk roads or worse -web.get_off_bike_for=Bike must be dismounted and pushed for %1$s +web.get_off_bike_for=Get off the bike and push for %1$s pt_transfer_to=change to %1$s web.start_label=Start web.intermediate_label=Intermediate web.end_label=End web.set_start=From here -web.set_intermediate=Via point +web.set_intermediate=Via location web.set_end=To here web.center_map=Center map web.show_coords=Show coordinates web.query_osm=Query OSM web.route=Route -web.add_to_route=Add Location +web.add_to_route=Add location web.delete_from_route=Delete from Route web.open_custom_model_box=Open custom model box web.draw_areas_enabled=Draw and modify areas on map @@ -72,6 +79,9 @@ web.custom_model_enabled=Custom Model Active web.settings=Settings web.settings_close=Close web.exclude_motorway_example=Exclude Motorway +web.exclude_disneyland_paris_example=Exclude Disneyland Paris +web.simple_electric_car_example=Simple Electric Car +web.avoid_tunnels_bridges_example=Avoid Bridges & Tunnels web.limit_speed_example=Limit Speed web.cargo_bike_example=Cargo Bike web.prefer_bike_network=Prefer Cycle Routes @@ -84,6 +94,7 @@ web.refresh_button=Refresh page web.server_status=Status web.zoom_in=Zoom in web.zoom_out=Zoom out +web.zoom_to_route=Zoom to route web.drag_to_reorder=Drag to reorder web.route_timed_out=Route calculation timed out web.route_request_failed=Route request failed @@ -115,17 +126,31 @@ web.imprint=Imprint web.privacy=Privacy web.terms=Terms web.bike=Bike -web.racingbike=Racing Bike +web.racingbike=Road Bike web.mtb=MTB web.car=Car -web.foot=Foot -web.hike=Hike +web.foot=Walking +web.hike=Hiking web.small_truck=Small Truck web.bus=Bus web.truck=Truck web.staticlink=static link web.motorcycle=Motorcycle web.scooter=Scooter +web.car_settings=Route options (Car) +web.car_settings_car=Standard +web.car_settings_car_avoid_ferry=Avoid Ferries +web.car_settings_car_avoid_motorway=Avoid Motorways +web.car_settings_car_avoid_toll=Avoid Tolls and Ferries +web.truck_settings=Route options (Truck) +web.truck_settings_truck=Big Truck +web.truck_settings_small_truck=Delivery Van +web.foot_settings=Route options (Walking) +web.foot_settings_foot=Standard +web.foot_settings_hike=Hiking +web.racingbike_settings=Route options (Road Bike) +web.racingbike_settings_racingbike=Standard +web.racingbike_settings_ecargobike=E-Cargo Bike web.back_to_map=Back web.distance_unit=Distances are in %1$s web.waiting_for_gps=Waiting for the GPS signal... @@ -141,6 +166,78 @@ web.max_speed=Max. speed web.average_speed=Avg. speed web.track_type=Track type web.toll=Toll +web.next=Next +web.back=Back +web.as_start=As start +web.as_destination=As destination +web.poi_removal_words=area, around, here, in, local, nearby, this +web.poi_nearby=%1$s nearby +web.poi_in=%1$s in %2$s +web.poi_airports=airports, airport +web.poi_atm=atm, money +web.poi_banks=banks, bank +web.poi_bureau_de_change=bureau de change, money changer, money change, currency exchange +web.poi_bus_stops=bus stops, bus stop, bus +web.poi_bicycle=bicycle shop, bike repair, bike shop +web.poi_bicycle_rental=bicycle rental, bike rental +web.poi_cafe=café, cafe, cafe shop, coffeehouse, bistro +web.poi_car_rental=car rental, car sharing, car share, rental car, vehicle hire +web.poi_car_repair=car repair, auto repair, car service, vehicle maintenance +web.poi_charging_station=charging stations, charging station, charging, charger +web.poi_cinema=cinema, movie theater, theater, picture house, motion pictures +web.poi_cuisine_american=american food, american +web.poi_cuisine_african=african food, african +web.poi_cuisine_arab=arab food, arab +web.poi_cuisine_asian=asian food, asian +web.poi_cuisine_chinese=chinese food, chinese +web.poi_cuisine_greek=greek food, greek +web.poi_cuisine_indian=indian food, indian +web.poi_cuisine_italian=italian food, italian +web.poi_cuisine_japanese=japanese food, japanese +web.poi_cuisine_mexican=mexican food, mexican +web.poi_cuisine_polish=polish food, polish +web.poi_cuisine_russian=russian food, russian +web.poi_cuisine_turkish=turkish food, turkish +web.poi_diy=hardware stores, hardware store, hardware shop, home improvement, home improvement store, home improvement shop, do it yourself, DYI, DYI store, DYI shop, Lumberyard, +web.poi_dentist=dentist, dental surgeon +web.poi_doctor=doctors, doctor, physician +web.poi_education=education +web.poi_fast_food=fast food, take away +web.poi_food_burger=eat burger, burger, eat hamburger, hamburger +web.poi_food_kebab=eat kebab, kebab +web.poi_food_pizza=eat pizza, pizza +web.poi_food_sandwich=eat sandwich, sandwich, toast, eat toast +web.poi_food_sushi=eat sushi, sushi +web.poi_food_chicken=eat chicken, chicken +web.poi_gas_station=gas stations, gas station, petrol stations, petrol station +web.poi_hospitals=hospitals, hospital +web.poi_hotels=hotels, hotel +web.poi_leisure=leisure +web.poi_museums=museums, museum +web.poi_parking=parking, parking place +web.poi_parks=parks, park +web.poi_pharmacies=pharmacies, pharmacy +web.poi_playgrounds=playgrounds, playground +web.poi_public_transit=public transit +web.poi_police=police +web.poi_post=post, postal office, stamps +web.poi_post_box=mailbox, post box, letterbox, letter box +web.poi_railway_station=railway stations, railway station, trains, train +web.poi_recycling=recycling +web.poi_restaurants=restaurants, restaurant, eat, go eat +web.poi_schools=schools, school +web.poi_shopping=shops, shop, shopping +web.poi_shop_bakery=bakery, baker +web.poi_shop_butcher=butcher shop, butcher's shop, butchers shop, meat market, butcher +web.poi_super_markets=super markets, super market, supermarket +web.poi_swim=swim, bathing +web.poi_toilets=toilets, toilet +web.poi_tourism=tourism +web.poi_townhall=town hall, city hall, municipal, municipal building, council, council building +web.poi_transit_stops=stop, station, public transit, public transport +web.poi_viewpoint=viewpoint, view, lookout, vantage point, outlook +web.poi_water=water +web.poi_wifi=wifi, wlan, internet navigate.accept_risks_after_warning=I understand and agree navigate.for_km=for %1$s kilometers navigate.for_mi=for %1$s miles diff --git a/core/src/main/resources/com/graphhopper/util/eo.txt b/core/src/main/resources/com/graphhopper/util/eo.txt index 17eed2bc046..00bcd941595 100644 --- a/core/src/main/resources/com/graphhopper/util/eo.txt +++ b/core/src/main/resources/com/graphhopper/util/eo.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=pluu continue_onto=pluu laŭlonge de %1$s @@ -36,19 +36,26 @@ stopover=%1$s-a haltejo roundabout_enter=Enveturu trafikcirklon roundabout_exit=Ĉe trafikcirklo, elveturu al %1$s roundabout_exit_onto=Ĉe trafikcirklo, elveturu al %1$s‑a vojo al %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s supreniro tute web.total_descend=%1$s malsupreniro tute web.way_contains_ford=travadejo sur la kurso web.way_contains_ferry=enpramiĝu -web.way_contains_private=privata vojo web.way_contains_toll=pegenda vojo web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=transveturiĝu al %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Aktualigi paĝon web.server_status=Stato web.zoom_in=Pligrandigi web.zoom_out=Malgrandigi +web.zoom_to_route= web.drag_to_reorder=Trenu por reordigi web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Kamiono web.staticlink=konstanta ligilo web.motorcycle=Motorciklo web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/es.txt b/core/src/main/resources/com/graphhopper/util/es.txt index 7587184add2..3c404d0edfe 100644 --- a/core/src/main/resources/com/graphhopper/util/es.txt +++ b/core/src/main/resources/com/graphhopper/util/es.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continúa continue_onto=continúa por %1$s @@ -36,19 +36,26 @@ stopover=pasando por %1$s roundabout_enter=Entra en la rotonda roundabout_exit=En la rotonda, toma la %1$sª salida roundabout_exit_onto=En la rotonda, toma la %1$sª salida hacia %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=Ascender %1$s en total web.total_descend=Descender %1$s en total web.way_contains_ford=hay un vado en el camino web.way_contains_ferry=toma el ferry -web.way_contains_private=carretera privada web.way_contains_toll=carretera con peaje web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=cambia a %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Actualizar página web.server_status=Estado web.zoom_in=Acercar web.zoom_out=Alejar +web.zoom_to_route= web.drag_to_reorder=Arrastra para cambiar el orden web.route_timed_out=Se ha agotado el tiempo de cálculo de la ruta web.route_request_failed=Solicitud de ruta fallida @@ -126,6 +137,20 @@ web.truck=Camión web.staticlink=enlace estático web.motorcycle=Motocicleta web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=por %1$s kilómetros navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/fa.txt b/core/src/main/resources/com/graphhopper/util/fa.txt index 5c94a0ade0f..822800bc779 100644 --- a/core/src/main/resources/com/graphhopper/util/fa.txt +++ b/core/src/main/resources/com/graphhopper/util/fa.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=ادامه دهید continue_onto=در %1$s ادامه دهید @@ -13,9 +13,9 @@ turn_slight_right=کمی به راست بپیچید turn_sharp_left=در پیچ تند به چپ بپیچید turn_sharp_right=در پیچ تند به راست بپیچید u_turn=دور بزنید -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=%1$s و به سمت %2$s ادامه دهید +toward_destination_ref_only= %1$s و به %2$s وارد شوید +toward_destination_with_ref=%1$s و از %2$s به سمت %3$s خارج شوید unknown=علامت راهنمایی ناشناخته: '%1$s' via=با گذر از hour_abbr=ساعت @@ -33,22 +33,29 @@ small_way=مسیر باریک paved=سنگفرش/آسفالت شده unpaved=سنگفرش/آسفالت نشده stopover=نقطهٔ بین‌راهی %1$s -roundabout_enter=وارد فلکه شوید -roundabout_exit=در فلکه، به خروجی %1$s بروید -roundabout_exit_onto=در فلکه، از خروجی %1$s به %2$s بروید +roundabout_enter=وارد میدان شوید +roundabout_exit=در میدان، از خروجی %1$s خارج شوید +roundabout_exit_onto=در میدان، از خروجی %1$s به سمت %2$s خارج شوید +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=مجموع صعود %1$s web.total_descend=مجموع نزول %1$s web.way_contains_ford=در طول مسیر گُدار وجود دارد web.way_contains_ferry=این مسیر دارای کشتی است -web.way_contains_private=مسیردارای گذرگاه های شخصی (مالکیت خصوصی) web.way_contains_toll=این مسیر دارای عوارض است web.way_crosses_border=این مسیر از مرز یک کشور عبور میکند web.way_contains=این مسیر دارای %1$s است +web.way_contains_restrictions= web.tracks=هشدار: این مسیر دارای جاده های خاکی و غیر آسفالت است web.steps=پله web.footways= web.steep_sections=هشدار: این مسیر دارای شیب تند رو به بالا و یا رو به پایین است web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=مسیر را به %1$s تغییر دهید @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=تازه‌سازی صفحه web.server_status=وضعیت web.zoom_in=بزرگ‌نمایی web.zoom_out=کوچک‌نمایی +web.zoom_to_route= web.drag_to_reorder=برای مرتب‌سازی جابه‌جا کنید web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=کامیون web.staticlink=پیوند ثابت web.motorcycle=موتورسیکلت web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,20 +166,92 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= navigate.full_screen_for_navigation= -navigate.in_km_singular= -navigate.in_km= -navigate.in_m= -navigate.in_mi_singular= -navigate.in_mi= -navigate.in_ft= +navigate.in_km_singular=1 کیلومتر دیگر +navigate.in_km=%1$s کیلومتر دیگر +navigate.in_m=%1$s متر دیگر +navigate.in_mi_singular=1 مایل دیگر +navigate.in_mi=%1$s مایل دیگر +navigate.in_ft=%1$s فوت دیگر navigate.reroute= navigate.start_navigation= -navigate.then= -navigate.thenSign= +navigate.then=سپس +navigate.thenSign=سپس navigate.turn_navigation_settings_title= navigate.vector_tiles_for_navigation= navigate.warning= diff --git a/core/src/main/resources/com/graphhopper/util/fi.txt b/core/src/main/resources/com/graphhopper/util/fi.txt index 20b52e1b487..8d21afb2264 100644 --- a/core/src/main/resources/com/graphhopper/util/fi.txt +++ b/core/src/main/resources/com/graphhopper/util/fi.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=jatka continue_onto=jatka tielle %1$S @@ -36,19 +36,26 @@ stopover=%1$s. pysähdys roundabout_enter=Aja liikenneympyrään roundabout_exit=Liikenneympyrästä poistu %1$s. liittymästä roundabout_exit_onto=Liikenneympyrästä poistu %1$s. liittymästä suuntaan %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=nousu yhteensä %1$s web.total_descend=lasku yhteensä %1$s web.way_contains_ford=Huomio, reitillä on kahlaamo web.way_contains_ferry=käytä lauttaa -web.way_contains_private=yksityinen tie web.way_contains_toll=maksullinen tie web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=vaihda %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Päivitä sivu web.server_status=Tilanne web.zoom_in=Lähennä web.zoom_out=Loitonna +web.zoom_to_route= web.drag_to_reorder=Järjestele vetämällä web.route_timed_out=Reitin laskenan aikaraja ylittyi web.route_request_failed=Reittipyyntö epäonnistui @@ -126,6 +137,20 @@ web.truck=Kuorma-auto web.staticlink=yhteys web.motorcycle=Moottoripyörällä web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Ymmärrän ja hyväksyn navigate.for_km=kulje %1$s kilometriä navigate.for_mi=kulje %1$s mailia diff --git a/core/src/main/resources/com/graphhopper/util/fil.txt b/core/src/main/resources/com/graphhopper/util/fil.txt index f51fc59c2fd..9600eb32611 100644 --- a/core/src/main/resources/com/graphhopper/util/fil.txt +++ b/core/src/main/resources/com/graphhopper/util/fil.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=tuwirín ang daán continue_onto=magpatuloy papunta sa %1$s @@ -36,19 +36,26 @@ stopover=pamahingahan %1$s roundabout_enter=Lpasok Rotonda roundabout_exit=Sa rotonda, lumabas sa exit %1$s roundabout_exit_onto=Sa rotonda, lumabas sa exit papunta %1$s %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink=static link web.motorcycle=motorsiklo web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/fr_CH.txt b/core/src/main/resources/com/graphhopper/util/fr_CH.txt index 6b24b0a4f23..60148193929 100644 --- a/core/src/main/resources/com/graphhopper/util/fr_CH.txt +++ b/core/src/main/resources/com/graphhopper/util/fr_CH.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continuez continue_onto=continuez sur %1$s @@ -36,19 +36,26 @@ stopover=escale %1$s roundabout_enter=Empruntez le giratoire roundabout_exit=Au giratoire, prenez la %1$se sortie roundabout_exit_onto=Au giratoire, prenez la %1$se sortie vers %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s de dénivelé positif web.total_descend=%1$s de dénivelé négatif web.way_contains_ford=cet itinéraire comprend un passage à gué web.way_contains_ferry=prenez le bateau -web.way_contains_private=chemin privé web.way_contains_toll=route à péage web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=changez vers %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Rafraîchir web.server_status=Statut web.zoom_in=Zoom avant web.zoom_out=Zoom arrière +web.zoom_to_route= web.drag_to_reorder=Faire glisser pour réorganiser web.route_timed_out=Le calcul de l'itinéraire a expiré web.route_request_failed=Échec du calcul de l'itinéraire @@ -126,6 +137,20 @@ web.truck=Camion web.staticlink=Lien web.motorcycle=Moto web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=pendant %1$s kilomètres navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/fr_FR.txt b/core/src/main/resources/com/graphhopper/util/fr_FR.txt index 8912c39050e..fa2578f9c54 100644 --- a/core/src/main/resources/com/graphhopper/util/fr_FR.txt +++ b/core/src/main/resources/com/graphhopper/util/fr_FR.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continuez continue_onto=continuez sur %1$s @@ -36,19 +36,26 @@ stopover=étape %1$s roundabout_enter=Empruntez le rond-point roundabout_exit=Au rond-point, prenez la %1$se sortie roundabout_exit_onto=Au rond-point, prenez la %1$se sortie vers %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s de dénivelé positif web.total_descend=%1$s de dénivelé négatif web.way_contains_ford=cet itinéraire comprend un passage à gué web.way_contains_ferry=prenez le bateau -web.way_contains_private=chemin privé web.way_contains_toll=route à péage web.way_crosses_border=L'itinéraire traverse une frontière nationale web.way_contains=L'itinéraire inclut %1$s +web.way_contains_restrictions= web.tracks=chemins de terre non pavés web.steps=pas web.footways=chemin pédestre web.steep_sections=section raide web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=changez vers %1$s @@ -72,18 +79,22 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example=Eviter les Autoroutes +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Vitesse Limite web.cargo_bike_example=Vélo cargo web.prefer_bike_network= web.exclude_area_example=Exclure Zone web.combined_example=Exemple Combiné -web.examples_custom_model=Examples +web.examples_custom_model=Exemples web.marker=Marqueur web.gh_offline_info=GraphHopper API hors connexion? web.refresh_button=Rafraîchir web.server_status=Statut web.zoom_in=Zoom avant web.zoom_out=Zoom arrière +web.zoom_to_route= web.drag_to_reorder=Faire glisser pour réorganiser web.route_timed_out=Le calcul de l'itinéraire a expiré web.route_request_failed=Échec du calcul de l'itinéraire @@ -126,6 +137,20 @@ web.truck=Camion web.staticlink=Lien web.motorcycle=Moto web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Retour web.distance_unit=Les distances sont en %1$s web.waiting_for_gps=En attente du signal GPS... @@ -141,12 +166,84 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Je comprends et j'accepte navigate.for_km=pendant %1$s kilomètres navigate.for_mi=Pendant %1$s miles navigate.full_screen_for_navigation= navigate.in_km_singular=à 1 kilomètre -navigate.in_km=à %1$s kilomètres +navigate.in_km=à %1$s kilomètres navigate.in_m=à %1$s mètres navigate.in_mi_singular=Dans 1 mile navigate.in_mi=Dans %1$s miles diff --git a/core/src/main/resources/com/graphhopper/util/gl.txt b/core/src/main/resources/com/graphhopper/util/gl.txt index a815879be66..eb5de9a6673 100644 --- a/core/src/main/resources/com/graphhopper/util/gl.txt +++ b/core/src/main/resources/com/graphhopper/util/gl.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continúe continue_onto=continúe por %1$s @@ -36,19 +36,26 @@ stopover=escala%1$s roundabout_enter=Entre na rotonda roundabout_exit=Na rotonda tome a saída %1$s roundabout_exit_onto=Na rotonda, tome a saída %1$s cara %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink=Enlace web.motorcycle=Motocicleta web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/he.txt b/core/src/main/resources/com/graphhopper/util/he.txt index 1558e02b187..52cbf1fde45 100644 --- a/core/src/main/resources/com/graphhopper/util/he.txt +++ b/core/src/main/resources/com/graphhopper/util/he.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=להמשיך continue_onto=להמשיך אל %1$s @@ -36,19 +36,26 @@ stopover=נקודת ביניים מס׳ %1$s roundabout_enter=יש להיכנס לכיכר roundabout_exit=בכיכר, יש לצאת ביציאה %1$s roundabout_exit_onto=בכיכר, יש לצאת ביציאה %1$s לתוך %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=עלייה כוללת של %1$s web.total_descend=ירידה כוללת של %1$s web.way_contains_ford=המסלול כולל מעברי מים (גשרים איריים) web.way_contains_ferry=המסלול כולל מעבורות -web.way_contains_private=המסלול כולל דרכים פרטיות web.way_contains_toll=המסלול כולל כבישי אגרה web.way_crosses_border=המסלול כולל חציית גבולות בין מדינות web.way_contains=המסלול כולל %1$s +web.way_contains_restrictions= web.tracks=דרכי עפר לא סלולות web.steps=מדרגות web.footways=שבילים להולכי רגל web.steep_sections=קטעים תלולים web.private_sections=קטעים פרטיים +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn=המסלול כולל דרכי עפר שיכולות להיות מסוכנות ואף גרוע מכך. web.get_off_bike_for=יש לרדת מהאופניים למשך %1$s pt_transfer_to=להחליף ל%1$s @@ -72,6 +79,9 @@ web.custom_model_enabled=מודל מותאם אישית מופעל web.settings=הגדרות web.settings_close=סגירה web.exclude_motorway_example=ללא כבישים מהירים +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=הגבלת מהירות web.cargo_bike_example=אופני משא web.prefer_bike_network=העדפת מסלולי אופניים @@ -84,6 +94,7 @@ web.refresh_button=רענון הדף web.server_status=מצב web.zoom_in=התקרבות web.zoom_out=התרחקות +web.zoom_to_route= web.drag_to_reorder=יש לגרור כדי לסדר מחדש web.route_timed_out=חישוב המסלול ארך זמן רב מדי web.route_request_failed=בקשת חישוב המסלול נכשלה @@ -126,6 +137,20 @@ web.truck=משאית web.staticlink=קישור קבוע web.motorcycle=אופנוע web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=חזרה web.distance_unit=מרחקים ב%1$s web.waiting_for_gps=המתנה לאות GPS... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=מובן ומוסכם עלי navigate.for_km=במשך %1$s קילומטרים navigate.for_mi=במשך %1$s מיילים diff --git a/core/src/main/resources/com/graphhopper/util/hr_HR.txt b/core/src/main/resources/com/graphhopper/util/hr_HR.txt index 65cbc470004..00886c4fe8e 100644 --- a/core/src/main/resources/com/graphhopper/util/hr_HR.txt +++ b/core/src/main/resources/com/graphhopper/util/hr_HR.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=nastavite continue_onto=nastavite na %1$s @@ -36,19 +36,26 @@ stopover=zaustavite se za %1$s roundabout_enter=Uđite na kružni tok roundabout_exit=Sa kružnog toka izađite na izlaz %1$s roundabout_exit_onto=Sa kružnog toka izađite na izlaz %1$s na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s ukupni uspon web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Osvježi stranicu web.server_status=Status web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Kamion web.staticlink=statička poveznica web.motorcycle=Motocikl web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/hsb.txt b/core/src/main/resources/com/graphhopper/util/hsb.txt index 08ed65168af..2fbc335ecf2 100644 --- a/core/src/main/resources/com/graphhopper/util/hsb.txt +++ b/core/src/main/resources/com/graphhopper/util/hsb.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=runjewon continue_onto=runjewon na %1$s @@ -36,19 +36,26 @@ stopover=mjezycil %1$s roundabout_enter=do kružneho wobchada zajěć roundabout_exit=we kružnym wobchadźe %1$s. wujězd wzać roundabout_exit_onto=we kružnym wobchadźe %1$s. wujězd na %2$s wzać +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink=link web.motorcycle=motorske web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/hu_HU.txt b/core/src/main/resources/com/graphhopper/util/hu_HU.txt index 804efbc4f6c..8d1cfe636fd 100644 --- a/core/src/main/resources/com/graphhopper/util/hu_HU.txt +++ b/core/src/main/resources/com/graphhopper/util/hu_HU.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=haladjon tovább continue_onto=haladjon tovább erre: %1$s @@ -36,19 +36,26 @@ stopover=%1$s. útpont roundabout_enter=Hajtson be a körforgalomba roundabout_exit=Hajtson ki a körforgalomból itt: %1$s. kijárat roundabout_exit_onto=Hajtson ki a körforgalomból itt: %1$s. kijárat, majd hajtson rá erre: %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=Összes szintemelkedés: %1$s web.total_descend=Összes szintcsökkenés: %1$s web.way_contains_ford=Az útvonalon gázló található web.way_contains_ferry=Az útvonalon kompátkelés található -web.way_contains_private=Az útvonalon magánút is található web.way_contains_toll=Az útvonalon útdíjat kell fizetni web.way_crosses_border=Az útvonal országhatárt keresztez web.way_contains=Az útvonalon előfordul %1$s +web.way_contains_restrictions=Az útvonalon korlátozások lehetnek web.tracks=burkolatlan földút web.steps=lépcső web.footways=gyalogút web.steep_sections=meredek szakasz web.private_sections=magánút +web.restricted_sections=korlátozott szakasz +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn=Az útvonal potenciálisan veszélyes autóutat vagy forgalmasabbat is tartalmaz web.get_off_bike_for=A kerékpárt tolni kell ennyit: %1$s pt_transfer_to=szálljon át erre: %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled=Egyedi modell bekapcsolva web.settings=Beállítások web.settings_close=Bezárás web.exclude_motorway_example=Autópálya nélkül +web.exclude_disneyland_paris_example=Disneyland Párizs kizárása +web.simple_electric_car_example=Egyszerű elektromos autó +web.avoid_tunnels_bridges_example=Hidak és alagutak elkerülése web.limit_speed_example=Sebességkorlátozás web.cargo_bike_example=Viszikli (teherkerékpár) web.prefer_bike_network=Kerékpárutak előnyben részesítése @@ -79,11 +89,12 @@ web.exclude_area_example=Terület elkerülése web.combined_example=Kombinált példa web.examples_custom_model=Példák web.marker=Jelölő -web.gh_offline_info=Lehet, hogy a GraphHopper API nem érhető el? +web.gh_offline_info=Lehet, hogy a GraphHopper API nem elérhető? web.refresh_button=Oldal frissítése web.server_status=Állapot web.zoom_in=Nagyítás web.zoom_out=Kicsinyítés +web.zoom_to_route=Útvonalra közelítés web.drag_to_reorder=Húzza el az átrendezéshez web.route_timed_out=Időtúllépés az útvonaltervezéskor web.route_request_failed=Útvonaltervezés sikertelen @@ -95,9 +106,9 @@ web.from_hint=Innen web.gpx_export_button=GPX export web.gpx_button=GPX web.settings_gpx_export=GPX-be való exportálás beállításai -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= +web.settings_gpx_export_trk=Nyomvonallal +web.settings_gpx_export_rte=Útvonallal +web.settings_gpx_export_wpt=Útpontokkal web.hide_button=Elrejtés web.details_button=Részletek web.to_hint=Ide @@ -109,11 +120,11 @@ web.pt_route_info_walking=érkezés %1$s órakor, csak gyalog (%2$s) web.locations_not_found=Útvonaltervezés nem lehetséges. A megadott hely(ek) nem található(k) meg a területen. web.search_with_nominatim=Keresés a Nominatim segítségével web.powered_by=Motor: -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.info=Információ +web.feedback=Visszajelzés +web.imprint=Impresszum +web.privacy=Adatvédelem +web.terms=Feltételek web.bike=Kerékpár web.racingbike=Versenykerékpár web.mtb=Hegyi kerékpár @@ -123,24 +134,110 @@ web.hike=Túrázás web.small_truck=Kisteherautó web.bus=Busz web.truck=Teherautó -web.staticlink=Statikus hivatkozás +web.staticlink=statikus hivatkozás web.motorcycle=Motorkerékpár -web.scooter= +web.scooter=Robogó +web.car_settings=Útvonaltervezési beállítások (autó) +web.car_settings_car=Standard +web.car_settings_car_avoid_ferry=Komp elkerülése +web.car_settings_car_avoid_motorway=Autópálya elkerülése +web.car_settings_car_avoid_toll=Útdíjas szakaszok és autópálya elkerülése +web.truck_settings=Útvonaltervezési beállítások (teherautó) +web.truck_settings_truck=Nehéz tehergépjármű +web.truck_settings_small_truck=Kisteherautó +web.foot_settings=Útvonaltervezési beállítások (séta) +web.foot_settings_foot=Standard +web.foot_settings_hike=Túrázás +web.racingbike_settings=Útvonaltervezési beállítások (országúti kerékpár) +web.racingbike_settings_racingbike=Standard +web.racingbike_settings_ecargobike=Elektromos teherkerékpár web.back_to_map=Vissza web.distance_unit=Távolság mértékegysége: %1$s -web.waiting_for_gps= -web.elevation= -web.slope= +web.waiting_for_gps=Várakozás a GPS jelre... +web.elevation=Magasság +web.slope=Meredekség web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= +web.country=Ország +web.surface=Felület +web.road_environment=Típus +web.road_access=Használhatóság +web.road_class=Besorolás +web.max_speed=Sebességkorlátozás +web.average_speed=Átlagsebesség +web.track_type=Mzg./erdészeti út besorolása +web.toll=Útdíj +web.next=Tovább +web.back=Vissza +web.as_start=Kiindulási pontnak +web.as_destination=Célpontnak +web.poi_removal_words=a, az, egy +web.poi_nearby=%1$s a közelben +web.poi_in=%1$s itt: %2$s +web.poi_airports=repülőterek, repülőtér +web.poi_atm=bankautomata +web.poi_banks=bankok, bank +web.poi_bureau_de_change=pénzváltó, valutaváltó +web.poi_bus_stops=buszmegállók, buszmegálló, busz +web.poi_bicycle=kerékpárbolt, kerékpáros bolt, biciklibolt, biciklis bolt +web.poi_bicycle_rental=kerékpárkölcsönző, biciklikölcsönző +web.poi_cafe=kávé, kávézó, kávéház, cukrászda +web.poi_car_rental=autókölcsönző, gépkocsikölcsönző, járműkölcsönző +web.poi_car_repair=autószerelő, gépjárműszerelő, autószerviz, gépjárműszerviz, szerviz +web.poi_charging_station=töltőállomás, elektromos töltőállomás, töltőpont +web.poi_cinema=mozi +web.poi_cuisine_american=amerikai ételek, amerikai konyha, amerikai +web.poi_cuisine_african=afrikai ételek, afrikai konyha, afrikai +web.poi_cuisine_arab=arab ételek, arab konyha, arab +web.poi_cuisine_asian=ázsiai ételek, ázsiai konyha, ázsiai +web.poi_cuisine_chinese=kínai ételek, kínai konyha, kínai +web.poi_cuisine_greek=görög ételek, görög konyha, görög +web.poi_cuisine_indian=indiai ételek, indiai konyha, indiai +web.poi_cuisine_italian=olasz ételek, olasz konyha, olasz +web.poi_cuisine_japanese=japán ételek, japán konyha, japán +web.poi_cuisine_mexican=mexikói ételek, mexikói konyha, mexikói +web.poi_cuisine_polish=lengyel ételek, lengyel konyha, lengyel +web.poi_cuisine_russian=orosz ételek, orosz konyha, orosz +web.poi_cuisine_turkish=török ételek, török konyha, török +web.poi_diy=barkácsbolt, barkácsáruház, vas-műszaki bolt, vaskereskedés +web.poi_dentist=fogorvos, fogász +web.poi_doctor=orvos, doktor, háziorvos +web.poi_education=oktatás, nevelés +web.poi_fast_food=büfé, gyorsétterem, menza +web.poi_food_burger=burger, hamburger +web.poi_food_kebab=kebab, döner +web.poi_food_pizza=pizza +web.poi_food_sandwich=szendvics +web.poi_food_sushi=szusi +web.poi_food_chicken=csirke +web.poi_gas_station=benzinkutak, benzinkút, benzinkutak, benzinkút +web.poi_hospitals=kórházak, kórház +web.poi_hotels=hotelek, hotel, szállodák, szálloda +web.poi_leisure=szabadidő +web.poi_museums=múzeumok, múzeum +web.poi_parking=parkoló, parkolóhely +web.poi_parks=parkok, park +web.poi_pharmacies=gyógyszertárak, gyógyszertár, patikák, patika +web.poi_playgrounds=játszóterek, játszótér +web.poi_public_transit=közösségi közlekedés, tömegközlekedés +web.poi_police=rendőrség +web.poi_post=posta, postahivatal, bélyeg +web.poi_post_box=postaláda, postaládák +web.poi_railway_station=vasútállomások, vasútállomás, vonat +web.poi_recycling=újrahasznosítás, hulladék, szemét +web.poi_restaurants=éttermek, étterem, vendéglők, vendéglő +web.poi_schools=iskolák, iskola +web.poi_shopping=bolt, üzlet, vásárlás, bevásárlás +web.poi_shop_bakery=pékség, pék +web.poi_shop_butcher=hentes, mészáros +web.poi_super_markets=szupermarketek, szupermarket +web.poi_swim=úszás, fürdés, fürdőzés +web.poi_toilets=mosdók, mosdó +web.poi_tourism=turizmus +web.poi_townhall=városháza, községháza, polgármesteri hivatal +web.poi_transit_stops=buszmegállók, buszmegálló, busz +web.poi_viewpoint=kilátó, kilátóhely, panoráma +web.poi_water=ivóvíz +web.poi_wifi=wifi, internet navigate.accept_risks_after_warning=Megértettem és elfogadom navigate.for_km=%1$s kilométert navigate.for_mi=%1$s mérföldet diff --git a/core/src/main/resources/com/graphhopper/util/in_ID.txt b/core/src/main/resources/com/graphhopper/util/in_ID.txt index 455fd6b34e3..22799b762fa 100644 --- a/core/src/main/resources/com/graphhopper/util/in_ID.txt +++ b/core/src/main/resources/com/graphhopper/util/in_ID.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=lanjut continue_onto=menuju pada %1$s @@ -13,9 +13,9 @@ turn_slight_right=belok kanan sedikit turn_sharp_left=belok kiri tajam turn_sharp_right=belok kanan tajam u_turn=putar balik -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=%1$s dan berkendara menuju %2$s +toward_destination_ref_only=%1$s menuju %2$s +toward_destination_with_ref=%1$s dan ambil %2$s menuju %3$s unknown=petunjuk baru %1$s via=melalui hour_abbr=jam @@ -35,22 +35,29 @@ unpaved=non-aspal stopover=titik hubung %1$s roundabout_enter=Masuk bundaran roundabout_exit=Pada bundaran, keluar melalui %1$s -roundabout_exit_onto=At roundabout, take exit %1$s onto %2$s +roundabout_exit_onto=Pada bundaran, keluar melalui %1$s menuju %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=naik dengan jarak %1$s web.total_descend=turun dengan jarak %1$s web.way_contains_ford=terdapat jalan untuk dilewati -web.way_contains_ferry= -web.way_contains_private= -web.way_contains_toll= -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_contains_ferry=Rute termasuk kapal feri +web.way_contains_toll=Rute dengan jalan tol +web.way_crosses_border=Rute melintasi perbatasan negara +web.way_contains=Rute termasuk %1$s +web.way_contains_restrictions=Rute dengan potensi pembatasan akses +web.tracks=Jalan Tanah +web.steps=Langkah +web.footways=Jalan setapak +web.steep_sections=bagian curam +web.private_sections=bagian pribadi +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=Rute termasuk jalan utama yang berpotensi berbahaya atau lebih buruk +web.get_off_bike_for=Turun dan dorong sepeda untuk %1$s pt_transfer_to=berpindah ke jalur %1$s web.start_label=Mulai web.intermediate_label=Antara @@ -60,46 +67,50 @@ web.set_intermediate=Atur sebagai titik antara web.set_end=Atur sebagai titik akhir web.center_map=Tengahkan Peta web.show_coords=Tampilkan koordinat -web.query_osm= +web.query_osm=Query OSM web.route=Rute -web.add_to_route= +web.add_to_route=Tambah Lokasi web.delete_from_route=Hapus dari rute -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=Buka kotak model kustom +web.draw_areas_enabled=Gambar dan ubah area di peta +web.help_custom_model=Bantuan +web.apply_custom_model=Terapkan +web.custom_model_enabled=Model Kustom Aktif +web.settings=Pengaturan +web.settings_close=Tutup +web.exclude_motorway_example=Tidak termasuk jalur motor +web.exclude_disneyland_paris_example=Tidak termasuk Disneyland Paris +web.simple_electric_car_example=Mobil Listrik Sederhana +web.avoid_tunnels_bridges_example=Hindari Jembatan & Terowongan +web.limit_speed_example=Batas Kecepatan +web.cargo_bike_example=Sepeda Kargo +web.prefer_bike_network=Lebih suka Rute Sepeda +web.exclude_area_example=Kecualikan Area +web.combined_example=Contoh Gabungan +web.examples_custom_model=Contoh web.marker=Titik web.gh_offline_info=Pelayanan API Graphhopper dalam kondisi offline web.refresh_button=Perbarui Halaman web.server_status=Status web.zoom_in=Perbesaran web.zoom_out=Pengecilan +web.zoom_to_route= web.drag_to_reorder=Drag untuk mengatur urutan -web.route_timed_out= -web.route_request_failed= -web.current_location= -web.searching_location= -web.searching_location_failed= +web.route_timed_out=Waktu Penghitungan Rute Habis +web.route_request_failed=Permintaan Rute Gagal +web.current_location=Lokasi Saat Ini +web.searching_location=Mencari Lokasi +web.searching_location_failed=Pencarian lokasi gagal web.via_hint=melalui web.from_hint=dari web.gpx_export_button=Ekspor GPX -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=Ekspor GPX +web.settings_gpx_export_trk=Dengan trek +web.settings_gpx_export_rte=Dengan rute +web.settings_gpx_export_wpt=Dengan titik jalan +web.hide_button=Sembunyikan +web.details_button=Detil web.to_hint=ke web.route_info=%1$s berada dalam waktu %2$s web.search_button=pencarian @@ -107,13 +118,13 @@ web.more_button=lebih lanjut web.pt_route_info=sampai pada %1$s dengan %2$s jarak (%3$s) web.pt_route_info_walking=sampai pada %1$s dengan berjalan kaki (%2$s) web.locations_not_found=Penentuan rute tidak dapat dilakukan. Lokasi tidak ditemukan -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.search_with_nominatim=Pencarian dengan Nominatim +web.powered_by=Didukung oleh +web.info=Info +web.feedback=Umpan Balik +web.imprint=Jejak +web.privacy=Privasi +web.terms=Syarat web.bike=Sepeda web.racingbike=Sepeda Balap web.mtb=Sepeda Gunung @@ -125,36 +136,122 @@ web.bus=Bus web.truck=Truk web.staticlink=Jalur tetap web.motorcycle=Motor -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= +web.scooter=Vespa +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=Kembali +web.distance_unit=Jarak dalam %1$s +web.waiting_for_gps=Menunggu sinyal GPS +web.elevation=Ketinggian +web.slope=Kemiringan web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= -navigate.accept_risks_after_warning= -navigate.for_km= -navigate.for_mi= -navigate.full_screen_for_navigation= -navigate.in_km_singular= -navigate.in_km= -navigate.in_m= -navigate.in_mi_singular= -navigate.in_mi= -navigate.in_ft= -navigate.reroute= -navigate.start_navigation= -navigate.then= -navigate.thenSign= -navigate.turn_navigation_settings_title= +web.country=Negara +web.surface=Permukaan +web.road_environment=Lingkungan Jalan +web.road_access=Akses Jalan +web.road_class=Kelas Jalan +web.max_speed=Kecepatan Maks +web.average_speed=Kecepatan Rata-Rata +web.track_type=Tipe trek +web.toll=Tol +web.next=Selanjutnya +web.back=Sebelumnya +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= +navigate.accept_risks_after_warning=Saya mengerti dan setuju +navigate.for_km=untuk %1$s kilometer +navigate.for_mi=untuk %1$s mil +navigate.full_screen_for_navigation=Layar Penuh +navigate.in_km_singular=dalam 1 kilometer +navigate.in_km=dalam %1$s kilometer +navigate.in_m=dalam %1$s meter +navigate.in_mi_singular=dalam 1 mil +navigate.in_mi=dalam %1$s mil +navigate.in_ft=dalam %1$s kaki +navigate.reroute=Atur ulang rute +navigate.start_navigation=Navigasi +navigate.then=selanjutnya +navigate.thenSign=selanjutnya +navigate.turn_navigation_settings_title=Belokan demi belokan navigate.vector_tiles_for_navigation= -navigate.warning= +navigate.warning=PERINGATAN: Fitur navigasi belokan demi belokan sangat eksperimental. Risiko penggunaan ditanggung sendiri, perhatikan jalan dan jangan sentuh perangkat saat mengemudi! diff --git a/core/src/main/resources/com/graphhopper/util/it.txt b/core/src/main/resources/com/graphhopper/util/it.txt index 8257a9be646..6794a4b9c67 100644 --- a/core/src/main/resources/com/graphhopper/util/it.txt +++ b/core/src/main/resources/com/graphhopper/util/it.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continua continue_onto=continua su %1$s @@ -36,19 +36,26 @@ stopover=sosta %1$s roundabout_enter=Entrare nella rotatoria roundabout_exit=Nella rotatoria, prendere l'uscita %1$s roundabout_exit_onto=Nella rotatoria, prendere l'uscita %1$s su %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s di dislivello positivo web.total_descend=%1$s di dislivello negativo web.way_contains_ford=Il percorso include un guado web.way_contains_ferry=Il percorso include traghetti -web.way_contains_private=Il percorso include strade private web.way_contains_toll=Il percorso include pedaggi web.way_crosses_border=Il percorso attraversa un confine di stato web.way_contains=Il percorso include %1$s +web.way_contains_restrictions= web.tracks=strade sterrate web.steps=Passi web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=cambia con %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example=Escludi autostrada +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Limita velocità web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Ricarica pagina web.server_status=Stato web.zoom_in=Zoom avanti web.zoom_out=Zoom indietro +web.zoom_to_route= web.drag_to_reorder=Trascina per riordinare web.route_timed_out=Calcolo del percorso fallito web.route_request_failed=Richiesta della posizione fallita @@ -126,6 +137,20 @@ web.truck=Camion web.staticlink=permalink web.motorcycle=Moto web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Indietro web.distance_unit=Le distanze sono in %1$s web.waiting_for_gps=Aspettando il segnale GPS... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Ho capito e accetto navigate.for_km=per %1$s chilometri navigate.for_mi=per %1$s miglia diff --git a/core/src/main/resources/com/graphhopper/util/ja.txt b/core/src/main/resources/com/graphhopper/util/ja.txt index 3e0acefe057..2b9147ead74 100644 --- a/core/src/main/resources/com/graphhopper/util/ja.txt +++ b/core/src/main/resources/com/graphhopper/util/ja.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=進む continue_onto=%1$sまで進む @@ -36,19 +36,26 @@ stopover=%1$sで降りる roundabout_enter=円形交差点に入る roundabout_exit=円形交差点の出口%1$sへ roundabout_exit_onto=円形交差点の出口%1$sから%2$sへ +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=更新 web.server_status=ステータス web.zoom_in=拡大 web.zoom_out=縮小 +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink=パーマリンク web.motorcycle=オートバイ web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ko.txt b/core/src/main/resources/com/graphhopper/util/ko.txt index b38d109071e..5aeba4251a5 100644 --- a/core/src/main/resources/com/graphhopper/util/ko.txt +++ b/core/src/main/resources/com/graphhopper/util/ko.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=계속 continue_onto=%1$s(으)로 계속 이동 @@ -36,19 +36,26 @@ stopover=경유지 %1$s roundabout_enter=회전교차로 진입 roundabout_exit=회전교차로에서 %1$s 진출 roundabout_exit_onto=회전교차로에서 %1$s 진출 후 %2$s 이동 +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=오르막길 총 %1$s web.total_descend=내리막길 총 %1$s web.way_contains_ford=경로에 여울이 있습니다 web.way_contains_ferry=페리 승선 -web.way_contains_private=사유 도로 web.way_contains_toll=유료 도로 web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=%1$s(으)로 환승 @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=새로고침 web.server_status=상태 web.zoom_in=확대 web.zoom_out=축소 +web.zoom_to_route= web.drag_to_reorder=드래그하여 재정렬 web.route_timed_out=경로 탐색 시간 초과 web.route_request_failed=경로 탐색 요청 실패 @@ -126,6 +137,20 @@ web.truck=트럭 web.staticlink=정적 링크 web.motorcycle=모터사이클 web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/kz.txt b/core/src/main/resources/com/graphhopper/util/kz.txt index d9074288647..d6d9232dbef 100644 --- a/core/src/main/resources/com/graphhopper/util/kz.txt +++ b/core/src/main/resources/com/graphhopper/util/kz.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=Жалғастырыңыз continue_onto=%1$s бойынша жалғастырыңыз @@ -36,19 +36,26 @@ stopover=%1$s аялдамасы roundabout_enter=айналма жолға өтіңіз roundabout_exit=%1$s-ші бұрылыстан бұрылыңыз roundabout_exit_onto=Айналымда %1$s-ден %2$s-ге +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s көтерілу web.total_descend=%1$s төмен түсу web.way_contains_ford=Жолда өткел бар web.way_contains_ferry=паромға отырыңыз -web.way_contains_private=жекеменшік жол web.way_contains_toll=жол ақылы web.way_crosses_border=жол ел шекарасын кесіп жатыр web.way_contains=жолда %1$s бар +web.way_contains_restrictions= web.tracks=асфальтталмаған қара жолдар web.steps=қадамдар web.footways=жаяу жүргіншілер жолы web.steep_sections=күрделі аялдамалар web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=%1$s-ған отырыңыз @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example=Автомогистральді алып тастау +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=шектеулі жылдамдық web.cargo_bike_example=жүк велосипеді web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=бетті жаңартыңыз web.server_status=жағдайы web.zoom_in=үлкейту web.zoom_out=кішірейту +web.zoom_to_route= web.drag_to_reorder=Ретін өзгерту үшін жылжытыңыз web.route_timed_out=Маршрутты есептеу уақыты таусылды web.route_request_failed=маршрут сұранысы орындалмады @@ -126,6 +137,20 @@ web.truck=үлкен жүк көлігі web.staticlink= web.motorcycle=мотоцикл web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Қайту web.distance_unit= web.waiting_for_gps=GPS сигналын күтіңіз... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=мен түсіндім және келісемін navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/lt_LT.txt b/core/src/main/resources/com/graphhopper/util/lt_LT.txt index 30179fd8555..f6cc7b8eaf9 100644 --- a/core/src/main/resources/com/graphhopper/util/lt_LT.txt +++ b/core/src/main/resources/com/graphhopper/util/lt_LT.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=tęskite continue_onto=tęskite toliau %1$s @@ -36,19 +36,26 @@ stopover=sustojimas %1$s roundabout_enter=Įvažiuokite į žiedą roundabout_exit=Žiede išvažiuokite %1$s išvažiavime roundabout_exit_onto=Žiede išvažiuokite %1$s išvažiavime į %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=persėskite į %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Atnaujinti puslapį web.server_status=Būsena web.zoom_in=Priartinti web.zoom_out=Atitolinti +web.zoom_to_route= web.drag_to_reorder=Tempkite, kad pakeistumėte eilės tvarką web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Sunkvežimis web.staticlink=statinė nuoroda web.motorcycle=Motociklas web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=%1$s kilometrus navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/mn.txt b/core/src/main/resources/com/graphhopper/util/mn.txt new file mode 100644 index 00000000000..699273e8ee5 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/util/mn.txt @@ -0,0 +1,257 @@ +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh + +continue=үргэлжлүүл +continue_onto=%1$s дээр үргэлжлүүлнэ үү +finish=зорьсон газраа хүрэх +keep_left=зүүн байлгах +keep_right=зөв байлгах +turn_onto=%1$s дээр %2$s +turn_left=зүүн эргэ +turn_right=баруун эргэ +turn_slight_left=бага зэрэг зүүн эргэ +turn_slight_right=бага зэрэг баруун эргэ +turn_sharp_left=огцом зүүн эргэ +turn_sharp_right=огцом баруун эргэ +u_turn=эргэлт хийх +toward_destination=%1$s ба %2$s руу яв +toward_destination_ref_only=%1$s %2$s руу +toward_destination_with_ref=%1$s ба %3$s руу %2$s-г аваарай +unknown=үл мэдэгдэх зааврын тэмдэг '%1$s' +via=дамжина +hour_abbr=цаг +day_abbr=өдөр +min_abbr=мин +km_abbr=км +m_abbr=м +mi_abbr=мил +ft_abbr=фут +road=зам +off_bike=дугуйнаасаа буу +cycleway=дугуйн зам +way=зам +small_way=жижиг зам +paved=хучилттай +unpaved=хучилтгүй +stopover=дайрах цэг %1$s +roundabout_enter=Тойрогруу ор +roundabout_exit=Тойрогоос %1$s гарцаар гар +roundabout_exit_onto=Тойрогоос %1$s гарцаар %2$s руу гар +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= +web.total_ascend=%1$s нийт өгсөнө +web.total_descend=%1$s нийт уруудна +web.way_contains_ford=Маршрут нь гүүрүүд багтдаг +web.way_contains_ferry=Маршрут нь гарам багтана +web.way_contains_toll=Маршрут хураамжтай +web.way_crosses_border=Маршрут нь улсын хилээр дамждаг +web.way_contains=Маршрут нь %1$s-г агуулдаг +web.way_contains_restrictions=Боломжит хандалтын хязгаарлалттай маршрут +web.tracks=шороон замууд +web.steps=алхам +web.footways=явган хүний ​​зам +web.steep_sections=эгц хэсгүүд +web.private_sections=хувийн хэсгүүд +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=Маршрут нь аюултай байж болзошгүй гол зам буюу түүнээс ч дор байдаг +web.get_off_bike_for=Унадаг дугуйнаасаа бууж, %1$s руу түлхэнэ үү +pt_transfer_to=%1$s болгож өөрчлөх +web.start_label=Эхлэл +web.intermediate_label=Дунд зэрэг +web.end_label=Төгсгөл +web.set_start=Эндээс +web.set_intermediate=Байршлаар дамжуулан +web.set_end=Тийшээ +web.center_map=Газрын зургийг голлуулах +web.show_coords=Координатуудыг харуулах +web.query_osm=OSM query +web.route=Маршрут +web.add_to_route=Байршил нэмэх +web.delete_from_route=Маршрутаас устгах +web.open_custom_model_box=Захиалгат загварын хайрцгийг нээнэ үү +web.draw_areas_enabled=Газрын зураг дээр газар нутгийг зурж, өөрчлөх +web.help_custom_model=Туслаач +web.apply_custom_model=Хэрэглэх +web.custom_model_enabled=Захиалгат загвар идэвхтэй +web.settings=Тохиргоо +web.settings_close=Хаах +web.exclude_motorway_example=Хурдны замыг оруулахгүй +web.exclude_disneyland_paris_example=Парисын Диснейлэндийг оруулахгүй +web.simple_electric_car_example=Энгийн цахилгаан машин +web.avoid_tunnels_bridges_example=Гүүр, хонгилоос зайлсхий +web.limit_speed_example=Хурд хязгаарлах +web.cargo_bike_example=Ачааны дугуй +web.prefer_bike_network=Дугуйн замыг илүүд үзээрэй +web.exclude_area_example=Талбайг оруулахгүй +web.combined_example=Хосолсон жишээ +web.examples_custom_model=Жишээ +web.marker=Тэмдэглэгч +web.gh_offline_info=GraphHopper API офлайн уу? +web.refresh_button=Хуудсыг шинэчлэх +web.server_status=Статус +web.zoom_in=Томруулах +web.zoom_out=Жижигрүүлэх +web.zoom_to_route=Чиглэл рүү томруулна уу +web.drag_to_reorder=Дахин эрэмбэлэхийн тулд чирнэ үү +web.route_timed_out=Маршрутын тооцооны хугацаа дууссан +web.route_request_failed=Маршрутын хүсэлт амжилтгүй боллоо +web.current_location=Одоогийн байршил +web.searching_location=Байршил хайж байна +web.searching_location_failed=Байршлыг хайж чадсангүй +web.via_hint=Via +web.from_hint=-аас +web.gpx_export_button=GPX экспорт +web.gpx_button=GPX +web.settings_gpx_export=GPX экспорт +web.settings_gpx_export_trk=Замтай +web.settings_gpx_export_rte=Маршруттай +web.settings_gpx_export_wpt=Замын цэгтэй +web.hide_button=Нуух +web.details_button=Дэлгэрэнгүй мэдээлэл +web.to_hint=руу +web.route_info=%1$s нь %2$s авна +web.search_button=Хайх +web.more_button=илүү +web.pt_route_info=%1$s-д %2$s шилжүүлгээр (%3$s) ирнэ +web.pt_route_info_walking=зүгээр л алхаж байж %1$s хүрнэ (%2$s) +web.locations_not_found=Чиглүүлэлт хийх боломжгүй. Тухайн бүсэд байршил олдсонгүй. +web.search_with_nominatim=Nominatim ашиглан хайх +web.powered_by=Powered by +web.info=Мэдээлэл +web.feedback=Санал хүсэлт +web.imprint=Дардас +web.privacy=Нууцлал +web.terms=Нөхцөл +web.bike=Унадаг дугуй +web.racingbike=Уралдааны дугуй +web.mtb=MTB +web.car=Машин +web.foot=Хөл +web.hike=Явган аялал +web.small_truck=Жижиг ачааны машин +web.bus=Автобус +web.truck=Ачааны машин +web.staticlink=статик холбоос +web.motorcycle=Мотоцикл +web.scooter=Скутер +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=Буцах +web.distance_unit=Зай %1$s байна +web.waiting_for_gps=GPS дохиог хүлээж байна... +web.elevation=Өндөр +web.slope=Налуу +web.towerslope=Цамхаг налуу +web.country=Улс +web.surface=Гадаргуу +web.road_environment=Замын орчин +web.road_access=Зам нэвтрэх +web.road_class=Замын анги +web.max_speed=Макс. хурд +web.average_speed=Дундаж. хурд +web.track_type=Замын төрөл +web.toll=Төлбөр +web.next=Дараачийн +web.back=Буцах +web.as_start= +web.as_destination= +web.poi_removal_words=талбай, эргэн тойронд, энд, дотор, орон нутгийн, ойролцоо, энэ +web.poi_nearby=%1$s ойролцоо байна +web.poi_in=%2$s-д %1$s +web.poi_airports=нисэх онгоцны буудал, нисэх онгоцны буудал +web.poi_atm=атм +web.poi_banks=банк, банк +web.poi_bureau_de_change= +web.poi_bus_stops=автобусны зогсоол, автобусны зогсоол, автобус +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station=цэнэглэх станц, цэнэглэх станц, цэнэглэгч, цэнэглэгч +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education=боловсрол +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station=шатахуун түгээх станц, шатахуун түгээх станц, шатахуун түгээх станц, шатахуун түгээх станц +web.poi_hospitals=эмнэлгүүд, эмнэлэг +web.poi_hotels=зочид буудал, зочид буудал +web.poi_leisure=чөлөөт цаг +web.poi_museums=музей, музей +web.poi_parking=зогсоол, зогсоол +web.poi_parks=цэцэрлэгт хүрээлэн, цэцэрлэгт хүрээлэн +web.poi_pharmacies=эмийн сан, эмийн сан +web.poi_playgrounds=тоглоомын талбай, тоглоомын талбай +web.poi_public_transit=нийтийн тээвэр +web.poi_police=цагдаа +web.poi_post=шуудан, шуудангийн газар +web.poi_post_box=шуудангийн хайрцаг, шуудангийн хайрцаг, шуудангийн хайрцаг, захидлын хайрцаг +web.poi_railway_station=төмөр замын өртөө, төмөр замын буудал, галт тэрэг, галт тэрэг +web.poi_recycling= +web.poi_restaurants=ресторан, зоогийн газар, идэх +web.poi_schools=сургууль, сургууль +web.poi_shopping=дэлгүүр, дэлгүүр, дэлгүүр +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets=супер захууд, супер захууд +web.poi_swim= +web.poi_toilets=бие засах газар, бие засах газар +web.poi_tourism=аялал жуулчлал +web.poi_townhall= +web.poi_transit_stops=автобусны зогсоол, автобусны зогсоол, автобус +web.poi_viewpoint= +web.poi_water=ус +web.poi_wifi= +navigate.accept_risks_after_warning=Би зөвшөөрч байна +navigate.for_km=%1$s километрт +navigate.for_mi=%1$s миль +navigate.full_screen_for_navigation=Бүтэн дэлгэцийг тохируулах +navigate.in_km_singular=1 километрт +navigate.in_km=%1$s километрт +navigate.in_m=%1$s метрт +navigate.in_mi_singular=1 миль дотор +navigate.in_mi=%1$s миль дотор +navigate.in_ft=%1$s фут дотор +navigate.reroute=дахин тооцох +navigate.start_navigation=Замчлал +navigate.then=тэгээд +navigate.thenSign=Дараа нь +navigate.turn_navigation_settings_title=Замчлах заавар +navigate.vector_tiles_for_navigation=Вектор tile ашиглах +navigate.warning=АНХААРУУЛГА: Ээлжит навигацийн функц нь маш туршилтын шинж чанартай байдаг. Үүнийг эрсдэлд оруулаарай, замд анхаарлаа хандуулаарай, жолоо барьж байхдаа төхөөрөмжид бүү хүр! diff --git a/core/src/main/resources/com/graphhopper/util/nb_NO.txt b/core/src/main/resources/com/graphhopper/util/nb_NO.txt index b7135fbfa74..eb7ec4a23a9 100644 --- a/core/src/main/resources/com/graphhopper/util/nb_NO.txt +++ b/core/src/main/resources/com/graphhopper/util/nb_NO.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=fortsett continue_onto=fortsett på %1$s @@ -36,19 +36,26 @@ stopover=delmål %1$s roundabout_enter=kjør inn i rundkjøringen roundabout_exit=ta den %1$s avkjøringen i rundkjøringen roundabout_exit_onto=I rundkjøringen, ta avkjørsel %1$s til %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s totale høydemeter web.total_descend=%1$s total nedkjøring web.way_contains_ford=ruten inneholder vadesteder web.way_contains_ferry=ruten inneholder ferge -web.way_contains_private=ruten inneholder privat vei web.way_contains_toll=ruten er avgiftsbelagt web.way_crosses_border=ruten krysser landegrense web.way_contains=ruten inneholder %1$s +web.way_contains_restrictions= web.tracks=grusvei web.steps=trapper web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=bytt til %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example=Unngå motorvei +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Begrens hastighet web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Oppdater side web.server_status=Status web.zoom_in=Zoom inn web.zoom_out=Zoom ut +web.zoom_to_route= web.drag_to_reorder=Dra for å endre rekkefølge web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Lastebil web.staticlink=lenke web.motorcycle=Motorsykkel web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Tilbake web.distance_unit=Avstand måles i %1$s web.waiting_for_gps=Venter på GPS-signal @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Jeg forstår og samtykker navigate.for_km=I %1$s kilometer navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ne.txt b/core/src/main/resources/com/graphhopper/util/ne.txt index c217440848f..449a9bbd20e 100644 --- a/core/src/main/resources/com/graphhopper/util/ne.txt +++ b/core/src/main/resources/com/graphhopper/util/ne.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=गहिरख्नुहोस continue_onto=%1$s गहिरख्नुहोस @@ -36,19 +36,26 @@ stopover=%1$s रोकिने ठाउँ roundabout_enter=घुम्ती मा छिर्नुहोस roundabout_exit=घुम्तीमा %1$s नम्बर को मोडबाट निस्कनुहोस roundabout_exit_onto=घुम्तीमा %1$s नम्बर को मोडबाट निस्केर %2$s मा जानुहोस +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend= web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck= web.staticlink=ईस्ट्यातिक लिंक web.motorcycle=मोटरसाइकल web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/nl.txt b/core/src/main/resources/com/graphhopper/util/nl.txt index 0a7fffc90c0..86b6045728c 100644 --- a/core/src/main/resources/com/graphhopper/util/nl.txt +++ b/core/src/main/resources/com/graphhopper/util/nl.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=Ga rechtdoor continue_onto=blijf op %1$s @@ -36,19 +36,26 @@ stopover=marker %1$s roundabout_enter=ga de rotonde op roundabout_exit=neem afslag %1$s roundabout_exit_onto=neem afslag %1$s naar %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s totale klim web.total_descend=%1$s totale daling web.way_contains_ford=Er is een doorwaadbare plaats web.way_contains_ferry=route met veerponten -web.way_contains_private=route met privé wegen web.way_contains_toll=route met tolwegen web.way_crosses_border=route met grensovergangen web.way_contains=route bevat %1$s +web.way_contains_restrictions= web.tracks=route bevat onverharde wegen web.steps=trappen web.footways=voetpaden web.steep_sections=route bevat stijle hellingen web.private_sections=privé gedeelten +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn=Route bevat mogelijk gevaarlijke stukken web.get_off_bike_for=stap af en duw voor %1$s pt_transfer_to=Stap over op de %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings=Instellingen web.settings_close=Sluit web.exclude_motorway_example=Vermijd snelweg +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Max snelheid web.cargo_bike_example=Bestel fiets web.prefer_bike_network=Prefereer fiets routes @@ -84,6 +94,7 @@ web.refresh_button=Ververs pagina web.server_status=Status web.zoom_in=Zoom in web.zoom_out=Zoom uit +web.zoom_to_route= web.drag_to_reorder=Slepen om opnieuw te organiseren web.route_timed_out=server berekening time out web.route_request_failed=Aanvraag mislukt @@ -93,7 +104,7 @@ web.searching_location_failed=locatie zoeken mislukt web.via_hint=via web.from_hint=van web.gpx_export_button=GPX export -web.gpx_button=GPX export te groot +web.gpx_button=GPX web.settings_gpx_export= web.settings_gpx_export_trk= web.settings_gpx_export_rte= @@ -126,6 +137,20 @@ web.truck=vrachtwagen web.staticlink=statische link web.motorcycle=motorfiets web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Terug web.distance_unit=Afstanden zijn in %1$s web.waiting_for_gps=Wacht op GPS signaal @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Ik snap het navigate.for_km=Voor %1$s kilometer navigate.for_mi=Voor %1$s mijl diff --git a/core/src/main/resources/com/graphhopper/util/pl_PL.txt b/core/src/main/resources/com/graphhopper/util/pl_PL.txt index 52a2dd53a62..12085f5fd84 100644 --- a/core/src/main/resources/com/graphhopper/util/pl_PL.txt +++ b/core/src/main/resources/com/graphhopper/util/pl_PL.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=kontynuuj continue_onto=kontynuuj na %1$s @@ -36,19 +36,26 @@ stopover=przystanek %1$s roundabout_enter=Wjedź na rondo roundabout_exit=Zjedź z ronda %1$s zjazdem roundabout_exit_onto=Zjedź z ronda %1$s zjazdem na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s w górę web.total_descend=%1$s w dół web.way_contains_ford=Trasa przez brody web.way_contains_ferry=Trasa obejmuje promy -web.way_contains_private=Trasa przez prywatne drogi web.way_contains_toll=Trasa jest płatna web.way_crosses_border=Trasa przebiega przez granicę państwową web.way_contains=Trasa przez %1$s +web.way_contains_restrictions= web.tracks=nieutwardzone drogi gruntowe web.steps=schody web.footways=chodniki web.steep_sections=strome fragmenty web.private_sections=tereny prywatne +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for=piechotą przez %1$s pt_transfer_to=przesiądź się na %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings=Ustawienia web.settings_close=Zamknij web.exclude_motorway_example=Pomijaj autostrady +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Ogranicz prędkość web.cargo_bike_example=Rower towarowy web.prefer_bike_network=Preferuj ścieżki rowerowe @@ -84,6 +94,7 @@ web.refresh_button=Odśwież stronę web.server_status=Stan web.zoom_in=Przybliż web.zoom_out=Oddal +web.zoom_to_route= web.drag_to_reorder=Przeciągnij, aby przestawić web.route_timed_out=Przekroczono limit czasu web.route_request_failed=Wyznaczenie trasy nie powiodło się @@ -126,6 +137,20 @@ web.truck=Ciężarówka web.staticlink=link web.motorcycle=Motocykl web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Wróć web.distance_unit=Odległości w %1$s web.waiting_for_gps=Oczekiwanie na sygnał GPS... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Rozumiem i akceptuję navigate.for_km=przez %1$s kilometrów navigate.for_mi=przez %1$s mil diff --git a/core/src/main/resources/com/graphhopper/util/pt_BR.txt b/core/src/main/resources/com/graphhopper/util/pt_BR.txt index 8bd987e5c6c..ee90767598e 100644 --- a/core/src/main/resources/com/graphhopper/util/pt_BR.txt +++ b/core/src/main/resources/com/graphhopper/util/pt_BR.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continuar continue_onto=continue na %1$s @@ -13,9 +13,9 @@ turn_slight_right=curva suave à direita turn_sharp_left=curva acentuada à esquerda turn_sharp_right=curva acentuada à direita u_turn=faça um retorno -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=%1$s em direção ao destino %2$s +toward_destination_ref_only=%1$s em direção ao destino (referência %2$s) +toward_destination_with_ref=%1$s e pegue %2$s em direção a %3$s unknown=sinalização desconhecida '%1$s' via=via hour_abbr=h @@ -36,21 +36,28 @@ stopover=parada %1$s roundabout_enter=Entre na rotatória roundabout_exit=Na rotatória, saia na %1$s saída roundabout_exit_onto=Na rotatória, saia na %1$s saida em direção a %2$s +roundabout_exit_now=sai da rotatória +roundabout_exit_onto_now=Saia agora para %1$s +leave_ferry=Saia da balsa e siga para %1$s +board_ferry=Atenção, embarque na balsa (%1$s) web.total_ascend=subida de %1$s web.total_descend=descida de %1$s -web.way_contains_ford= -web.way_contains_ferry= -web.way_contains_private= -web.way_contains_toll= -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_contains_ford=Contém travessia de rio +web.way_contains_ferry=Contém balsa +web.way_contains_toll=Contém pedágio +web.way_crosses_border=Atravessa a fronteira +web.way_contains=Contém %1$s +web.way_contains_restrictions=Contém restrições +web.tracks=Trilhas +web.steps=Escadas +web.footways=Caminhos de pedestres +web.steep_sections=Trechos íngremes +web.private_sections=Trechos privados +web.restricted_sections=Trechos restritos +web.challenging_sections=Trechos difíceis ou perigosos. Exigem experiência em montanhismo. +web.dangerous_sections=Trechos tecnicamente desafiadores e perigosos. Exigem cautela e experiência em montanhismo +web.trunk_roads_warn=Atenção: rota inclui rodovias principais +web.get_off_bike_for=Desça da bicleta por %1$s pt_transfer_to=mude para %1$s web.start_label=Início web.intermediate_label=Intermediário @@ -60,18 +67,21 @@ web.set_intermediate=Definir como intermediário web.set_end=Definir como fim web.center_map=Centralizar o mapa aqui web.show_coords=Mostrar coordenadas -web.query_osm= +web.query_osm=Consulta OSM web.route=Rota -web.add_to_route= +web.add_to_route=Adicionar Local web.delete_from_route=Remover da rota -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= +web.open_custom_model_box=Abrir modelo personalizado +web.draw_areas_enabled=Desenhar modelo personalizado +web.help_custom_model=Desenhar e modificar áreas no mapa +web.apply_custom_model=Ajuda +web.custom_model_enabled=Aplicar +web.settings=Configurações +web.settings_close=Fechar web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Recarregar página web.server_status=Status web.zoom_in=Ampliar web.zoom_out=Reduzir zoom +web.zoom_to_route= web.drag_to_reorder=Arraste para reordenar web.route_timed_out= web.route_request_failed= @@ -126,21 +137,107 @@ web.truck=Caminhão web.staticlink=Link estático web.motorcycle=Motocicleta web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=Voltar +web.distance_unit=Distância está em %1$s +web.waiting_for_gps=Aguardando o sinal do GPS... +web.elevation=Elevação web.slope= web.towerslope= -web.country= +web.country=País web.surface= web.road_environment= web.road_access= web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= +web.max_speed=Velocidade Máx. +web.average_speed=Velocidade Média +web.track_type=Tipo de estrada +web.toll=Pedágio +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= @@ -152,9 +249,9 @@ navigate.in_mi_singular= navigate.in_mi= navigate.in_ft= navigate.reroute= -navigate.start_navigation= -navigate.then= -navigate.thenSign= +navigate.start_navigation=Navegação +navigate.then=então +navigate.thenSign=Então navigate.turn_navigation_settings_title= navigate.vector_tiles_for_navigation= navigate.warning= diff --git a/core/src/main/resources/com/graphhopper/util/pt_PT.txt b/core/src/main/resources/com/graphhopper/util/pt_PT.txt index 8a115735101..f26c8b58906 100644 --- a/core/src/main/resources/com/graphhopper/util/pt_PT.txt +++ b/core/src/main/resources/com/graphhopper/util/pt_PT.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continuar continue_onto=continue na %1$s @@ -36,19 +36,26 @@ stopover=paragem %1$s roundabout_enter=Entre na rotunda roundabout_exit=Na rotunda, saia na %1$s saída roundabout_exit_onto=Na rotunda, saia na %1$s saida em direção a %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=subida de %1$s web.total_descend=descida de %1$s web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button= web.server_status= web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Camião web.staticlink=Ligação permanente web.motorcycle=Motocicleta web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ro.txt b/core/src/main/resources/com/graphhopper/util/ro.txt index b26523cae70..45c94ed15fc 100644 --- a/core/src/main/resources/com/graphhopper/util/ro.txt +++ b/core/src/main/resources/com/graphhopper/util/ro.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=continuă continue_onto=continuă pe %1$s @@ -36,19 +36,26 @@ stopover=escala %1$s roundabout_enter=Intrați în sensul giratoriu roundabout_exit=În sensul giratoriu folosiți ieșirea %1$s roundabout_exit_onto=În sensul giratoriu folosiți ieșirea %1$s către %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=urcare %1$s web.total_descend=coborâre %1$s web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Reîmprospătează pagina web.server_status=Disponibilitate web.zoom_in=Mărește web.zoom_out=Micșorează +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Camion web.staticlink=link web.motorcycle=Motocicletă web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/ru.txt b/core/src/main/resources/com/graphhopper/util/ru.txt index a85474b2ebf..32923a563cc 100644 --- a/core/src/main/resources/com/graphhopper/util/ru.txt +++ b/core/src/main/resources/com/graphhopper/util/ru.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=Продолжите движение continue_onto=Продолжите движение по %1$s @@ -13,9 +13,9 @@ turn_slight_right=Плавно поверните направо turn_sharp_left=Резко поверните налево turn_sharp_right=Резко поверните направо u_turn=Развернитесь -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= +toward_destination=поверните %1$s и двигайтесь в направлении %1$s +toward_destination_ref_only=поверните %1$s в направлении %1$s +toward_destination_with_ref=поверните %1$s и следуйте по %1$s в направлении %1$s unknown=Неизвестная инструкция '%1$s' via=через hour_abbr=ч @@ -36,21 +36,28 @@ stopover=остановка %1$s roundabout_enter=Поверните на кольцо roundabout_exit=Сверните на %1$s-й съезд roundabout_exit_onto=Сверните на %1$s-й съезд на %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=подъём на %1$s web.total_descend=спуск на %1$s web.way_contains_ford=На пути есть брод web.way_contains_ferry=садитесь на паром -web.way_contains_private=частная дорога web.way_contains_toll=платная дорога -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.way_crosses_border=Маршрут пересекает государственную границу +web.way_contains=Маршрут содержит %1$s +web.way_contains_restrictions=Маршрут с возможными ограничениями доступа +web.tracks=грунтовые дороги +web.steps=шаги +web.footways=пешеходные дорожки +web.steep_sections=крутые участки +web.private_sections=частный сектор +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=Маршрут содержит потенциально опасные магистральные дороги +web.get_off_bike_for=Сойдите с велосипеда и двигайтесь %1$s pt_transfer_to=Пересядьте на %1$s web.start_label=Начало web.intermediate_label=Промежуточная точка @@ -60,46 +67,50 @@ web.set_intermediate=Установить промежуточной точко web.set_end=Установить конец здесь web.center_map=Сдвинуть карту сюда web.show_coords=Показать координаты -web.query_osm= +web.query_osm=Запрос OSM web.route=Маршрут -web.add_to_route= +web.add_to_route=Добавить пунк назначения web.delete_from_route=Удалить из маршрута -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=Открыть окно пользовательской модели +web.draw_areas_enabled=Рисовать и изменять области на карте +web.help_custom_model=Помощь +web.apply_custom_model=Применить +web.custom_model_enabled=Пользовательский Режим активирован +web.settings=Настройки +web.settings_close=Закрыть +web.exclude_motorway_example=Избегать магистрали +web.exclude_disneyland_paris_example=Избегать Disneyland Paris +web.simple_electric_car_example=Электромобиль +web.avoid_tunnels_bridges_example=Избегать мосты и туннели +web.limit_speed_example=Предельная скорость +web.cargo_bike_example=Грузовой велосипед +web.prefer_bike_network=Предпочитать веломаршруты +web.exclude_area_example=Исключить область +web.combined_example=Комбинированный пример +web.examples_custom_model=Примеры web.marker=Значок web.gh_offline_info=GraphHopper API недоступен? web.refresh_button=Обновить страницу web.server_status=Статус сервера web.zoom_in=Приблизить web.zoom_out=Отдалить +web.zoom_to_route=Показать весь маршрут web.drag_to_reorder=Переместите путевую точку -web.route_timed_out= -web.route_request_failed= -web.current_location= -web.searching_location= -web.searching_location_failed= +web.route_timed_out=Время ожидания маршрута истекло +web.route_request_failed=Не удалось выполнить запрос маршрута +web.current_location=Текущее местоположение +web.searching_location=Поиск местоположения +web.searching_location_failed=Не удалось найти местоположение web.via_hint=Через web.from_hint=От web.gpx_export_button=Экспорт GPX -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=Экспорт GPX +web.settings_gpx_export_trk=С дорожкой +web.settings_gpx_export_rte=С маршрутом +web.settings_gpx_export_wpt=С путевой точкой +web.hide_button=Скрыть +web.details_button=Подробности web.to_hint=До web.route_info=%1$s займет %2$s web.search_button=Поиск @@ -107,13 +118,13 @@ web.more_button=еще web.pt_route_info=Прибытие в %1$s с %2$s пересадками (%3$s) web.pt_route_info_walking=Прибытие в %1$s пешком (%2$s) web.locations_not_found=Построить маршрут невозможно. Не определено местоположение. -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.search_with_nominatim=Поиск с помощью Nominatim +web.powered_by=При поддержке +web.info=Информация +web.feedback=Обратная связь +web.imprint=Отпечаток +web.privacy=Конфиденциальность +web.terms=Условия web.bike=Велосипед web.racingbike=Гоночный велосипед web.mtb=Горный велосипед @@ -125,36 +136,122 @@ web.bus=Автобус web.truck=Грузовой автомобиль web.staticlink=Постоянная ссылка web.motorcycle=Мотоцикл -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= +web.scooter=Самокат +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=Назад +web.distance_unit=Расстояния показаны в %1$s +web.waiting_for_gps=Ожидание сигнала GPS... +web.elevation=Высота +web.slope=Уклон +web.towerslope=Наклон башни +web.country=Страна +web.surface=Поверхность +web.road_environment=Доступ к дороге +web.road_access=Дорожный класс +web.road_class=Класс дороги +web.max_speed=Максимальная скорость +web.average_speed=Средняя скорость web.track_type= -web.toll= -navigate.accept_risks_after_warning= +web.toll=Плата за проезд +web.next=Дальше +web.back=Назад +web.as_start=Как пункт отправления +web.as_destination=Как пункт назначения +web.poi_removal_words=область, вокруг, здесь, в, местный, неподалеку, этот +web.poi_nearby=%1$s неподалеку +web.poi_in=%1$s через %2$s +web.poi_airports=аэропорты, аэропорт +web.poi_atm=банкомат, деньги +web.poi_banks=банки, банк +web.poi_bureau_de_change=бюро обмена, пункт обмена валюты, обменный пункт, пункт обмена валюты +web.poi_bus_stops=автобусные остановки, автобусная остановка, автобус +web.poi_bicycle=магазин велосипедов, ремонт велосипедов, магазин велосипедов +web.poi_bicycle_rental=прокат велосипедов, прокат велосипедов +web.poi_cafe=кафе, кафе, кафе, кофейня, бистро +web.poi_car_rental=прокат автомобилей, каршеринг, каршеринг, арендованая машина, прокат транспортных средств +web.poi_car_repair=автосервис, автосервис, автосервис, автосервис +web.poi_charging_station=зарядные станции, зарядная станция, зарядка, зарядное устройство +web.poi_cinema=кинотеатр, кинотеатр, театр, кинотеатр, кинофильмы +web.poi_cuisine_american=американская кухня, американский +web.poi_cuisine_african=африканская кухня, африканский +web.poi_cuisine_arab=арабская кухня, арабский +web.poi_cuisine_asian=азиатская кухня, азиатский +web.poi_cuisine_chinese=китайская кухня, китайский +web.poi_cuisine_greek=греческая кухня, греческий +web.poi_cuisine_indian=индийская кухня, индийский +web.poi_cuisine_italian=итальянская кухня, итальянский +web.poi_cuisine_japanese=японская кухня, японский +web.poi_cuisine_mexican=мексиканская кухня, мексиканский +web.poi_cuisine_polish=польская кухня, польский +web.poi_cuisine_russian=русская кухня, русский +web.poi_cuisine_turkish=турецкая кухня, турецкий +web.poi_diy=хозяйственные магазины, хозяйственный магазин, хозяйственный магазин, товары для дома, магазин товаров для дома, магазин товаров для дома, рукоделие, рукоделие, магазин товаров для рукоделия, магазин товаров для рукоделия, пиломатериалы +web.poi_dentist=стоматолог, зубной хирург +web.poi_doctor=врачи, врач, терапевт +web.poi_education=образование +web.poi_fast_food=фастфуд, еда на вынос +web.poi_food_burger=есть бургер, бургер, есть гамбургер, гамбургер +web.poi_food_kebab=есть кебаб, кебаб +web.poi_food_pizza=есть пиццу, пицца +web.poi_food_sandwich=есть сэндвич, сэндвич, тост, есть тост +web.poi_food_sushi=есть суши, суши +web.poi_food_chicken=есть курицу, курица +web.poi_gas_station=автозаправки, автозаправка, автозаправки, автозаправка +web.poi_hospitals=больницы, больница +web.poi_hotels=гостиницы, гостиница +web.poi_leisure=досуг +web.poi_museums=музеи, музей +web.poi_parking=парковка, парковочное место +web.poi_parks=парки, парк +web.poi_pharmacies=аптеки, аптека +web.poi_playgrounds=детские площадки, детская площадка +web.poi_public_transit=общественный транспорт +web.poi_police=полиция +web.poi_post=почта, почта, марки +web.poi_post_box=почтовый ящик, почтовый ящик, почтовый ящик, почтовый ящик +web.poi_railway_station=ж/д станции, ж/д станция, поезда, поезд +web.poi_recycling=переработка +web.poi_restaurants=рестораны, ресторан, есть, пойти поесть +web.poi_schools=школы, школа +web.poi_shopping=магазины, магазин, шоппинг +web.poi_shop_bakery=пекарня, пекарь +web.poi_shop_butcher=мясная лавка, мясная лавка, мясной магазин, мясной рынок, мясник +web.poi_super_markets=супермаркеты, супермаркет, супермаркет +web.poi_swim=плавать, купание +web.poi_toilets=уборные, уборная +web.poi_tourism=туризм +web.poi_townhall=ратуша, мэрия, муниципальный, муниципальное здание, совет, здание совета +web.poi_transit_stops=остановка, станция, общественный транспорт, общественный транспорт +web.poi_viewpoint=обзорная точка +web.poi_water=вода +web.poi_wifi=Wi-Fi, беспроводная сеть, интернет +navigate.accept_risks_after_warning=я понимаю и соглашаюсь navigate.for_km=За %1$s километрах navigate.for_mi=В %1$s милях -navigate.full_screen_for_navigation= +navigate.full_screen_for_navigation=Включить полноэкранный режим navigate.in_km_singular=В 1 километре navigate.in_km=В %1$s километрах navigate.in_m=В %1$s метрах navigate.in_mi_singular=В 1 миле navigate.in_mi=В %1$s милях navigate.in_ft=В %1$s футах -navigate.reroute= -navigate.start_navigation= -navigate.then=Затем -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= -navigate.warning= +navigate.reroute=изменить маршрут +navigate.start_navigation=Навигация +navigate.then=затем +navigate.thenSign=Затем +navigate.turn_navigation_settings_title=Навигация от поворота к повороту +navigate.vector_tiles_for_navigation=Используйте векторные плитки +navigate.warning=ВНИМАНИЕ: Функция навигации от поворота к повороту является экспериментальной. Используйте ее на свой страх и риск, следите за дорогой и не прикасайтесь к устройству во время движения! diff --git a/core/src/main/resources/com/graphhopper/util/sk.txt b/core/src/main/resources/com/graphhopper/util/sk.txt index 61d6dfcc7cb..31aea6edc8e 100644 --- a/core/src/main/resources/com/graphhopper/util/sk.txt +++ b/core/src/main/resources/com/graphhopper/util/sk.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=pokračujte continue_onto=pokračujte na %1$s @@ -36,19 +36,26 @@ stopover=zastávka %1$s roundabout_enter=Vojdite na kruhový objazd roundabout_exit=Na kruhovom objazde použite %1$s. výjazd roundabout_exit_onto=Na kruhovom objazde použite %1$s. výjazd, na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s celkové stúpanie web.total_descend=%1$s celkové klesanie web.way_contains_ford=popri ceste sa nachádza brod web.way_contains_ferry= -web.way_contains_private=Trasa obsahuje súkromné cesty. web.way_contains_toll=Trasa obsahuje spoplatnené úseky. web.way_crosses_border=Trasa križuje štátnu hranicu. web.way_contains=Trasa obsahuje %1$s +web.way_contains_restrictions= web.tracks=nespevnené cesty web.steps=schody web.footways=chodníky web.steep_sections=úseky s prudkým stúpaním web.private_sections=súkromné úseky +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for=Je potrebné zosadnúť z bicykla a %1$s ho tlačiť pt_transfer_to=prestúpte na linku %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled=Je aktívny vlastný model web.settings=Nastavenia web.settings_close=Zavrieť web.exclude_motorway_example=Vynechať diaľnice +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Limit rýchlosti web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Obnoviť stránku web.server_status=Stav web.zoom_in=Priblížiť web.zoom_out=Oddialiť +web.zoom_to_route= web.drag_to_reorder=Ťahaj pre zmenu poradia web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Kamión web.staticlink=nemenný odkaz web.motorcycle=Motocykel web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Späť web.distance_unit=Vzdialenosti sú v %1$s web.waiting_for_gps=Čakám na GPS signál... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Rozumiem a súhlasím navigate.for_km=%1$s kilometrov navigate.for_mi=%1$s míľ diff --git a/core/src/main/resources/com/graphhopper/util/sl_SI.txt b/core/src/main/resources/com/graphhopper/util/sl_SI.txt index 0d8d4684521..62863c798cc 100644 --- a/core/src/main/resources/com/graphhopper/util/sl_SI.txt +++ b/core/src/main/resources/com/graphhopper/util/sl_SI.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=nadaljujte continue_onto=nadaljujte po %1$s @@ -36,19 +36,26 @@ stopover=postanek %1$s roundabout_enter=Zapeljite v krožišče roundabout_exit=V krožišču uporabite %1$s. izhod roundabout_exit_onto=V krožišču uporabite %1$s. izhod, da zavijete na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=Skupni vzpon: %1$s web.total_descend=Skupni spust: %1$s web.way_contains_ford=Na poti je pregaz web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=Prestopite na %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Osveži stran web.server_status=Stanje web.zoom_in=Povečaj web.zoom_out=Pomanjšaj +web.zoom_to_route= web.drag_to_reorder=Povlecite za spremembo vrstnega reda web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Tovornjak web.staticlink=Povezava web.motorcycle=Motorno kolo web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/sr_RS.txt b/core/src/main/resources/com/graphhopper/util/sr_RS.txt index b7ffb28d47d..601df5a9452 100644 --- a/core/src/main/resources/com/graphhopper/util/sr_RS.txt +++ b/core/src/main/resources/com/graphhopper/util/sr_RS.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=nastavite continue_onto=nastavite na %1$s @@ -36,19 +36,26 @@ stopover=zaustavite se za %1$s roundabout_enter=Uđite na kružni tok roundabout_exit=Sa kružnog toka izađite na izlaz %1$s roundabout_exit_onto=Sa kružnog toka izađite na izlaz %1$s na %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s ukupni uspon web.total_descend= web.way_contains_ford= web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to= @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Osveži stranicu web.server_status=Status web.zoom_in= web.zoom_out= +web.zoom_to_route= web.drag_to_reorder= web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Kamion web.staticlink=statička veza web.motorcycle=Motocikl web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/sv_SE.txt b/core/src/main/resources/com/graphhopper/util/sv_SE.txt index a7430365aba..44f4b41dd64 100644 --- a/core/src/main/resources/com/graphhopper/util/sv_SE.txt +++ b/core/src/main/resources/com/graphhopper/util/sv_SE.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=fortsätt continue_onto=fortsätt in på %1$s @@ -36,19 +36,26 @@ stopover=delmål %1$s roundabout_enter=Kör in i rondellen roundabout_exit=I rondellen, ta avfart %1$s roundabout_exit_onto=I rondellen, ta avfart %1$s in på %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s stigning web.total_descend=%1$s nedstigning web.way_contains_ford=Observera att det finns ett vadställe längs rutten web.way_contains_ferry=Ta färjan -web.way_contains_private=Privat väg web.way_contains_toll=Avgiftsbelagd väg web.way_crosses_border= web.way_contains=Rutten inkluderar %1$s +web.way_contains_restrictions= web.tracks= web.steps=steg web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=byt till %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Ladda om sida web.server_status=Status web.zoom_in=Zooma in web.zoom_out=Zooma ut +web.zoom_to_route= web.drag_to_reorder=Dra för att ändra ordning web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Lastbil web.staticlink=direktlänk web.motorcycle=Motorcykel web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Backa web.distance_unit=Avstånden är i %1$s web.waiting_for_gps=Väntar på GPS signal... @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Jag förstår och godkänner navigate.for_km=i %1$s kilometer navigate.for_mi=i %1$s mil diff --git a/core/src/main/resources/com/graphhopper/util/tr.txt b/core/src/main/resources/com/graphhopper/util/tr.txt index 4eec34ce595..39ffb2ede7d 100644 --- a/core/src/main/resources/com/graphhopper/util/tr.txt +++ b/core/src/main/resources/com/graphhopper/util/tr.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=devam continue_onto=%1$s üstünde devam edin @@ -36,19 +36,26 @@ stopover=varış noktası %1$s roundabout_enter=dönel kavşağa gir roundabout_exit=dönel kavşaktan %1$s çıkışa girin roundabout_exit_onto=dönel kavşaktan %2$s üzerinde %1$s çıkışa girin +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s toplam tırmanış web.total_descend=%1$s toplam alçalış web.way_contains_ford=Dikkat, yolda bir sığ yer var web.way_contains_ferry=feribota binin -web.way_contains_private=özel yol web.way_contains_toll=Ücretli yol web.way_crosses_border=Rota ülke sınırından geçmektedir web.way_contains=Rota %1$s içermektedir +web.way_contains_restrictions= web.tracks=Asfaltsız toprak yol web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=%1$s geçiş yap @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example=Hız limiti web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Sayfayı yinele web.server_status=Durum web.zoom_in=Yakınlaştır web.zoom_out=Uzaklaştır +web.zoom_to_route= web.drag_to_reorder=Yeniden sıralamak için sürükle bırak web.route_timed_out=Rota planlaması zaman aşımına uğradı web.route_request_failed=Rota oluşturulamadı @@ -126,6 +137,20 @@ web.truck=Kamyon web.staticlink=kalıcı bağlantı web.motorcycle=motosiklet web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=geri web.distance_unit=Mesafe birimi %1$s web.waiting_for_gps=GPS sinyali bekleniyor @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=Anladım ve kabul ediyorum navigate.for_km=%1$s kilometre boyunca navigate.for_mi=%1$s mil boyunca diff --git a/core/src/main/resources/com/graphhopper/util/uk.txt b/core/src/main/resources/com/graphhopper/util/uk.txt index cbe1a8bb67e..df469b1cff7 100644 --- a/core/src/main/resources/com/graphhopper/util/uk.txt +++ b/core/src/main/resources/com/graphhopper/util/uk.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=продовжуйте continue_onto=продовжуйте по %1$s @@ -36,19 +36,26 @@ stopover=зупинка %1$s roundabout_enter=В’їжджайте на кільце roundabout_exit=На кільці використовуйте з’їзд %1$s roundabout_exit_onto=На кільці використовуйте з’їзд %1$s на %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s загалом підйому web.total_descend=%1$s загалом спуску web.way_contains_ford=На шляху є брід web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll= web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=Пересядьте на %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Оновити сторінку web.server_status=Стан web.zoom_in=Наблизити web.zoom_out=Віддалити +web.zoom_to_route= web.drag_to_reorder=Перемістіть шляхову точку web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Велика вантажівка web.staticlink=статичне посилання web.motorcycle=Мотоцикл web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km= navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/uz.txt b/core/src/main/resources/com/graphhopper/util/uz.txt index 5368f8c4961..1e9de51e926 100644 --- a/core/src/main/resources/com/graphhopper/util/uz.txt +++ b/core/src/main/resources/com/graphhopper/util/uz.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=Harakatni davom ettiring continue_onto=%1$s bo'ylab harakatni davom ettiring @@ -36,19 +36,26 @@ stopover=%1$s da to'xtash roundabout_enter=Aylanma roundabout_exit=%1$s-chi chiqish yo'liga buring roundabout_exit_onto=%1$s-chi chiqish yo'li %2$s ga buring +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=%1$s ga ko'tarilish web.total_descend=%1$s ga tushish web.way_contains_ford=Yo'lda ford bor web.way_contains_ferry=paromga o'tiring -web.way_contains_private=xususiy yo'l web.way_contains_toll=pullik yo'l web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=%1$s ga qayta o'tiring @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Sahifani yangilash web.server_status=Server holati web.zoom_in=Yaqinlashtirish web.zoom_out=Uzoqlashtirish +web.zoom_to_route= web.drag_to_reorder=Yoʻnalish nuqtasini koʻchirish web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Yuk mashinasi web.staticlink=Doimiy havola web.motorcycle=Mototsikl web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=Xaritaga qaytish web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=%1$s kilometr uchun navigate.for_mi= diff --git a/core/src/main/resources/com/graphhopper/util/vi_VN.txt b/core/src/main/resources/com/graphhopper/util/vi_VN.txt index c48a3e9c848..8b64c6c52da 100644 --- a/core/src/main/resources/com/graphhopper/util/vi_VN.txt +++ b/core/src/main/resources/com/graphhopper/util/vi_VN.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=tiếp tục continue_onto=tiếp tục đến %1$s @@ -36,19 +36,26 @@ stopover=chặng dừng chân %1$s roundabout_enter=Đi vào vòng xoay roundabout_exit=Tại vòng xoay, rẽ lối rẽ %1$s roundabout_exit_onto=Tại vòng xoay, rẽ lối rẽ %1$s vào đường %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=Đi tiếp %1$s nữa web.total_descend=Tổng số hạ %1$s web.way_contains_ford=Chú ý, có khúc sông cạn trên đường web.way_contains_ferry= -web.way_contains_private= web.way_contains_toll=đường thu phí web.way_crosses_border= web.way_contains= +web.way_contains_restrictions= web.tracks= web.steps= web.footways= web.steep_sections= web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn= web.get_off_bike_for= pt_transfer_to=chuyển sang tuyến %1$s @@ -72,6 +79,9 @@ web.custom_model_enabled= web.settings= web.settings_close= web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= web.limit_speed_example= web.cargo_bike_example= web.prefer_bike_network= @@ -84,6 +94,7 @@ web.refresh_button=Làm mới lại trang web.server_status=Tình trạng Server web.zoom_in=Phóng to web.zoom_out=Thu nhỏ +web.zoom_to_route= web.drag_to_reorder=Kéo để sắp xếp lại web.route_timed_out= web.route_request_failed= @@ -126,6 +137,20 @@ web.truck=Xe tải web.staticlink=liên kết tĩnh web.motorcycle=Mô tô web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map= web.distance_unit= web.waiting_for_gps= @@ -141,6 +166,78 @@ web.max_speed= web.average_speed= web.track_type= web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning= navigate.for_km=Khoảng %1$s km navigate.for_mi=Khoảng %1$s dặm diff --git a/core/src/main/resources/com/graphhopper/util/zh_CN.txt b/core/src/main/resources/com/graphhopper/util/zh_CN.txt index 6bf286b3120..a1a243adbf2 100644 --- a/core/src/main/resources/com/graphhopper/util/zh_CN.txt +++ b/core/src/main/resources/com/graphhopper/util/zh_CN.txt @@ -1,4 +1,4 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=继续 continue_onto=继续行驶到 %1$s @@ -30,27 +30,34 @@ off_bike=下自行车 cycleway=自行车道 way=路 small_way=小路 -paved=路面铺就 -unpaved=路面未铺就 +paved=铺好的路面 +unpaved=没有铺好的路面 stopover=中途点 %1$s roundabout_enter=进入环岛 roundabout_exit=在环岛内,使用 %1$s 出口出环岛 roundabout_exit_onto=在环岛内,使用 %1$s 出口出环岛,进入 %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=总上升 %1$s web.total_descend=总下降 %1$s web.way_contains_ford=路径中包含河滩 web.way_contains_ferry=路径中包含轮渡 -web.way_contains_private=路径中包含私有道路 web.way_contains_toll=路径中包含收费路段 web.way_crosses_border=路径中跨越了国界 web.way_contains=路径中包含%1$s +web.way_contains_restrictions=可能存在访问限制的路线 web.tracks=未铺设的土路 web.steps=台阶 -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= +web.footways=人行道 +web.steep_sections=陡峭路段 +web.private_sections=私人路段 +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=路线中可能包含潜在危险的主干道或其他更糟糕的道路条件 +web.get_off_bike_for=骑行者必须下车并推行自行车 %1$s pt_transfer_to=换乘%1$s web.start_label=起点 web.intermediate_label=途经点 @@ -65,16 +72,19 @@ web.route=路线 web.add_to_route=增加位置 web.delete_from_route=从线路中移除 web.open_custom_model_box=打开自定义模型选项 -web.draw_areas_enabled= +web.draw_areas_enabled= 在地图上绘制和修改区域 web.help_custom_model=帮助 web.apply_custom_model=应用 -web.custom_model_enabled= -web.settings= -web.settings_close= +web.custom_model_enabled=激活自定义模型 +web.settings=设置 +web.settings_close=关闭 web.exclude_motorway_example=排除高速公路 +web.exclude_disneyland_paris_example=请避开巴黎迪士尼乐园 +web.simple_electric_car_example=适合电动汽车行驶的路线 +web.avoid_tunnels_bridges_example=请避开桥梁和隧道 web.limit_speed_example=限速 -web.cargo_bike_example= -web.prefer_bike_network= +web.cargo_bike_example=货运自行车 +web.prefer_bike_network=优先选择自行车道 web.exclude_area_example=排除区域 web.combined_example=综合范例 web.examples_custom_model=范例 @@ -84,6 +94,7 @@ web.refresh_button=刷新网页 web.server_status=状态 web.zoom_in=放大 web.zoom_out=缩小 +web.zoom_to_route= web.drag_to_reorder=拖动可重新排序 web.route_timed_out=导航计算超时 web.route_request_failed=导航请求失败 @@ -94,10 +105,10 @@ web.via_hint=途经点 web.from_hint=起点 web.gpx_export_button=GPX 格式导出 web.gpx_button=GPX -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= +web.settings_gpx_export=GPX导出 +web.settings_gpx_export_trk=轨迹 +web.settings_gpx_export_rte=路线 +web.settings_gpx_export_wpt=航点 web.hide_button=隐藏 web.details_button=详情 web.to_hint=终点 @@ -109,11 +120,11 @@ web.pt_route_info_walking=仅需步行,在 %1$s 到达 (%2$s) web.locations_not_found=无法导航,因为地点未找到。 web.search_with_nominatim=用 Nominatim 搜索 web.powered_by=Powered by -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.info=信息 +web.feedback=反馈 +web.imprint=印记 +web.privacy=个人隐私 +web.terms=条款 web.bike=自行车 web.racingbike=竞技自行车 web.mtb=山地自行车 @@ -125,36 +136,122 @@ web.bus=公交车 web.truck=卡车 web.staticlink=永久链接 web.motorcycle=摩托车 -web.scooter= +web.scooter=滑板车 +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= web.back_to_map=返回 web.distance_unit=距离单位:%1$s web.waiting_for_gps=正在搜索 GPS 信号…… -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= +web.elevation=高程(地面某点相对于海平面或其他参考平面的垂直距离) +web.slope=坡度 +web.towerslope=塔形坡 +web.country=国家/乡村 +web.surface=表面 +web.road_environment=道路环境 +web.road_access=道路通行 +web.road_class=道路等级 +web.max_speed=最高速度 +web.average_speed=平均速度 +web.track_type=轨道类型 +web.toll=收费 +web.next=下一页 +web.back=返回 +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= navigate.accept_risks_after_warning=我理解并同意 -navigate.for_km=行驶 %1$skm +navigate.for_km=行驶 %1$s km navigate.for_mi=行驶 %1$s 英里 -navigate.full_screen_for_navigation= +navigate.full_screen_for_navigation=全屏 navigate.in_km_singular=1km 后 navigate.in_km=%1$skm 后 navigate.in_m=%1$sm 后 navigate.in_mi_singular=1 英里后 navigate.in_mi=%1$s 英里后 navigate.in_ft=%1$s 英尺后 -navigate.reroute= -navigate.start_navigation= +navigate.reroute=重新规划路线 +navigate.start_navigation=导航 navigate.then=然后 -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= +navigate.thenSign=然后 +navigate.turn_navigation_settings_title=逐向导航 +navigate.vector_tiles_for_navigation=使用矢量加速 navigate.warning=警告:该应用程序处于早期实验阶段,使用时风险自负! diff --git a/core/src/main/resources/com/graphhopper/util/zh_HK.txt b/core/src/main/resources/com/graphhopper/util/zh_HK.txt index fae32846cdc..fe2d665d1a7 100644 --- a/core/src/main/resources/com/graphhopper/util/zh_HK.txt +++ b/core/src/main/resources/com/graphhopper/util/zh_HK.txt @@ -1,10 +1,10 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh continue=繼續 continue_onto=繼續行駛到 %1$s finish=到達目的地 -keep_left= -keep_right= +keep_left=保持左行 +keep_right=保持右行 turn_onto=%1$s 到 %2$s turn_left=左轉 turn_right=右轉 @@ -12,11 +12,11 @@ turn_slight_left=左轉 turn_slight_right=右轉 turn_sharp_left=左急轉 turn_sharp_right=右急轉 -u_turn= -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= -unknown= +u_turn=轉頭 +toward_destination=%1$s,向 %2$s 行駛 +toward_destination_ref_only=往 %2$s 方向%1$s +toward_destination_with_ref=%1$s,由 %2$s 往 %3$s 方向行駛 +unknown=未知標誌牌“%1$s” via=途經 hour_abbr=小時 day_abbr=天 @@ -26,135 +26,232 @@ m_abbr=米 mi_abbr=英里 ft_abbr=英尺 road=道路 -off_bike= +off_bike=落單車 cycleway=單車徑 way=道路 small_way=小路 -paved= -unpaved= +paved=鋪好的路面 +unpaved=没有鋪好的路面 stopover=中途站 %1$s roundabout_enter=進入迴旋處 roundabout_exit=使用 %1$s 出口離開迴旋處 roundabout_exit_onto=使用 %1$s 出口離開迴旋處到 %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= web.total_ascend=總共上昇 %1$s web.total_descend=總共下降 %1$s -web.way_contains_ford= -web.way_contains_ferry= -web.way_contains_private= -web.way_contains_toll= -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= -pt_transfer_to= +web.way_contains_ford=路径中包含河灘 +web.way_contains_ferry=路径中包含渡輪 +web.way_contains_toll=路径中包含收費路段 +web.way_crosses_border=路径跨越了國界 +web.way_contains=路径中包含%1$s +web.way_contains_restrictions=可能存在訪問限制的路線 +web.tracks=未鋪設的土路 +web.steps=台階 +web.footways=人行道 +web.steep_sections=陡峭路段 +web.private_sections=私人路段 +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn=路線中可能包含潛在危險的主幹道或其他更糟糕的路況 +web.get_off_bike_for=騎行者必須下車並推行自行車 %1$s +pt_transfer_to=換乘%1$s web.start_label=起點 web.intermediate_label=途經點 web.end_label=目的地 web.set_start=設置為起點 web.set_intermediate=設置為途經點 web.set_end=設置為目的地 -web.center_map= +web.center_map=地圖居中 web.show_coords=顯示坐標 -web.query_osm= +web.query_osm=查詢 OSM web.route=路線 -web.add_to_route= +web.add_to_route=增加位置 web.delete_from_route=從路線移除 -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= +web.open_custom_model_box=打開自定義模型選項 +web.draw_areas_enabled=在地圖上繪製和修改區域 +web.help_custom_model=幫助 +web.apply_custom_model=應用 +web.custom_model_enabled=啟用自定義模型 +web.settings=設置 +web.settings_close=關閉 +web.exclude_motorway_example=排除高速公路 +web.exclude_disneyland_paris_example=請避開巴黎迪士尼樂園 +web.simple_electric_car_example=適合電動汽車行駛的路線 +web.avoid_tunnels_bridges_example=請避開橋樑和隧道 +web.limit_speed_example=限速 +web.cargo_bike_example=貨運自行車 +web.prefer_bike_network=優先選擇自行車道 +web.exclude_area_example=排除區域 +web.combined_example=綜合範例 +web.examples_custom_model=範例 web.marker=標記 web.gh_offline_info=無法連接 GraphHopper API web.refresh_button=刷新網頁 web.server_status=狀況 web.zoom_in=放大 web.zoom_out=縮小 -web.drag_to_reorder= -web.route_timed_out= -web.route_request_failed= -web.current_location= -web.searching_location= -web.searching_location_failed= +web.zoom_to_route= +web.drag_to_reorder=拖動可重新排序 +web.route_timed_out=導航計算超時 +web.route_request_failed=導航請求失敗 +web.current_location=當前位置 +web.searching_location=正在搜索位置 +web.searching_location_failed=搜索位置失敗 web.via_hint=途經 web.from_hint=起點 web.gpx_export_button=GPX格式輸出 -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= +web.gpx_button=GPX +web.settings_gpx_export=GPX導出 +web.settings_gpx_export_trk=軌跡 +web.settings_gpx_export_rte=路線 +web.settings_gpx_export_wpt=航點 +web.hide_button=隱藏 +web.details_button=詳情 web.to_hint=目的地 web.route_info=%1$s 需時 %2$s web.search_button=搜尋 web.more_button=更多 -web.pt_route_info= -web.pt_route_info_walking= -web.locations_not_found=找不到起點/目的地 -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= +web.pt_route_info=换乘 %2$s 次,在 %1$s 到達 (%3$s) +web.pt_route_info_walking=僅需步行,在 %1$s 到達 (%2$s) +web.locations_not_found=無法導航,因為地點未找到。 +web.search_with_nominatim=用 Nominatim 搜索 +web.powered_by=Powered by +web.info=信息 +web.feedback=反饋 +web.imprint=印記 +web.privacy=個人隱私 +web.terms=條款 web.bike=單車 web.racingbike=競技單車 web.mtb=越野單車 web.car=汽車 web.foot=步行 -web.hike= +web.hike=徒步 web.small_truck=小型貨車 web.bus=巴士 web.truck=貨車 web.staticlink=鏈接 web.motorcycle=電單車 -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= -navigate.accept_risks_after_warning= -navigate.for_km= -navigate.for_mi= -navigate.full_screen_for_navigation= -navigate.in_km_singular= -navigate.in_km= -navigate.in_m= -navigate.in_mi_singular= -navigate.in_mi= -navigate.in_ft= -navigate.reroute= -navigate.start_navigation= -navigate.then= -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= -navigate.warning= +web.scooter=滑板車 +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map=返回 +web.distance_unit=距離單位:%1$s +web.waiting_for_gps=正在搜索 GPS 信號…… +web.elevation=高程(地面某點相對於海平面或其他參考平面的垂直距離) +web.slope=坡度 +web.towerslope=塔形坡 +web.country=國家/鄉村 +web.surface=表面 +web.road_environment=道路環境 +web.road_access=道路通行 +web.road_class=道路等級 +web.max_speed=最高速度 +web.average_speed=平均速度 +web.track_type=軌道類型 +web.toll=收費 +web.next=下一頁 +web.back=返回 +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= +navigate.accept_risks_after_warning=我理解並同意 +navigate.for_km=行駛 %1$s km +navigate.for_mi=行駛 %1$s 英里 +navigate.full_screen_for_navigation=全屏 +navigate.in_km_singular=1km 後 +navigate.in_km=%1$skm 後 +navigate.in_m=%1$sm 後 +navigate.in_mi_singular=1 英里後 +navigate.in_mi=%1$s 英里後 +navigate.in_ft=%1$s 英尺後 +navigate.reroute=重新規劃路線 +navigate.start_navigation=導航 +navigate.then=然後 +navigate.thenSign=然後 +navigate.turn_navigation_settings_title=逐向導航 +navigate.vector_tiles_for_navigation=使用矢量加速 +navigate.warning=警告:該應用程序處於早期實驗階段,使用時風險自負! diff --git a/core/src/main/resources/com/graphhopper/util/zh_TW.txt b/core/src/main/resources/com/graphhopper/util/zh_TW.txt index ad04fa2bd44..3043b97d2c5 100644 --- a/core/src/main/resources/com/graphhopper/util/zh_TW.txt +++ b/core/src/main/resources/com/graphhopper/util/zh_TW.txt @@ -1,160 +1,257 @@ -# do not edit manually, instead use spreadsheet https://t.co/f086oJXAEI and script ./core/files/update-translations.sh +# do not edit manually, instead use spreadsheet from translations.md and script ./core/files/update-translations.sh -continue=繼續 -continue_onto=繼續行駛到 %1$s -finish=抵達目的地 -keep_left=保持左側 -keep_right=保持右側 -turn_onto=%1$s 進入%2$s -turn_left=左轉 -turn_right=右轉 -turn_slight_left=微靠左轉 -turn_slight_right=微靠右轉 -turn_sharp_left=左急轉 -turn_sharp_right=右急轉 -u_turn=迴轉 -toward_destination= -toward_destination_ref_only= -toward_destination_with_ref= -unknown=未知指示標誌 '%1$s' -via=途經 -hour_abbr=小時 -day_abbr=天 -min_abbr=分鐘 -km_abbr=公里 -m_abbr=公尺 -mi_abbr=英里 -ft_abbr=英尺 -road=道路 -off_bike=下自行車 -cycleway=自行車道 -way=路 -small_way=小路 -paved=路面有鋪設 -unpaved=路面無鋪設 -stopover=中途點 %1$s -roundabout_enter=進入圓環 -roundabout_exit=於 %1$s 個出口離開圓環 -roundabout_exit_onto=於 %1$s 個出口離開圓環,進入 %2$s -web.total_ascend=總共上昇 %1$s -web.total_descend=總共下降 %1$s -web.way_contains_ford=路徑中含有淺灘 -web.way_contains_ferry=搭乘渡輪 -web.way_contains_private=私人道路 -web.way_contains_toll=收費道路 -web.way_crosses_border= -web.way_contains= -web.tracks= -web.steps= -web.footways= -web.steep_sections= -web.private_sections= -web.trunk_roads_warn= -web.get_off_bike_for= -pt_transfer_to=變換至 %1$s -web.start_label=出發點 -web.intermediate_label=途經點 -web.end_label=抵達點 -web.set_start=設為出發點 -web.set_intermediate=設為途經點 -web.set_end=設為抵達點 -web.center_map=設為地圖中心 -web.show_coords=顯示坐標 -web.query_osm= -web.route=路線 -web.add_to_route= -web.delete_from_route=從路線中移除 -web.open_custom_model_box= -web.draw_areas_enabled= -web.help_custom_model= -web.apply_custom_model= -web.custom_model_enabled= -web.settings= -web.settings_close= -web.exclude_motorway_example= -web.limit_speed_example= -web.cargo_bike_example= -web.prefer_bike_network= -web.exclude_area_example= -web.combined_example= -web.examples_custom_model= -web.marker=標記 -web.gh_offline_info=GraphHopper API 離線狀態? -web.refresh_button=刷新頁面 -web.server_status=狀態 -web.zoom_in=放大 -web.zoom_out=縮小 -web.drag_to_reorder=拖曳排序 -web.route_timed_out= -web.route_request_failed= -web.current_location= -web.searching_location= -web.searching_location_failed= -web.via_hint=途經 -web.from_hint=起點 -web.gpx_export_button=匯出GPS -web.gpx_button= -web.settings_gpx_export= -web.settings_gpx_export_trk= -web.settings_gpx_export_rte= -web.settings_gpx_export_wpt= -web.hide_button= -web.details_button= -web.to_hint=迄點 -web.route_info=%1$s 需時 %2$s -web.search_button=搜尋 -web.more_button=更多 -web.pt_route_info=於 %1$s 抵達,%2$s 次轉乘 (%3$s) -web.pt_route_info_walking=於 %1$s 抵達,僅步行 (%2$s) -web.locations_not_found=無法進行規劃。無法在此區域內找到指定的地點 -web.search_with_nominatim= -web.powered_by= -web.info= -web.feedback= -web.imprint= -web.privacy= -web.terms= -web.bike=自行車 -web.racingbike=競技自行車 -web.mtb=登山車 -web.car=汽車 -web.foot=步行 -web.hike=健行 -web.small_truck=小貨車 -web.bus=公車 -web.truck=貨車 -web.staticlink=永久鏈結 -web.motorcycle=摩托車 -web.scooter= -web.back_to_map= -web.distance_unit= -web.waiting_for_gps= -web.elevation= -web.slope= -web.towerslope= -web.country= -web.surface= -web.road_environment= -web.road_access= -web.road_class= -web.max_speed= -web.average_speed= -web.track_type= -web.toll= -navigate.accept_risks_after_warning= -navigate.for_km= -navigate.for_mi= -navigate.full_screen_for_navigation= -navigate.in_km_singular= -navigate.in_km= -navigate.in_m= -navigate.in_mi_singular= -navigate.in_mi= -navigate.in_ft= -navigate.reroute= -navigate.start_navigation= -navigate.then= -navigate.thenSign= -navigate.turn_navigation_settings_title= -navigate.vector_tiles_for_navigation= +continue=繼續 +continue_onto=繼續行駛到 %1$s +finish=抵達目的地 +keep_left=保持左側 +keep_right=保持右側 +turn_onto=%1$s 進入%2$s +turn_left=左轉 +turn_right=右轉 +turn_slight_left=微靠左轉 +turn_slight_right=微靠右轉 +turn_sharp_left=左急轉 +turn_sharp_right=右急轉 +u_turn=迴轉 +toward_destination= +toward_destination_ref_only= +toward_destination_with_ref= +unknown=未知指示標誌 '%1$s' +via=途經 +hour_abbr=小時 +day_abbr=天 +min_abbr=分鐘 +km_abbr=公里 +m_abbr=公尺 +mi_abbr=英里 +ft_abbr=英尺 +road=道路 +off_bike=下自行車 +cycleway=自行車道 +way=路 +small_way=小路 +paved=路面有鋪設 +unpaved=路面無鋪設 +stopover=中途點 %1$s +roundabout_enter=進入圓環 +roundabout_exit=於 %1$s 個出口離開圓環 +roundabout_exit_onto=於 %1$s 個出口離開圓環,進入 %2$s +roundabout_exit_now= +roundabout_exit_onto_now= +leave_ferry= +board_ferry= +web.total_ascend=總共上昇 %1$s +web.total_descend=總共下降 %1$s +web.way_contains_ford=路徑中含有淺灘 +web.way_contains_ferry=搭乘渡輪 +web.way_contains_toll=收費道路 +web.way_crosses_border= +web.way_contains= +web.way_contains_restrictions= +web.tracks= +web.steps= +web.footways= +web.steep_sections= +web.private_sections= +web.restricted_sections= +web.challenging_sections= +web.dangerous_sections= +web.trunk_roads_warn= +web.get_off_bike_for= +pt_transfer_to=變換至 %1$s +web.start_label=出發點 +web.intermediate_label=途經點 +web.end_label=抵達點 +web.set_start=設為出發點 +web.set_intermediate=設為途經點 +web.set_end=設為抵達點 +web.center_map=設為地圖中心 +web.show_coords=顯示坐標 +web.query_osm= +web.route=路線 +web.add_to_route= +web.delete_from_route=從路線中移除 +web.open_custom_model_box= +web.draw_areas_enabled= +web.help_custom_model= +web.apply_custom_model= +web.custom_model_enabled= +web.settings= +web.settings_close= +web.exclude_motorway_example= +web.exclude_disneyland_paris_example= +web.simple_electric_car_example= +web.avoid_tunnels_bridges_example= +web.limit_speed_example= +web.cargo_bike_example= +web.prefer_bike_network= +web.exclude_area_example= +web.combined_example= +web.examples_custom_model= +web.marker=標記 +web.gh_offline_info=GraphHopper API 離線狀態? +web.refresh_button=刷新頁面 +web.server_status=狀態 +web.zoom_in=放大 +web.zoom_out=縮小 +web.zoom_to_route= +web.drag_to_reorder=拖曳排序 +web.route_timed_out= +web.route_request_failed= +web.current_location= +web.searching_location= +web.searching_location_failed= +web.via_hint=途經 +web.from_hint=起點 +web.gpx_export_button=匯出GPS +web.gpx_button= +web.settings_gpx_export= +web.settings_gpx_export_trk= +web.settings_gpx_export_rte= +web.settings_gpx_export_wpt= +web.hide_button= +web.details_button= +web.to_hint=迄點 +web.route_info=%1$s 需時 %2$s +web.search_button=搜尋 +web.more_button=更多 +web.pt_route_info=於 %1$s 抵達,%2$s 次轉乘 (%3$s) +web.pt_route_info_walking=於 %1$s 抵達,僅步行 (%2$s) +web.locations_not_found=無法進行規劃。無法在此區域內找到指定的地點 +web.search_with_nominatim= +web.powered_by= +web.info= +web.feedback= +web.imprint= +web.privacy= +web.terms= +web.bike=自行車 +web.racingbike=競技自行車 +web.mtb=登山車 +web.car=汽車 +web.foot=步行 +web.hike=健行 +web.small_truck=小貨車 +web.bus=公車 +web.truck=貨車 +web.staticlink=永久鏈結 +web.motorcycle=摩托車 +web.scooter= +web.car_settings= +web.car_settings_car= +web.car_settings_car_avoid_ferry= +web.car_settings_car_avoid_motorway= +web.car_settings_car_avoid_toll= +web.truck_settings= +web.truck_settings_truck= +web.truck_settings_small_truck= +web.foot_settings= +web.foot_settings_foot= +web.foot_settings_hike= +web.racingbike_settings= +web.racingbike_settings_racingbike= +web.racingbike_settings_ecargobike= +web.back_to_map= +web.distance_unit= +web.waiting_for_gps= +web.elevation= +web.slope= +web.towerslope= +web.country= +web.surface= +web.road_environment= +web.road_access= +web.road_class= +web.max_speed= +web.average_speed= +web.track_type= +web.toll= +web.next= +web.back= +web.as_start= +web.as_destination= +web.poi_removal_words= +web.poi_nearby= +web.poi_in= +web.poi_airports= +web.poi_atm= +web.poi_banks= +web.poi_bureau_de_change= +web.poi_bus_stops= +web.poi_bicycle= +web.poi_bicycle_rental= +web.poi_cafe= +web.poi_car_rental= +web.poi_car_repair= +web.poi_charging_station= +web.poi_cinema= +web.poi_cuisine_american= +web.poi_cuisine_african= +web.poi_cuisine_arab= +web.poi_cuisine_asian= +web.poi_cuisine_chinese= +web.poi_cuisine_greek= +web.poi_cuisine_indian= +web.poi_cuisine_italian= +web.poi_cuisine_japanese= +web.poi_cuisine_mexican= +web.poi_cuisine_polish= +web.poi_cuisine_russian= +web.poi_cuisine_turkish= +web.poi_diy= +web.poi_dentist= +web.poi_doctor= +web.poi_education= +web.poi_fast_food= +web.poi_food_burger= +web.poi_food_kebab= +web.poi_food_pizza= +web.poi_food_sandwich= +web.poi_food_sushi= +web.poi_food_chicken= +web.poi_gas_station= +web.poi_hospitals= +web.poi_hotels= +web.poi_leisure= +web.poi_museums= +web.poi_parking= +web.poi_parks= +web.poi_pharmacies= +web.poi_playgrounds= +web.poi_public_transit= +web.poi_police= +web.poi_post= +web.poi_post_box= +web.poi_railway_station= +web.poi_recycling= +web.poi_restaurants= +web.poi_schools= +web.poi_shopping= +web.poi_shop_bakery= +web.poi_shop_butcher= +web.poi_super_markets= +web.poi_swim= +web.poi_toilets= +web.poi_tourism= +web.poi_townhall= +web.poi_transit_stops= +web.poi_viewpoint= +web.poi_water= +web.poi_wifi= +navigate.accept_risks_after_warning= +navigate.for_km= +navigate.for_mi= +navigate.full_screen_for_navigation= +navigate.in_km_singular= +navigate.in_km= +navigate.in_m= +navigate.in_mi_singular= +navigate.in_mi= +navigate.in_ft= +navigate.reroute= +navigate.start_navigation= +navigate.then= +navigate.thenSign= +navigate.turn_navigation_settings_title= +navigate.vector_tiles_for_navigation= navigate.warning= diff --git a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java index 38381a4945e..5d2b17dbc1a 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperProfileTest.java @@ -23,10 +23,12 @@ import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; import com.graphhopper.jackson.Jackson; +import com.graphhopper.routing.TestProfiles; import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.Arrays; +import java.util.List; import static org.junit.jupiter.api.Assertions.*; @@ -37,12 +39,12 @@ public class GraphHopperProfileTest { @Test public void deserialize() throws IOException { ObjectMapper objectMapper = Jackson.newObjectMapper(); - String json = "{\"name\":\"my_car\",\"vehicle\":\"car\",\"weighting\":\"custom\",\"turn_costs\":true,\"foo\":\"bar\",\"baz\":\"buzz\"}"; + String json = "{\"name\":\"my_car\",\"weighting\":\"custom\",\"turn_costs\":{\"vehicle_types\":[\"motorcar\"]},\"foo\":\"bar\",\"baz\":\"buzz\"}"; Profile profile = objectMapper.readValue(json, Profile.class); assertEquals("my_car", profile.getName()); - assertEquals("car", profile.getVehicle()); + assertEquals(List.of("motorcar"), profile.getTurnCostsConfig().getVehicleTypes()); assertEquals("custom", profile.getWeighting()); - assertTrue(profile.isTurnCosts()); + assertTrue(profile.hasTurnCosts()); assertEquals(2, profile.getHints().toMap().size()); assertEquals("bar", profile.getHints().getString("foo", "")); assertEquals("buzz", profile.getHints().getString("baz", "")); @@ -52,49 +54,16 @@ public void deserialize() throws IOException { public void duplicateProfileName_error() { final GraphHopper hopper = createHopper(); assertIllegalArgument(() -> hopper.setProfiles( - new Profile("my_profile").setVehicle("car"), - new Profile("your_profile").setVehicle("car"), - new Profile("my_profile").setVehicle("car") + new Profile("my_profile"), + new Profile("your_profile"), + new Profile("my_profile") ), "Profile names must be unique. Duplicate name: 'my_profile'"); } - @Test - public void vehicleDoesNotExist_error() { - final GraphHopper hopper = new GraphHopper(); - hopper.setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false). - setProfiles(new Profile("profile").setVehicle("your_car")); - assertIllegalArgument(hopper::importOrLoad, "entry in vehicle list not supported: your_car"); - } - - @Test - public void vehicleDoesNotExist_error2() { - final GraphHopper hopper = new GraphHopper().setGraphHopperLocation(GH_LOCATION).setStoreOnFlush(false). - setProfiles(new Profile("profile").setVehicle("your_car")); - assertIllegalArgument(hopper::importOrLoad, "entry in vehicle list not supported: your_car"); - } - - @Test - public void oneVehicleTwoProfilesWithAndWithoutTC_noError() { - final GraphHopper hopper = createHopper(); - hopper.setProfiles( - new Profile("profile1").setVehicle("car").setTurnCosts(false), - new Profile("profile2").setVehicle("car").setTurnCosts(true)); - hopper.load(); - } - - @Test - public void oneVehicleTwoProfilesWithAndWithoutTC2_noError() { - final GraphHopper hopper = createHopper(); - hopper.setProfiles( - new Profile("profile2").setVehicle("car").setTurnCosts(true), - new Profile("profile1").setVehicle("car").setTurnCosts(false)); - hopper.load(); - } - @Test public void profileWithUnknownWeighting_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile").setVehicle("car").setWeighting("your_weighting")); + hopper.setProfiles(new Profile("profile").setWeighting("your_weighting")); assertIllegalArgument(hopper::importOrLoad, "Could not create weighting for profile: 'profile'", "Weighting 'your_weighting' not supported" @@ -104,7 +73,7 @@ public void profileWithUnknownWeighting_error() { @Test public void chProfileDoesNotExist_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile1").setVehicle("car")); + hopper.setProfiles(TestProfiles.constantSpeed("profile1")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("other_profile")); assertIllegalArgument(hopper::importOrLoad, "CH profile references unknown profile 'other_profile'"); } @@ -112,7 +81,7 @@ public void chProfileDoesNotExist_error() { @Test public void duplicateCHProfile_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile").setVehicle("car")); + hopper.setProfiles(TestProfiles.constantSpeed("profile")); hopper.getCHPreparationHandler().setCHProfiles( new CHProfile("profile"), new CHProfile("profile") @@ -123,7 +92,7 @@ public void duplicateCHProfile_error() { @Test public void lmProfileDoesNotExist_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile1").setVehicle("car")); + hopper.setProfiles(TestProfiles.constantSpeed("profile1")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("other_profile")); assertIllegalArgument(hopper::importOrLoad, "LM profile references unknown profile 'other_profile'"); } @@ -131,7 +100,7 @@ public void lmProfileDoesNotExist_error() { @Test public void duplicateLMProfile_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile").setVehicle("car")); + hopper.setProfiles(TestProfiles.constantSpeed("profile")); hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile"), new LMProfile("profile") @@ -142,7 +111,7 @@ public void duplicateLMProfile_error() { @Test public void unknownLMPreparationProfile_error() { final GraphHopper hopper = createHopper(); - hopper.setProfiles(new Profile("profile").setVehicle("car")); + hopper.setProfiles(TestProfiles.constantSpeed("profile")); hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile").setPreparationProfile("xyz") ); @@ -153,9 +122,9 @@ public void unknownLMPreparationProfile_error() { public void lmPreparationProfileChain_error() { final GraphHopper hopper = createHopper(); hopper.setProfiles( - new Profile("profile1").setVehicle("car"), - new Profile("profile2").setVehicle("bike"), - new Profile("profile3").setVehicle("foot") + TestProfiles.constantSpeed("profile1"), + TestProfiles.constantSpeed("profile2"), + TestProfiles.constantSpeed("profile3") ); hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile1"), @@ -169,9 +138,9 @@ public void lmPreparationProfileChain_error() { public void noLMProfileForPreparationProfile_error() { final GraphHopper hopper = createHopper(); hopper.setProfiles( - new Profile("profile1").setVehicle("car"), - new Profile("profile2").setVehicle("bike"), - new Profile("profile3").setVehicle("foot") + TestProfiles.constantSpeed("profile1"), + TestProfiles.constantSpeed("profile2"), + TestProfiles.constantSpeed("profile3") ); hopper.getLMPreparationHandler().setLMProfiles( new LMProfile("profile1").setPreparationProfile("profile2") diff --git a/core/src/test/java/com/graphhopper/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index 568fbcee66d..4ec27343494 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -23,19 +23,13 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.reader.dem.SRTMProvider; import com.graphhopper.reader.dem.SkadiProvider; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.EncodedValueLookup; -import com.graphhopper.routing.ev.RoadEnvironment; -import com.graphhopper.routing.ev.Subnetwork; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.AllEdgesIterator; import com.graphhopper.routing.util.DefaultSnapFilter; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.routing.util.countryrules.CountryRuleFactory; -import com.graphhopper.routing.util.parsers.DefaultTagParserFactory; import com.graphhopper.routing.util.parsers.OSMRoadEnvironmentParser; -import com.graphhopper.routing.util.parsers.TagParser; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.search.KVStorage; import com.graphhopper.storage.IntsRef; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; @@ -65,11 +59,13 @@ import java.util.concurrent.atomic.AtomicInteger; import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.Op.LIMIT; import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.util.GHUtility.createCircle; import static com.graphhopper.util.GHUtility.createRectangle; import static com.graphhopper.util.Parameters.Algorithms.*; import static com.graphhopper.util.Parameters.Curbsides.*; +import static com.graphhopper.util.Parameters.Details.STREET_REF; import static com.graphhopper.util.Parameters.Routing.TIMEOUT_MS; import static com.graphhopper.util.Parameters.Routing.U_TURN_COSTS; import static java.util.Arrays.asList; @@ -104,19 +100,18 @@ public void setup() { @ParameterizedTest @CsvSource({ DIJKSTRA + ",false,705", - ASTAR + ",false,361", - DIJKSTRA_BI + ",false,340", + ASTAR + ",false,362", + DIJKSTRA_BI + ",false,342", ASTAR_BI + ",false,192", - ASTAR_BI + ",true,46", - DIJKSTRA_BI + ",true,51" + DIJKSTRA_BI + ",true,45", + ASTAR_BI + ",true,43", }) public void testMonacoDifferentAlgorithms(String algo, boolean withCH, int expectedVisitedNodes) { - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile("profile").setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("profile", "car")). setStoreOnFlush(true); hopper.getCHPreparationHandler() .setCHProfiles(new CHProfile("profile")); @@ -131,9 +126,9 @@ public void testMonacoDifferentAlgorithms(String algo, boolean withCH, int expec assertEquals(expectedVisitedNodes, rsp.getHints().getLong("visited_nodes.sum", 0)); ResponsePath res = rsp.getBest(); - assertEquals(3586.9, res.getDistance(), .1); - assertEquals(274209, res.getTime(), 10); - assertEquals(91, res.getPoints().size()); + assertEquals(3587.6, res.getDistance(), .1); + assertEquals(274255, res.getTime(), 10); + assertEquals(105, res.getPoints().size()); assertEquals(43.7276852, res.getWaypoints().getLat(0), 1e-7); assertEquals(43.7495432, res.getWaypoints().getLat(1), 1e-7); @@ -148,12 +143,12 @@ public void testMonacoDifferentAlgorithms(String algo, boolean withCH, int expec @Test public void testMonacoWithInstructions() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -161,11 +156,11 @@ public void testMonacoWithInstructions() { setAlgorithm(ASTAR).setProfile(profile)); // identify the number of counts to compare with CH foot route - assertEquals(1022, rsp.getHints().getLong("visited_nodes.sum", 0)); + assertEquals(1040, rsp.getHints().getLong("visited_nodes.sum", 0)); ResponsePath res = rsp.getBest(); - assertEquals(3535, res.getDistance(), 1); - assertEquals(115, res.getPoints().size()); + assertEquals(3536, res.getDistance(), 1); + assertEquals(131, res.getPoints().size()); assertEquals(43.7276852, res.getWaypoints().getLat(0), 1e-7); assertEquals(43.7495432, res.getWaypoints().getLat(1), 1e-7); @@ -176,7 +171,7 @@ public void testMonacoWithInstructions() { // TODO roundabout fine tuning -> enter + leave roundabout (+ two roundabouts -> is it necessary if we do not leave the street?) Translation tr = hopper.getTranslationMap().getWithFallBack(Locale.US); assertEquals("continue onto Avenue des Guelfes", il.get(0).getTurnDescription(tr)); - assertEquals("turn slight left onto Avenue des Papalins", il.get(1).getTurnDescription(tr)); + assertEquals("continue onto Avenue des Papalins", il.get(1).getTurnDescription(tr)); assertEquals("turn sharp right onto Quai Jean-Charles Rey", il.get(4).getTurnDescription(tr)); assertEquals("turn left", il.get(5).getTurnDescription(tr)); assertEquals("turn right onto Avenue Albert II", il.get(6).getTurnDescription(tr)); @@ -195,18 +190,18 @@ public void testMonacoWithInstructions() { assertEquals(7, il.get(4).getTime() / 1000); assertEquals(30, il.get(5).getTime() / 1000); - assertEquals(115, res.getPoints().size()); + assertEquals(131, res.getPoints().size()); } @Test public void withoutInstructions() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -217,30 +212,31 @@ public void withoutInstructions() { // no simplification hopper.getRouterConfig().setSimplifyResponse(false); GHResponse routeRsp = hopper.route(request); - assertEquals(8, routeRsp.getBest().getInstructions().size()); - assertEquals(42, routeRsp.getBest().getPoints().size()); + assertEquals(10, routeRsp.getBest().getInstructions().size()); + assertEquals(52, routeRsp.getBest().getPoints().size()); // with simplification hopper.getRouterConfig().setSimplifyResponse(true); routeRsp = hopper.route(request); - assertEquals(8, routeRsp.getBest().getInstructions().size()); - assertEquals(37, routeRsp.getBest().getPoints().size()); + assertEquals(10, routeRsp.getBest().getInstructions().size()); + assertEquals(50, routeRsp.getBest().getPoints().size()); // no instructions request.getHints().putObject("instructions", false); routeRsp = hopper.route(request); // the path is still simplified - assertEquals(39, routeRsp.getBest().getPoints().size()); + assertEquals(50, routeRsp.getBest().getPoints().size()); } @Test public void testUTurnInstructions() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(true).putHint(U_TURN_COSTS, 20)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car"). + setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"), 20))); hopper.importOrLoad(); Translation tr = hopper.getTranslationMap().getWithFallBack(Locale.US); @@ -256,7 +252,7 @@ public void testUTurnInstructions() { ResponsePath res = rsp.getBest(); assertEquals(286, res.getDistance(), 1); // note that this includes the u-turn time for the second u-turn, but not the first, because it's a waypoint! - assertEquals(54351, res.getTime(), 1); + assertEquals(34358, res.getTime(), 1); // the route follows Avenue de l'Annonciade to the waypoint, u-turns there, then does a sharp right turn onto the parallel (dead-end) road, // does a u-turn at the dead-end and then arrives at the destination InstructionList il = res.getInstructions(); @@ -292,9 +288,9 @@ public void testUTurnInstructions() { } private void testImportCloseAndLoad(boolean ch, boolean lm) { - final String vehicle = "foot"; final String profileName = "profile"; GraphHopper hopper = new GraphHopper(). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). setStoreOnFlush(true); @@ -306,10 +302,10 @@ private void testImportCloseAndLoad(boolean ch, boolean lm) { new Coordinate(7.4198, 43.7355), new Coordinate(7.4207, 43.7344), new Coordinate(7.4174, 43.7345)})); - CustomModel customModel = new CustomModel().setDistanceInfluence(0d); + Profile profile = TestProfiles.accessSpeedAndPriority(profileName, "foot"); + CustomModel customModel = profile.getCustomModel(); customModel.getPriority().add(If("in_area51", MULTIPLY, "0.1")); customModel.getAreas().getFeatures().add(area51Feature); - Profile profile = new Profile(profileName).setCustomModel(customModel).setVehicle(vehicle); hopper.setProfiles(profile); if (ch) { @@ -351,8 +347,8 @@ private void testImportCloseAndLoad(boolean ch, boolean lm) { long sum = rsp.getHints().getLong("visited_nodes.sum", 0); assertNotEquals(sum, 0); assertTrue(sum < 155, "Too many nodes visited " + sum); - assertEquals(3535, bestPath.getDistance(), 1); - assertEquals(115, bestPath.getPoints().size()); + assertEquals(3536, bestPath.getDistance(), 1); + assertEquals(131, bestPath.getPoints().size()); } if (lm) { @@ -367,8 +363,8 @@ private void testImportCloseAndLoad(boolean ch, boolean lm) { long sum = rsp.getHints().getLong("visited_nodes.sum", 0); assertNotEquals(sum, 0); assertTrue(sum < 125, "Too many nodes visited " + sum); - assertEquals(3535, bestPath.getDistance(), 1); - assertEquals(115, bestPath.getPoints().size()); + assertEquals(3536, bestPath.getDistance(), 1); + assertEquals(131, bestPath.getPoints().size()); } // flexible @@ -382,8 +378,8 @@ private void testImportCloseAndLoad(boolean ch, boolean lm) { long sum = rsp.getHints().getLong("visited_nodes.sum", 0); assertNotEquals(sum, 0); assertTrue(sum > 120, "Too few nodes visited " + sum); - assertEquals(3535, bestPath.getDistance(), 1); - assertEquals(115, bestPath.getPoints().size()); + assertEquals(3536, bestPath.getDistance(), 1); + assertEquals(131, bestPath.getPoints().size()); hopper.close(); } @@ -403,6 +399,11 @@ public void testImportThenLoadCHLM() { testImportCloseAndLoad(true, true); } + @Test + public void testImportThenLoadCHLMAndSort() { + testImportCloseAndLoad(true, true); + } + @Test public void testImportThenLoadFlexible() { testImportCloseAndLoad(false, false); @@ -411,12 +412,12 @@ public void testImportThenLoadFlexible() { @Test public void testAlternativeRoutes() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed, foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -443,39 +444,38 @@ public void testAlternativeRoutes() { @Test public void testAlternativeRoutesBike() { - final String profile = "profile"; - final String vehicle = "bike"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setEncodedValuesString("car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority("bike", "bike")); hopper.importOrLoad(); - GHRequest req = new GHRequest(50.028917, 11.496506, 49.985228, 11.600876). - setAlgorithm(ALT_ROUTE).setProfile(profile); + GHRequest req = new GHRequest(50.028917, 11.496506, 49.981979, 11.591156). + setAlgorithm(ALT_ROUTE).setProfile("bike"); + req.putHint("alternative_route.max_paths", 3); GHResponse rsp = hopper.route(req); assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); assertEquals(3, rsp.getAll().size()); + // via obergräfenthal + assertEquals(2651, rsp.getAll().get(0).getTime() / 1000); // via ramsenthal - assertEquals(2888, rsp.getAll().get(0).getTime() / 1000); + assertEquals(2782, rsp.getAll().get(1).getTime() / 1000); // via unterwaiz - assertEquals(3318, rsp.getAll().get(1).getTime() / 1000); - // via eselslohe -> theta; BTW: here smaller time as 2nd alternative due to priority influences time order - assertEquals(3116, rsp.getAll().get(2).getTime() / 1000); + assertEquals(2872, rsp.getAll().get(2).getTime() / 1000); } @Test public void testAlternativeRoutesCar() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")); hopper.importOrLoad(); GHRequest req = new GHRequest(50.023513, 11.548862, 49.969441, 11.537876). @@ -497,18 +497,16 @@ public void testAlternativeRoutesCar() { @Test public void testPointHint() { final String profile = "profile"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(LAUF). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setEncodedValuesString("car_access,car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")); hopper.importOrLoad(); GHRequest req = new GHRequest(49.46553, 11.154669, 49.465244, 11.152577). setProfile(profile); - req.setPointHints(new ArrayList<>(asList("Laufamholzstraße, 90482, Nürnberg, Deutschland", ""))); GHResponse rsp = hopper.route(req); assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); @@ -538,7 +536,8 @@ public void testForwardBackwardDestination() { GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAUTZEN). - setProfiles(new Profile(profile).setVehicle("car")); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")); hopper.setMinNetworkSize(0); hopper.importOrLoad(); @@ -548,6 +547,7 @@ public void testForwardBackwardDestination() { assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); assertEquals("keep right and take B 96 toward Bautzen-West, Hoyerswerda", rsp.getBest().getInstructions().get(1).getTurnDescription(tr)); + assertEquals("Bautzen-West", rsp.getBest().getInstructions().get(1).getExtraInfoJSON().get("motorway_junction")); assertEquals("turn left onto Hoyerswerdaer Straße and drive toward Hoyerswerda, Kleinwelka", rsp.getBest().getInstructions().get(2).getTurnDescription(tr)); @@ -559,14 +559,15 @@ public void testForwardBackwardDestination() { @Test public void testNorthBayreuthAccessDestination() { final String profile = "profile"; - final String vehicle = "car"; + + Profile p = TestProfiles.accessAndSpeed(profile, "car"); + p.getCustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1")); GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile). - setCustomModel(new CustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1"))). - setVehicle(vehicle)); + setEncodedValuesString("car_access, road_access, car_average_speed"). + setProfiles(p); hopper.importOrLoad(); GHRequest req = new GHRequest(49.985307, 11.50628, 49.985731, 11.507465). @@ -580,12 +581,12 @@ public void testNorthBayreuthAccessDestination() { @Test public void testNorthBayreuthBlockedEdges() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")); hopper.importOrLoad(); GHRequest req = new GHRequest(49.985272, 11.506151, 49.986107, 11.507202). @@ -693,18 +694,16 @@ public void testNorthBayreuthBlockedEdges() { @Test public void testCustomModel() { - final String vehicle = "car"; final String customCar = "custom_car"; final String emptyCar = "empty_car"; - CustomModel customModel = new CustomModel(); - customModel.addToSpeed(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "0.1")); + Profile p1 = TestProfiles.accessAndSpeed(customCar, "car"); + p1.getCustomModel().addToSpeed(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "0.1")); + Profile p2 = TestProfiles.accessAndSpeed(emptyCar, "car"); GraphHopper hopper = new GraphHopper(). + setEncodedValuesString("car_average_speed,car_access,road_class"). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles( - new Profile(emptyCar).setCustomModel(new CustomModel()).setVehicle(vehicle), - new Profile(customCar).setCustomModel(customModel).setVehicle(vehicle) - ). + setProfiles(p1, p2). importOrLoad(); // standard car route @@ -712,7 +711,7 @@ public void testCustomModel() { // the custom car takes a detour in the north to avoid tertiary roads assertDistance(hopper, customCar, null, 13223); // we can achieve the same by using the empty profile and using a client-side model, we just need to copy the model because of the internal flag - assertDistance(hopper, emptyCar, new CustomModel(customModel), 13223); + assertDistance(hopper, emptyCar, new CustomModel(p1.getCustomModel()), 13223); // now we prevent using unclassified roads as well and the route goes even further north CustomModel strictCustomModel = new CustomModel().addToSpeed( If("road_class == TERTIARY || road_class == TRACK || road_class == UNCLASSIFIED", MULTIPLY, "0.1")); @@ -740,12 +739,14 @@ private void assertDistance(GraphHopper hopper, String profile, CustomModel cust @Test public void testMonacoVia() { final String profile = "profile"; - final String vehicle = "foot"; + Profile p = TestProfiles.accessSpeedAndPriority(profile, "foot"); + p.getCustomModel().setDistanceInfluence(10_000d); GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(p). setStoreOnFlush(true). importOrLoad(); @@ -757,13 +758,13 @@ public void testMonacoVia() { setAlgorithm(ASTAR).setProfile(profile)); ResponsePath res = rsp.getBest(); - assertEquals(6874.2, res.getDistance(), .1); - assertEquals(170, res.getPoints().size()); + assertEquals(6874, res.getDistance(), 1); + assertEquals(197, res.getPoints().size()); InstructionList il = res.getInstructions(); assertEquals(30, il.size()); assertEquals("continue onto Avenue des Guelfes", il.get(0).getTurnDescription(tr)); - assertEquals("turn slight left onto Avenue des Papalins", il.get(1).getTurnDescription(tr)); + assertEquals("continue onto Avenue des Papalins", il.get(1).getTurnDescription(tr)); assertEquals("turn sharp right onto Quai Jean-Charles Rey", il.get(4).getTurnDescription(tr)); assertEquals("turn left", il.get(5).getTurnDescription(tr)); assertEquals("turn right onto Avenue Albert II", il.get(6).getTurnDescription(tr)); @@ -775,7 +776,7 @@ public void testMonacoVia() { assertEquals("turn left", il.get(24).getTurnDescription(tr)); assertEquals("turn right onto Quai Jean-Charles Rey", il.get(25).getTurnDescription(tr)); assertEquals("turn sharp left onto Avenue des Papalins", il.get(26).getTurnDescription(tr)); - assertEquals("turn slight right onto Avenue des Guelfes", il.get(28).getTurnDescription(tr)); + assertEquals("continue onto Avenue des Guelfes", il.get(28).getTurnDescription(tr)); assertEquals("arrive at destination", il.get(29).getTurnDescription(tr)); assertEquals(11, il.get(0).getDistance(), 1); @@ -826,12 +827,12 @@ public void testMonacoVia() { @Test public void testMonacoPathDetails() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -858,12 +859,12 @@ public void testMonacoPathDetails() { @Test public void testMonacoEnforcedDirection() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -876,8 +877,8 @@ public void testMonacoEnforcedDirection() { GHResponse rsp = hopper.route(req); ResponsePath res = rsp.getBest(); - assertEquals(921, res.getDistance(), 10.); - assertEquals(36, res.getPoints().size()); + assertEquals(575, res.getDistance(), 10.); + assertEquals(26, res.getPoints().size()); // headings must be in [0, 360) req = new GHRequest(). @@ -906,11 +907,11 @@ public void testMonacoEnforcedDirection() { @Test public void testHeading() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setStoreOnFlush(true). importOrLoad(); @@ -922,26 +923,26 @@ public void testHeading() { GHResponse rsp = hopper.route(req); assertFalse(rsp.hasErrors()); assertEquals(138, rsp.getBest().getDistance(), 1); - assertEquals(17, rsp.getBest().getRouteWeight(), 1); + assertEquals(166, rsp.getBest().getRouteWeight()); assertEquals(17000, rsp.getBest().getTime(), 1000); // with heading req.setHeadings(Arrays.asList(100., 0.)); rsp = hopper.route(req); assertFalse(rsp.hasErrors()); assertEquals(138, rsp.getBest().getDistance(), 1); - assertEquals(317, rsp.getBest().getRouteWeight(), 1); + assertEquals(3166, rsp.getBest().getRouteWeight()); assertEquals(17000, rsp.getBest().getTime(), 1000); } @Test public void testMonacoMaxVisitedNodes() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -953,7 +954,7 @@ public void testMonacoMaxVisitedNodes() { assertTrue(rsp.hasErrors()); Throwable throwable = rsp.getErrors().get(0); - assertTrue(throwable instanceof MaximumNodesExceededException); + assertInstanceOf(MaximumNodesExceededException.class, throwable); Object nodesDetail = ((MaximumNodesExceededException) throwable).getDetails().get(MaximumNodesExceededException.NODES_KEY); assertEquals(5, nodesDetail); @@ -966,12 +967,11 @@ public void testMonacoMaxVisitedNodes() { @Test public void testMonacoNonChMaxWaypointDistance() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setStoreOnFlush(true). importOrLoad(); @@ -998,12 +998,11 @@ public void testMonacoNonChMaxWaypointDistance() { @Test public void testMonacoNonChMaxWaypointDistanceMultiplePoints() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setStoreOnFlush(true). importOrLoad(); @@ -1037,12 +1036,12 @@ public void testMonacoNonChMaxWaypointDistanceMultiplePoints() { @Test public void testMonacoStraightVia() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -1056,7 +1055,7 @@ public void testMonacoStraightVia() { ResponsePath res = rsp.getBest(); assertEquals(297, res.getDistance(), 5.); - assertEquals(23, res.getPoints().size()); + assertEquals(25, res.getPoints().size()); // test if start and first point are identical leading to an empty path, #788 rq = new GHRequest(). @@ -1072,12 +1071,12 @@ public void testMonacoStraightVia() { @Test public void testSRTMWithInstructions() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true); hopper.setElevationProvider(new SRTMProvider(DIR)); @@ -1087,45 +1086,39 @@ public void testSRTMWithInstructions() { setAlgorithm(ASTAR).setProfile(profile)); ResponsePath res = rsp.getBest(); - assertEquals(1614.3, res.getDistance(), .1); - assertEquals(55, res.getPoints().size()); + assertEquals(1616.8, res.getDistance(), .1); + assertEquals(68, res.getPoints().size()); assertTrue(res.getPoints().is3D()); InstructionList il = res.getInstructions(); assertEquals(12, il.size()); assertTrue(il.get(0).getPoints().is3D()); - String str = res.getPoints().toString(); - - assertEquals("(43.730684662577524,7.421283725164733,62.0), (43.7306797,7.4213823,66.0), " + - "(43.731098,7.4215463,45.0), (43.7312991,7.42159,45.0), (43.7313271,7.4214147,45.0), " + - "(43.7312506,7.4213664,45.0), (43.7312822,7.4211156,52.0), (43.7313624,7.4211455,52.0), " + - "(43.7313714,7.4211233,52.0), (43.7314858,7.4211734,52.0), (43.7315753,7.4208688,52.0), " + - "(43.7316061,7.4208249,52.0), (43.7316404,7.4208503,52.0), (43.7316741,7.4210502,52.0), " + - "(43.7316276,7.4214636,45.0), (43.7316391,7.4215065,45.0), (43.7316664,7.4214904,45.0), " + - "(43.7317185,7.4211861,52.0), (43.7319676,7.4206159,19.0), (43.732038,7.4203936,20.0), " + - "(43.7322266,7.4196414,26.0), (43.7323236,7.4192656,26.0), (43.7323374,7.4190461,26.0), " + - "(43.7323875,7.4189195,26.0), (43.731974,7.4181688,29.0), (43.7316421,7.4173042,23.0), " + - "(43.7315686,7.4170356,31.0), (43.7314269,7.4166815,31.0), (43.7312401,7.4163184,49.0), " + - "(43.7308286,7.4157613,29.399999618530273), (43.730662,7.4155599,22.0), " + - "(43.7303643,7.4151193,51.0), (43.729579,7.4137274,40.0), (43.7295167,7.4137244,40.0), " + - "(43.7294669,7.4137725,40.0), (43.7285987,7.4149068,23.0), (43.7285167,7.4149272,22.0), " + - "(43.7283974,7.4148646,22.0), (43.7285619,7.4151365,23.0), (43.7285774,7.4152444,23.0), " + - "(43.7285763,7.4159759,21.0), (43.7285238,7.4161982,20.0), (43.7284592,7.4163655,18.0), " + - "(43.7281669,7.4168192,18.0), (43.7281442,7.4169449,18.0), (43.7281684,7.4172435,14.0), " + - "(43.7282784,7.4179606,14.0), (43.7282757,7.418175,11.0), (43.7282319,7.4183683,11.0), " + - "(43.7281482,7.4185473,11.0), (43.7280654,7.4186535,11.0), (43.7279259,7.418748,11.0), " + - "(43.727779,7.4187731,11.0), (43.7276825,7.4190072,11.0), " + - "(43.72767974015672,7.419198523220426,11.0)", str); - - assertEquals(84, res.getAscend(), 1e-1); + assertEquals(Helper.createPointList3D(43.730684691859956, 7.4212837037152255, 63.743792110420685, 43.7306797, 7.4213823, 66.0, 43.730949, 7.4214948, 66.0, + 43.731098, 7.4215463, 45.0, 43.731227, 7.4215824, 45.0, 43.7312991, 7.42159, 45.0, 43.7313271, 7.4214147, 45.0, + 43.7312506, 7.4213664, 45.0, 43.7312546, 7.4212741, 52.0, 43.7312823, 7.4211156, 52.0, 43.7313624, 7.4211455, 52.0, + 43.7313714, 7.4211233, 52.0, 43.7314858, 7.4211734, 52.0, 43.7315522, 7.4209778, 52.0, 43.7315753, 7.4208688, 52.0, + 43.7316061, 7.4208249, 52.0, 43.7316404, 7.4208503, 52.0, 43.7316741, 7.4210502, 52.0, 43.7316276, 7.4214636, 45.0, + 43.7316392, 7.4215065, 45.0, 43.7316664, 7.4214904, 45.0, 43.7316982, 7.4212652, 52.0, 43.7317185, 7.4211861, 52.0, + 43.7319676, 7.4206159, 19.0, 43.732038, 7.4203936, 20.0, 43.732173, 7.4198886, 20.0, 43.7322266, 7.4196414, 26.0, + 43.732266, 7.4194654, 26.0, 43.7323236, 7.4192656, 26.0, 43.7323374, 7.4191503, 26.0, 43.7323374, 7.4190461, 26.0, + 43.7323875, 7.4189195, 26.0, 43.7323444, 7.4188579, 26.0, 43.731974, 7.4181688, 29.0, 43.7316421, 7.4173042, 23.0, + 43.7315686, 7.4170356, 31.0, 43.7314269, 7.4166815, 31.0, 43.7312401, 7.4163184, 49.0, 43.7308286, 7.4157613, 29.4000244140625, + 43.730662, 7.4155599, 22.0, 43.7303643, 7.4151193, 51.0, 43.729579, 7.4137274, 40.0, 43.7295167, 7.4137244, 40.0, 43.7294669, 7.4137725, 40.0, + 43.7285987, 7.4149068, 23.0, 43.7285167, 7.4149272, 22.0, 43.7283974, 7.4148646, 22.0, 43.7285619, 7.4151365, 23.0, 43.7285774, 7.4152444, 23.0, + 43.7285863, 7.4157656, 21.0, 43.7285763, 7.4159759, 21.0, 43.7285238, 7.4161982, 20.0, 43.7284592, 7.4163655, 18.0, 43.72838, 7.4165003, 18.0, + 43.7281669, 7.4168192, 18.0, 43.7281442, 7.4169449, 18.0, 43.7281477, 7.4170695, 18.0, 43.7281684, 7.4172435, 14.0, 43.7282784, 7.4179606, 14.0, + 43.7282757, 7.418175, 11.0, 43.7282319, 7.4183683, 11.0, 43.7281482, 7.4185473, 11.0, 43.7280654, 7.4186535, 11.0, 43.727926, 7.418748, 11.0, + 43.7278398, 7.4187697, 11.0, 43.727779, 7.4187731, 11.0, 43.7276825, 7.4190072, 11.0, 43.72767974015672, 7.419198523220426, 11.0), res.getPoints()); + + assertEquals(82.3, res.getAscend(), 1e-1); assertEquals(135, res.getDescend(), 1e-1); - assertEquals(55, res.getPoints().size()); - assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 62.0), res.getPoints().get(0)); + assertEquals(68, res.getPoints().size()); + assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 63.74379211), res.getPoints().get(0)); assertEquals(new GHPoint3D(43.727679637988224, 7.419198521975086, 11.0), res.getPoints().get(res.getPoints().size() - 1)); - assertEquals(62, res.getPoints().get(0).getEle(), 1e-2); + assertEquals(63.74, res.getPoints().get(0).getEle(), 1e-2); assertEquals(66, res.getPoints().get(1).getEle(), 1e-2); assertEquals(52, res.getPoints().get(10).getEle(), 1e-2); } @@ -1134,30 +1127,30 @@ public void testSRTMWithInstructions() { @ValueSource(booleans = {true, false}) public void testSRTMWithTunnelInterpolation(boolean withTunnelInterpolation) { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true); if (!withTunnelInterpolation) { - hopper.setTagParserFactory(new DefaultTagParserFactory() { + hopper.setImportRegistry(new DefaultImportRegistry() { @Override - public TagParser create(EncodedValueLookup lookup, String name, PMap properties) { - TagParser parser = super.create(lookup, name, properties); - if (name.equals("road_environment")) - parser = new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)) { - @Override - public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) { - // do not change RoadEnvironment to avoid triggering tunnel interpolation - } - }; - return parser; + public ImportUnit createImportUnit(String name) { + ImportUnit importUnit = super.createImportUnit(name); + if ("road_environment".equals(name)) + importUnit = ImportUnit.create(name, props -> RoadEnvironment.create(), + (lookup, props) -> new OSMRoadEnvironmentParser(lookup.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class)) { + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay readerWay, IntsRef relationFlags) { + // do not change RoadEnvironment to avoid triggering tunnel interpolation + } + }); + return importUnit; } }); - hopper.setEncodedValuesString("road_environment"); } hopper.setElevationProvider(new SRTMProvider(DIR)); @@ -1199,13 +1192,13 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay rea @Test public void testSRTMWithLongEdgeSampling() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). setStoreOnFlush(true). - setProfiles(new Profile("profile").setVehicle(vehicle)); + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority("profile", "foot")); hopper.getRouterConfig().setElevationWayPointMaxDistance(1.); hopper.getReaderConfig(). setElevationMaxWayPointDistance(1.). @@ -1220,38 +1213,35 @@ public void testSRTMWithLongEdgeSampling() { setAlgorithm(ASTAR).setProfile(profile)); ResponsePath arsp = rsp.getBest(); - assertEquals(1569.7, arsp.getDistance(), .1); - assertEquals(60, arsp.getPoints().size()); + assertEquals(1570, arsp.getDistance(), 1); + assertEquals(74, arsp.getPoints().size()); assertTrue(arsp.getPoints().is3D()); InstructionList il = arsp.getInstructions(); assertEquals(12, il.size()); assertTrue(il.get(0).getPoints().is3D()); - String str = arsp.getPoints().toString(); - - assertEquals(23.8, arsp.getAscend(), 1e-1); + assertEquals(22.8, arsp.getAscend(), 1e-1); assertEquals(67.4, arsp.getDescend(), 1e-1); - assertEquals(60, arsp.getPoints().size()); - assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 55.82900047302246), arsp.getPoints().get(0)); - assertEquals(new GHPoint3D(43.727679637988224, 7.419198521975086, 12.274499893188477), arsp.getPoints().get(arsp.getPoints().size() - 1)); + assertEquals(74, arsp.getPoints().size()); + assertEquals(new GHPoint3D(43.73068455771767, 7.421283689825812, 56.68), arsp.getPoints().get(0)); + assertEquals(new GHPoint3D(43.727679637988224, 7.419198521975086, 12.11), arsp.getPoints().get(arsp.getPoints().size() - 1)); - assertEquals(55.83, arsp.getPoints().get(0).getEle(), 1e-2); + assertEquals(56.68, arsp.getPoints().get(0).getEle(), 1e-2); assertEquals(57.78, arsp.getPoints().get(1).getEle(), 1e-2); - assertEquals(52.43, arsp.getPoints().get(10).getEle(), 1e-2); + assertEquals(53.62, arsp.getPoints().get(10).getEle(), 1e-2); } @Disabled @Test public void testSkadiElevationProvider() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true); hopper.setElevationProvider(new SkadiProvider(DIR)); @@ -1277,9 +1267,10 @@ public void testKremsCyclewayInstructionsWithWayTypeInfo() { GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(KREMS). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed, bike_access, bike_priority, bike_average_speed"). setProfiles( - new Profile(footProfile).setVehicle("foot"), - new Profile(bikeProfile).setVehicle("bike")). + TestProfiles.accessSpeedAndPriority(footProfile, "foot"), + TestProfiles.accessSpeedAndPriority(bikeProfile, "bike")). setStoreOnFlush(true). importOrLoad(); @@ -1288,11 +1279,11 @@ public void testKremsCyclewayInstructionsWithWayTypeInfo() { setProfile(bikeProfile)); assertFalse(rsp.hasErrors()); ResponsePath res = rsp.getBest(); - assertEquals(6931.8, res.getDistance(), .1); - assertEquals(103, res.getPoints().size()); + assertEquals(7007.7, res.getDistance(), .1); + assertEquals(136, res.getPoints().size()); InstructionList il = res.getInstructions(); - assertEquals(19, il.size()); + assertEquals(25, il.size()); assertEquals("continue onto Obere Landstraße", il.get(0).getTurnDescription(tr)); assertEquals(69.28, (Double) il.get(0).getExtraInfoJSON().get("heading"), .01); @@ -1303,12 +1294,9 @@ public void testKremsCyclewayInstructionsWithWayTypeInfo() { assertEquals("keep left onto Hoher Markt", il.get(4).getTurnDescription(tr)); assertEquals("turn right onto Wegscheid", il.get(6).getTurnDescription(tr)); assertEquals("continue onto Wegscheid", il.get(7).getTurnDescription(tr)); - assertEquals("turn right onto Ringstraße", il.get(8).getTurnDescription(tr)); - assertEquals("keep left onto Eyblparkstraße", il.get(9).getTurnDescription(tr)); - assertEquals("keep left onto Austraße", il.get(10).getTurnDescription(tr)); - assertEquals("keep left onto Rechte Kremszeile", il.get(11).getTurnDescription(tr)); + assertEquals("at roundabout, take exit 1 onto Hohensteinstraße", il.get(8).getTurnDescription(tr)); //.. - assertEquals("turn right onto Treppelweg", il.get(15).getTurnDescription(tr)); + assertEquals("turn right onto Treppelweg", il.get(21).getTurnDescription(tr)); rsp = hopper.route(new GHRequest(48.410987, 15.599492, 48.411172, 15.600371). setAlgorithm(ASTAR).setProfile(footProfile)); @@ -1321,15 +1309,14 @@ public void testKremsCyclewayInstructionsWithWayTypeInfo() { public void testRoundaboutInstructionsWithCH() { final String profile1 = "my_profile"; final String profile2 = "your_profile"; - final String vehicle1 = "car"; - final String vehicle2 = "bike"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). setProfiles(Arrays.asList( - new Profile(profile1).setVehicle(vehicle1), - new Profile(profile2).setVehicle(vehicle2)) + TestProfiles.accessAndSpeed(profile1, "car"), + TestProfiles.accessSpeedAndPriority(profile2, "bike")) ). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles( @@ -1367,15 +1354,14 @@ public void testRoundaboutInstructionsWithCH() { public void testCircularJunctionInstructionsWithCH() { String profile1 = "profile1"; String profile2 = "profile2"; - String vehicle1 = "car"; - String vehicle2 = "bike"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BERLIN). + setEncodedValuesString("car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). setProfiles( - new Profile(profile1).setVehicle(vehicle1), - new Profile(profile2).setVehicle(vehicle2) + TestProfiles.accessAndSpeed(profile1, "car"), + TestProfiles.accessSpeedAndPriority(profile2, "bike") ). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles( @@ -1400,12 +1386,13 @@ public void testMultipleVehiclesWithCH() { final String bikeProfile = "bike_profile"; final String carProfile = "car_profile"; List profiles = asList( - new Profile(bikeProfile).setVehicle("bike"), - new Profile(carProfile).setVehicle("car") + TestProfiles.accessSpeedAndPriority(bikeProfile, "bike"), + TestProfiles.accessAndSpeed(carProfile, "car") ); GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). + setEncodedValuesString("bike_access, bike_priority, bike_average_speed, car_access, car_average_speed"). setProfiles(profiles). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles( @@ -1424,8 +1411,8 @@ public void testMultipleVehiclesWithCH() { .setProfile(bikeProfile)); res = rsp.getBest(); assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); - assertEquals(536, res.getTime() / 1000f, 1); - assertEquals(2521, res.getDistance(), 1); + assertEquals(500, res.getTime() / 1000f, 1); + assertEquals(2211, res.getDistance(), 1); rsp = hopper.route(new GHRequest(43.73005, 7.415707, 43.741522, 7.42826) .setProfile("profile3")); @@ -1452,12 +1439,12 @@ public void testIfCHIsUsed() { private void executeCHFootRoute() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed, foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); hopper.importOrLoad(); @@ -1471,8 +1458,8 @@ private void executeCHFootRoute() { long sum = rsp.getHints().getLong("visited_nodes.sum", 0); assertNotEquals(sum, 0); assertTrue(sum < 147, "Too many nodes visited " + sum); - assertEquals(3535, bestPath.getDistance(), 1); - assertEquals(115, bestPath.getPoints().size()); + assertEquals(3536, bestPath.getDistance(), 1); + assertEquals(131, bestPath.getPoints().size()); hopper.close(); } @@ -1480,12 +1467,12 @@ private void executeCHFootRoute() { @Test public void testRoundTour() { final String profile = "profile"; - final String vehicle = "foot"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setStoreOnFlush(true). importOrLoad(); @@ -1501,21 +1488,20 @@ public void testRoundTour() { assertEquals(1, rsp.getAll().size()); ResponsePath res = rsp.getBest(); - assertEquals(1.49, rsp.getBest().getDistance() / 1000f, .01); + assertEquals(1.47, rsp.getBest().getDistance() / 1000f, .01); assertEquals(19, rsp.getBest().getTime() / 1000f / 60, 1); - assertEquals(66, res.getPoints().size()); + assertEquals(68, res.getPoints().size()); } @Test public void testPathDetails1216() { final String profile = "profile"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")); hopper.importOrLoad(); GHRequest req = new GHRequest(). @@ -1535,12 +1521,11 @@ public void testPathDetails1216() { @Test public void testPathDetailsSamePoint() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle(vehicle)); + setProfiles(TestProfiles.constantSpeed(profile)); hopper.importOrLoad(); GHRequest req = new GHRequest(). @@ -1557,19 +1542,19 @@ public void testPathDetailsSamePoint() { @Test public void testFlexMode_631() { final String profile = "car_profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setStoreOnFlush(true); hopper.getCHPreparationHandler(). setCHProfiles(new CHProfile(profile)); hopper.getLMPreparationHandler(). - setLMProfiles(new LMProfile(profile).setMaximumLMWeight(2000)); + setLMProfiles(new LMProfile(profile).setMaximumLMWeight(20000)); hopper.importOrLoad(); @@ -1584,7 +1569,7 @@ public void testFlexMode_631() { assertTrue(chSum < 70, "Too many visited nodes for ch mode " + chSum); ResponsePath bestPath = rsp.getBest(); assertEquals(3587, bestPath.getDistance(), 1); - assertEquals(91, bestPath.getPoints().size()); + assertEquals(105, bestPath.getPoints().size()); // request flex mode req.setAlgorithm(Parameters.Algorithms.ASTAR_BI); @@ -1596,7 +1581,7 @@ public void testFlexMode_631() { bestPath = rsp.getBest(); assertEquals(3587, bestPath.getDistance(), 1); - assertEquals(91, bestPath.getPoints().size()); + assertEquals(105, bestPath.getPoints().size()); // request hybrid mode req.putHint(Landmark.DISABLE, false); @@ -1610,7 +1595,7 @@ public void testFlexMode_631() { bestPath = rsp.getBest(); assertEquals(3587, bestPath.getDistance(), 1); - assertEquals(91, bestPath.getPoints().size()); + assertEquals(105, bestPath.getPoints().size()); // note: combining hybrid & speed mode is currently not possible and should be avoided: #1082 } @@ -1620,14 +1605,17 @@ public void testCrossQuery() { final String profile1 = "p1"; final String profile2 = "p2"; final String profile3 = "p3"; + Profile p1 = TestProfiles.accessAndSpeed(profile1, "car"); + Profile p2 = TestProfiles.accessAndSpeed(profile2, "car"); + Profile p3 = TestProfiles.accessAndSpeed(profile3, "car"); + p1.getCustomModel().setDistanceInfluence(70d); + p2.getCustomModel().setDistanceInfluence(100d); + p3.getCustomModel().setDistanceInfluence(150d); GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles( - new Profile(profile1).setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("car"), - new Profile(profile2).setCustomModel(new CustomModel().setDistanceInfluence(100d)).setVehicle("car"), - new Profile(profile3).setCustomModel(new CustomModel().setDistanceInfluence(150d)).setVehicle("car") - ). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(p1, p2, p3). setStoreOnFlush(true); hopper.getLMPreparationHandler(). @@ -1642,32 +1630,35 @@ public void testCrossQuery() { hopper.importOrLoad(); // flex - testCrossQueryAssert(profile1, hopper, 525.3, 196, true); - testCrossQueryAssert(profile2, hopper, 632.9, 198, true); - testCrossQueryAssert(profile3, hopper, 812.2, 198, true); + testCrossQueryAssert(profile1, hopper, 5255, 196, true); + testCrossQueryAssert(profile2, hopper, 6330, 198, true); + testCrossQueryAssert(profile3, hopper, 8126, 198, true); // LM (should be the same as flex, but with less visited nodes!) - testCrossQueryAssert(profile1, hopper, 525.3, 108, false); - testCrossQueryAssert(profile2, hopper, 632.9, 126, false); - testCrossQueryAssert(profile3, hopper, 812.2, 192, false); + testCrossQueryAssert(profile1, hopper, 5255, 108, false); + testCrossQueryAssert(profile2, hopper, 6330, 126, false); + testCrossQueryAssert(profile3, hopper, 8126, 192, false); } private void testCrossQueryAssert(String profile, GraphHopper hopper, double expectedWeight, int expectedVisitedNodes, boolean disableLM) { GHResponse response = hopper.route(new GHRequest(43.727687, 7.418737, 43.74958, 7.436566). setProfile(profile).putHint("lm.disable", disableLM)); - assertEquals(expectedWeight, response.getBest().getRouteWeight(), 0.1); + assertEquals(expectedWeight, response.getBest().getRouteWeight()); int visitedNodes = response.getHints().getInt("visited_nodes.sum", 0); assertEquals(expectedVisitedNodes, visitedNodes); } @Test public void testLMConstraints() { + Profile p1 = TestProfiles.accessAndSpeed("p1", "car"); + Profile p2 = TestProfiles.accessAndSpeed("p2", "car"); + p1.getCustomModel().setDistanceInfluence(100d); + p2.getCustomModel().setDistanceInfluence(100d); GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles( - new Profile("p1").setCustomModel(new CustomModel().setDistanceInfluence(100d)).setVehicle("car"), - new Profile("p2").setCustomModel(new CustomModel().setDistanceInfluence(100d)).setVehicle("car")). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(p1, p2). setStoreOnFlush(true); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("p1")); @@ -1693,13 +1684,14 @@ public void testLMConstraints() { assertFalse(response.hasErrors(), response.getErrors().toString()); assertEquals(3587, response.getBest().getDistance(), 1); - // currently required to disable LM for p2 too, see #1904 (default is LM for *all* profiles once LM preparation is enabled for any profile) + // p2 has no LM and so no lm.disable=true is required response = hopper.route(new GHRequest(43.727687, 7.418737, 43.74958, 7.436566). setCustomModel(customModel). setProfile("p2")); - assertTrue(response.getErrors().get(0).toString().contains("Cannot find LM preparation for the requested profile: 'p2'"), response.getErrors().toString()); - assertEquals(IllegalArgumentException.class, response.getErrors().get(0).getClass()); + assertFalse(response.hasErrors(), response.getErrors().toString()); + assertEquals(3587, response.getBest().getDistance(), 1); + // but still works response = hopper.route(new GHRequest(43.727687, 7.418737, 43.74958, 7.436566). setCustomModel(customModel). setProfile("p2").putHint("lm.disable", true)); @@ -1709,36 +1701,34 @@ public void testLMConstraints() { @Test public void testCreateWeightingHintsMerging() { - final String profile = "profile"; - final String vehicle = "mtb"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(true).putHint(U_TURN_COSTS, 123)); + setEncodedValuesString("car_access, car_average_speed, mtb_access, mtb_priority, mtb_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority("profile", "mtb").setTurnCostsConfig(new TurnCostsConfig(List.of("bicycle"), 123))); hopper.importOrLoad(); // if we do not pass u_turn_costs with the request hints we get those from the profile Weighting w = hopper.createWeighting(hopper.getProfiles().get(0), new PMap()); - assertEquals(123.0, w.calcTurnWeight(5, 6, 5)); + assertEquals(1230, w.calcTurnWeight(5, 6, 5)); - // we can overwrite the u_turn_costs given in the profile + // we can no longer overwrite the u_turn_costs w = hopper.createWeighting(hopper.getProfiles().get(0), new PMap().putObject(U_TURN_COSTS, 46)); - assertEquals(46.0, w.calcTurnWeight(5, 6, 5)); + assertEquals(460, w.calcTurnWeight(5, 6, 5)); } @Test public void testPreparedProfileNotAvailable() { final String profile1 = "fast_profile"; final String profile2 = "short_fast_profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed"). setProfiles( - new Profile(profile1).setVehicle(vehicle), - new Profile(profile2).setVehicle(vehicle) + TestProfiles.accessAndSpeed(profile1, "car"), + TestProfiles.accessAndSpeed(profile2, "car") ). setStoreOnFlush(true); @@ -1746,28 +1736,28 @@ public void testPreparedProfileNotAvailable() { setCHProfiles(new CHProfile(profile1)); hopper.getLMPreparationHandler(). - setLMProfiles(new LMProfile(profile1).setMaximumLMWeight(2000)); + setLMProfiles(new LMProfile(profile1).setMaximumLMWeight(20000)); hopper.importOrLoad(); // request a profile that was not prepared GHRequest req = new GHRequest(43.727687, 7.418737, 43.74958, 7.436566). setProfile(profile2); - // try with CH + // no CH or LM profile and so nothing can be ignored req.putHint(CH.DISABLE, false); req.putHint(Landmark.DISABLE, false); GHResponse res = hopper.route(req); - assertTrue(res.hasErrors(), res.getErrors().toString()); - assertTrue(res.getErrors().get(0).getMessage().contains("Cannot find CH preparation for the requested profile: 'short_fast_profile'"), res.getErrors().toString()); + assertFalse(res.hasErrors(), res.getErrors().toString()); + assertEquals(3587, res.getBest().getDistance(), 1); // try with LM req.putHint(CH.DISABLE, true); req.putHint(Landmark.DISABLE, false); res = hopper.route(req); - assertTrue(res.hasErrors(), res.getErrors().toString()); - assertTrue(res.getErrors().get(0).getMessage().contains("Cannot find LM preparation for the requested profile: 'short_fast_profile'"), res.getErrors().toString()); + assertFalse(res.hasErrors(), res.getErrors().toString()); + assertEquals(3587, res.getBest().getDistance(), 1); - // falling back to non-prepared algo works + // falling back to non-prepared algo req.putHint(CH.DISABLE, true); req.putHint(Landmark.DISABLE, true); res = hopper.route(req); @@ -1778,45 +1768,43 @@ public void testPreparedProfileNotAvailable() { @Test public void testDisablingLM() { // setup GH with LM preparation but no CH preparation - final String profile = "profile"; - final String vehicle = "car"; + // note that the pure presence of the bike profile leads to 'ghost' junctions with the bike network even for // cars such that the number of visited nodes depends on the bike profile added here or not, #1910 GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle), - new Profile("bike").setVehicle("bike")). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true); hopper.getLMPreparationHandler(). - setLMProfiles(new LMProfile(profile).setMaximumLMWeight(2000)); + setLMProfiles(new LMProfile("car").setMaximumLMWeight(20000)); hopper.importOrLoad(); // we can switch LM on/off GHRequest req = new GHRequest(43.727687, 7.418737, 43.74958, 7.436566). - setProfile(profile); + setProfile("car"); req.putHint(Landmark.DISABLE, false); GHResponse res = hopper.route(req); - assertTrue(res.getHints().getInt("visited_nodes.sum", 0) < 150); + int visited = res.getHints().getInt("visited_nodes.sum", 0); + assertTrue(visited < 150, "too many visited nodes: " + visited); req.putHint(Landmark.DISABLE, true); res = hopper.route(req); - assertTrue(res.getHints().getInt("visited_nodes.sum", 0) > 170); + visited = res.getHints().getInt("visited_nodes.sum", 0); + assertTrue(visited > 170, "not enough visited nodes: " + visited); } @ParameterizedTest @ValueSource(booleans = {true, false}) public void testCompareAlgos(boolean turnCosts) { - final String profile = "car"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(turnCosts)); - hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); - hopper.getLMPreparationHandler().setLMProfiles(new LMProfile(profile)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(turnCosts ? TurnCostsConfig.car() : null)); + hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); + hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.importOrLoad(); long seed = System.nanoTime(); @@ -1828,7 +1816,7 @@ public void testCompareAlgos(boolean turnCosts) { double lon1 = bounds.minLon + rnd.nextDouble() * (bounds.maxLon - bounds.minLon); double lon2 = bounds.minLon + rnd.nextDouble() * (bounds.maxLon - bounds.minLon); GHRequest req = new GHRequest(lat1, lon1, lat2, lon2); - req.setProfile(profile); + req.setProfile("car"); req.getHints().putObject(CH.DISABLE, false).putObject(Landmark.DISABLE, true); ResponsePath pathCH = hopper.route(req).getBest(); req.getHints().putObject(CH.DISABLE, true).putObject(Landmark.DISABLE, false); @@ -1841,6 +1829,9 @@ public void testCompareAlgos(boolean turnCosts) { assertEquals(path.hasErrors(), pathLM.hasErrors(), failMessage); if (!path.hasErrors()) { + assertEquals(path.getRouteWeight(), pathCH.getRouteWeight(), failMessage); + assertEquals(path.getRouteWeight(), pathLM.getRouteWeight(), failMessage); + assertEquals(path.getDistance(), pathCH.getDistance(), 0.1, failMessage); assertEquals(path.getDistance(), pathLM.getDistance(), 0.1, failMessage); @@ -1853,18 +1844,16 @@ public void testCompareAlgos(boolean turnCosts) { @ParameterizedTest @ValueSource(booleans = {true, false}) public void testAStarCHBug(boolean turnCosts) { - final String profile = "car"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(turnCosts)); - hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(turnCosts ? TurnCostsConfig.car() : null)); + hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.importOrLoad(); GHRequest req = new GHRequest(55.821744463888116, 37.60380604129401, 55.82608197039734, 37.62055856655137); - req.setProfile(profile); + req.setProfile("car"); req.getHints().putObject(CH.DISABLE, false); ResponsePath pathCH = hopper.route(req).getBest(); req.getHints().putObject(CH.DISABLE, true); @@ -1878,15 +1867,13 @@ public void testAStarCHBug(boolean turnCosts) { @Test public void testIssue1960() { - final String profile = "car"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(true)); - hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); - hopper.getLMPreparationHandler().setLMProfiles(new LMProfile(profile)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); + hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); + hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.importOrLoad(); @@ -1899,28 +1886,29 @@ public void testIssue1960() { req.getHints().putObject(CH.DISABLE, true).putObject(Landmark.DISABLE, true); ResponsePath path = hopper.route(req).getBest(); - assertEquals(1995.38, pathCH.getDistance(), 0.1); - assertEquals(1995.38, pathLM.getDistance(), 0.1); - assertEquals(1995.38, path.getDistance(), 0.1); + assertEquals(1995.18, pathCH.getDistance(), 0.1); + assertEquals(1995.18, pathLM.getDistance(), 0.1); + assertEquals(1995.18, path.getDistance(), 0.1); - assertEquals(149504, pathCH.getTime()); - assertEquals(149504, pathLM.getTime()); - assertEquals(149504, path.getTime()); + assertEquals(149482, pathCH.getTime()); + assertEquals(149482, pathLM.getTime()); + assertEquals(149482, path.getTime()); } @Test public void testTurnCostsOnOff() { final String profile1 = "profile_no_turn_costs"; final String profile2 = "profile_turn_costs"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). + setEncodedValuesString("car_access, car_average_speed"). // add profile with turn costs first when no flag encoder is explicitly added setProfiles( - new Profile(profile2).setVehicle(vehicle).setTurnCosts(true).putHint(U_TURN_COSTS, 30), - new Profile(profile1).setVehicle(vehicle).setTurnCosts(false) + TestProfiles.accessAndSpeed(profile2, "car"). + setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"), 30)), + TestProfiles.accessAndSpeed(profile1, "car") ). setStoreOnFlush(true); hopper.importOrLoad(); @@ -1942,14 +1930,14 @@ public void testTurnCostsOnOff() { public void testTurnCostsOnOffCH() { final String profile1 = "profile_turn_costs"; final String profile2 = "profile_no_turn_costs"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(Arrays.asList( - new Profile(profile1).setVehicle(vehicle).setTurnCosts(true), - new Profile(profile2).setVehicle(vehicle).setTurnCosts(false) + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(List.of( + TestProfiles.accessAndSpeed(profile1, "car").setTurnCostsConfig(TurnCostsConfig.car()), + TestProfiles.accessAndSpeed(profile2, "car") )). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles( @@ -1968,88 +1956,87 @@ public void testTurnCostsOnOffCH() { @Test public void testCHOnOffWithTurnCosts() { final String profile = "my_car"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(true)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car").setTurnCostsConfig(TurnCostsConfig.car())). setStoreOnFlush(true); hopper.getCHPreparationHandler() .setCHProfiles(new CHProfile(profile)); hopper.importOrLoad(); - GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689); + GHRequest req = new GHRequest(55.81358, 37.598616, 55.809915, 37.5947); req.setProfile("my_car"); // with CH req.putHint(CH.DISABLE, true); GHResponse rsp1 = hopper.route(req); - assertEquals(1044, rsp1.getBest().getDistance(), 1); + assertEquals(1350, rsp1.getBest().getDistance(), 1); // without CH req.putHint(CH.DISABLE, false); GHResponse rsp2 = hopper.route(req); - assertEquals(1044, rsp2.getBest().getDistance(), 1); + assertEquals(1350, rsp2.getBest().getDistance(), 1); // just a quick check that we did not run the same algorithm twice assertNotEquals(rsp1.getHints().getInt("visited_nodes.sum", -1), rsp2.getHints().getInt("visited_nodes.sum", -1)); } @Test public void testNodeBasedCHOnlyButTurnCostForNonCH() { - final String profile1 = "car_profile_tc"; - final String profile2 = "car_profile_notc"; + final String profile_tc = "car_profile_tc"; + final String profile_no_tc = "car_profile_notc"; // before edge-based CH was added a common case was to use edge-based without CH and CH for node-based GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(Arrays.asList( - new Profile(profile1).setVehicle("car").setTurnCosts(true), - new Profile(profile2).setVehicle("car").setTurnCosts(false))). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(List.of( + TestProfiles.accessAndSpeed(profile_tc, "car").setTurnCostsConfig(TurnCostsConfig.car()), + TestProfiles.accessAndSpeed(profile_no_tc, "car") + )). setStoreOnFlush(true); hopper.getCHPreparationHandler() // we only do the CH preparation for the profile without turn costs - .setCHProfiles(new CHProfile(profile2)); + .setCHProfiles(new CHProfile(profile_no_tc)); hopper.importOrLoad(); GHRequest req = new GHRequest(55.813357, 37.5958585, 55.811042, 37.594689); - // without CH, turn turn costs on and off + // without CH and with tc req.putHint(CH.DISABLE, true); - req.setProfile(profile1); + req.setProfile(profile_tc); assertEquals(1044, hopper.route(req).getBest().getDistance(), 1); - req.setProfile(profile2); + // without CH and without tc + req.setProfile(profile_no_tc); assertEquals(400, hopper.route(req).getBest().getDistance(), 1); - // with CH, turn turn costs on and off, since turn costs not supported for CH throw an error + // with CH and without tc => since turn costs not supported for CH throw an error req.putHint(CH.DISABLE, false); - req.setProfile(profile2); + req.setProfile(profile_no_tc); assertEquals(400, hopper.route(req).getBest().getDistance(), 1); - req.setProfile(profile1); + // since there is no CH preparation for car_profile_tc the ch.disable parameter is ignored + req.setProfile(profile_tc); GHResponse rsp = hopper.route(req); - assertEquals(1, rsp.getErrors().size()); - String expected = "Cannot find CH preparation for the requested profile: 'car_profile_tc'" + - "\nYou can try disabling CH using ch.disable=true" + - "\navailable CH profiles: [car_profile_notc]"; - assertTrue(rsp.getErrors().toString().contains(expected), "unexpected error:\n" + rsp.getErrors().toString() + "\nwhen expecting an error containing:\n" + expected - ); + assertEquals(1044, hopper.route(req).getBest().getDistance(), 1); } @Test - public void testEncoderWithTurnCostSupport_stillAllows_nodeBasedRouting() { + public void testProfileWithTurnCostSupport_stillAllows_nodeBasedRouting() { // see #1698 - final String profile = "profile"; - final String vehicle = "foot"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MOSCOW). - setProfiles(new Profile(profile).setVehicle(vehicle), - new Profile("car").setVehicle("car").setTurnCosts(true)); + setEncodedValuesString("foot_access, foot_priority, foot_average_speed, car_access, car_average_speed"). + setProfiles( + TestProfiles.accessSpeedAndPriority("foot"), + TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car()) + ); hopper.importOrLoad(); GHPoint p = new GHPoint(55.813357, 37.5958585); GHPoint q = new GHPoint(55.811042, 37.594689); GHRequest req = new GHRequest(p, q); - req.setProfile(profile); + req.setProfile("foot"); GHResponse rsp = hopper.route(req); assertEquals(0, rsp.getErrors().size(), "there should not be an error, but was: " + rsp.getErrors()); } @@ -2064,9 +2051,10 @@ public void testOneWaySubnetwork_issue1807() { setGraphHopperLocation(GH_LOCATION). setOSMFile(ESSEN). setMinNetworkSize(50). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed, car_access, car_average_speed"). setProfiles( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car").setTurnCosts(true) + TestProfiles.accessSpeedAndPriority("foot"), + TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car()) ); hopper.importOrLoad(); @@ -2087,40 +2075,13 @@ public void testOneWaySubnetwork_issue1807() { assertEquals(658, rsp.getBest().getDistance(), 1); } - @Test - public void testTagParserProcessingOrder() { - // it does not matter when the OSMBikeNetworkTagParser is added (before or even after BikeCommonPriorityParser) - // as it is a different type but it is important that OSMSmoothnessParser is added before smoothnessEnc is used - // in BikeCommonAverageSpeedParser - GraphHopper hopper = new GraphHopper(). - setGraphHopperLocation(GH_LOCATION). - setOSMFile(BAYREUTH). - setMinNetworkSize(0). - setProfiles(new Profile("bike").setVehicle("bike")); - - hopper.importOrLoad(); - GHRequest req = new GHRequest(new GHPoint(49.98021, 11.50730), new GHPoint(49.98026, 11.50795)); - req.setProfile("bike"); - GHResponse rsp = hopper.route(req); - assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); - // due to smoothness=bad => 7 seconds longer - assertEquals(21, rsp.getBest().getTime() / 1000.0, 1); - - req = new GHRequest(new GHPoint(50.015067, 11.502093), new GHPoint(50.014694, 11.499748)); - req.setProfile("bike"); - rsp = hopper.route(req); - assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); - // due to bike network (relation 2247905) a lower route weight => otherwise 29.0 - assertEquals(23.2, rsp.getBest().getRouteWeight(), .1); - } - @Test public void testEdgeCount() { GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). setMinNetworkSize(50). - setProfiles(new Profile("car").setVehicle("car")); + setProfiles(TestProfiles.constantSpeed("bike")); hopper.importOrLoad(); int count = 0; AllEdgesIterator iter = hopper.getBaseGraph().getAllEdges(); @@ -2134,9 +2095,10 @@ public void testCurbsides() { GraphHopper h = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile("my_profile").setVehicle("car").setTurnCosts(true)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); h.getCHPreparationHandler() - .setCHProfiles(new CHProfile("my_profile")); + .setCHProfiles(new CHProfile("car")); h.importOrLoad(); // depending on the curbside parameters we take very different routes @@ -2178,15 +2140,13 @@ public void testCurbsides() { @Test public void testForceCurbsides() { - final String profile = "my_profile"; - final String vehicle = "car"; - GraphHopper h = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(true)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); h.getCHPreparationHandler() - .setCHProfiles(new CHProfile(profile)); + .setCHProfiles(new CHProfile("car")); h.importOrLoad(); // depending on the curbside parameters we take very different routes @@ -2197,26 +2157,38 @@ public void testForceCurbsides() { GHPoint q = new GHPoint(43.737949, 7.423523); final String boulevard = "Boulevard de Suisse"; final String avenue = "Avenue de la Costa"; - assertCurbsidesPathError(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_RIGHT), "Impossible curbside constraint: 'curbside=right' at point 0", true); - assertCurbsidesPathError(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_LEFT), "Impossible curbside constraint: 'curbside=right' at point 0", true); + assertCurbsidesPathError(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_RIGHT), "Impossible curbside constraint: 'curbside=right' at point 0", "strict"); + assertCurbsidesPathError(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_LEFT), "Impossible curbside constraint: 'curbside=right' at point 0", "strict"); assertCurbsidesPath(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_RIGHT), 463, asList(boulevard, avenue)); - assertCurbsidesPathError(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_LEFT), "Impossible curbside constraint: 'curbside=left' at point 1", true); + assertCurbsidesPathError(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_LEFT), "Impossible curbside constraint: 'curbside=left' at point 1", "strict"); // without restricting anything we get the shortest path assertCurbsidesPath(h, p, q, asList(CURBSIDE_ANY, CURBSIDE_ANY), 463, asList(boulevard, avenue)); assertCurbsidesPath(h, p, q, Collections.emptyList(), 463, asList(boulevard, avenue)); - // if we set force_curbside to false impossible curbside constraints will be ignored - assertCurbsidesPath(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_RIGHT), 463, asList(boulevard, avenue), false); - assertCurbsidesPath(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_LEFT), 463, asList(boulevard, avenue), false); - assertCurbsidesPath(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_RIGHT), 463, asList(boulevard, avenue), false); - assertCurbsidesPath(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_LEFT), 463, asList(boulevard, avenue), false); - } - - private void assertCurbsidesPath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, int expectedDistance, List expectedStreets) { - assertCurbsidesPath(hopper, source, target, curbsides, expectedDistance, expectedStreets, true); - } - - private void assertCurbsidesPath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, int expectedDistance, List expectedStreets, boolean force) { - GHResponse rsp = calcCurbsidePath(hopper, source, target, curbsides, force); + // if we set curbside_strictness to "soft" then impossible curbside constraints will be ignored + assertCurbsidesPath(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_RIGHT), 463, asList(boulevard, avenue), "soft"); + assertCurbsidesPath(h, p, q, asList(CURBSIDE_RIGHT, CURBSIDE_LEFT), 463, asList(boulevard, avenue), "soft"); + assertCurbsidesPath(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_RIGHT), 463, asList(boulevard, avenue), "soft"); + assertCurbsidesPath(h, p, q, asList(CURBSIDE_LEFT, CURBSIDE_LEFT), 463, asList(boulevard, avenue), "soft"); + + // curbside AUTO in monaco (right-hand side traffic) will be set to 'right' due to PRIMARY road class (to avoid crossing). + p = new GHPoint(43.738783, 7.420465); + q = new GHPoint(43.739207, 7.4216); + final String princess = "Boulevard Princesse Charlotte"; + final String roq = "Avenue de Roqueville"; + final String berc = "Avenue du Berceau"; + assertCurbsidesPath(h, p, q, asList(CURBSIDE_AUTO, CURBSIDE_LEFT), 102, List.of(princess)); + assertCurbsidesPath(h, p, q, asList(CURBSIDE_AUTO, CURBSIDE_RIGHT), 513, List.of(princess, berc, berc, berc, roq, princess)); + assertCurbsidesPath(h, p, q, asList(CURBSIDE_AUTO, CURBSIDE_AUTO), 513, asList(princess, berc, berc, berc, roq, princess)); + } + + private void assertCurbsidesPath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, + int expectedDistance, List expectedStreets) { + assertCurbsidesPath(hopper, source, target, curbsides, expectedDistance, expectedStreets, "strict"); + } + + private void assertCurbsidesPath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, + int expectedDistance, List expectedStreets, String curbsideStrictness) { + GHResponse rsp = calcCurbsidePath(hopper, source, target, curbsides, curbsideStrictness); assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); ResponsePath path = rsp.getBest(); List streets = new ArrayList<>(path.getInstructions().size()); @@ -2229,17 +2201,18 @@ private void assertCurbsidesPath(GraphHopper hopper, GHPoint source, GHPoint tar assertEquals(expectedDistance, path.getDistance(), 1); } - private void assertCurbsidesPathError(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, String errorMessage, boolean force) { - GHResponse rsp = calcCurbsidePath(hopper, source, target, curbsides, force); + private void assertCurbsidesPathError(GraphHopper hopper, GHPoint source, GHPoint target, + List curbsides, String errorMessage, String curbsideStrictness) { + GHResponse rsp = calcCurbsidePath(hopper, source, target, curbsides, curbsideStrictness); assertTrue(rsp.hasErrors()); assertTrue(rsp.getErrors().toString().contains(errorMessage), "unexpected error. expected message containing: " + errorMessage + ", but got: " + rsp.getErrors()); } - private GHResponse calcCurbsidePath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, boolean force) { + private GHResponse calcCurbsidePath(GraphHopper hopper, GHPoint source, GHPoint target, List curbsides, String curbsideStrictness) { GHRequest req = new GHRequest(source, target); - req.putHint(Routing.FORCE_CURBSIDE, force); - req.setProfile("my_profile"); + req.putHint(Routing.CURBSIDE_STRICTNESS, curbsideStrictness); + req.setProfile("car"); req.setCurbsides(curbsides); return hopper.route(req); } @@ -2249,8 +2222,8 @@ public void testCHWithFiniteUTurnCosts() { GraphHopper h = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile("my_profile").setVehicle("car"). - setTurnCosts(true).putHint(U_TURN_COSTS, 40)); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("my_profile", "car").setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"), 40))); h.getCHPreparationHandler() .setCHProfiles(new CHProfile("my_profile")); h.importOrLoad(); @@ -2263,10 +2236,10 @@ public void testCHWithFiniteUTurnCosts() { // we reach the target. at the start location we do a u-turn at the crossing with the *steps* ('ghost junction') req.setCurbsides(Arrays.asList("right", "right")); GHResponse res = h.route(req); - assertFalse(res.hasErrors(), "routing should not fail"); - assertEquals(242.5, res.getBest().getRouteWeight(), 0.1); + assertFalse(res.hasErrors(), "routing should not fail but had errors: " + res.getErrors()); + assertEquals(2423, res.getBest().getRouteWeight()); assertEquals(1917, res.getBest().getDistance(), 1); - assertEquals(243000, res.getBest().getTime(), 1000); + assertEquals(163000, res.getBest().getTime(), 1000); } @Test @@ -2274,7 +2247,8 @@ public void simplifyWithInstructionsAndPathDetails() { final String profile = "profile"; GraphHopper hopper = new GraphHopper(); hopper.setOSMFile(BAYREUTH). - setProfiles(new Profile(profile).setVehicle("car")). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setGraphHopperLocation(GH_LOCATION); hopper.importOrLoad(); @@ -2283,7 +2257,7 @@ public void simplifyWithInstructionsAndPathDetails() { .addPoint(new GHPoint(50.016895, 11.4923)) .addPoint(new GHPoint(50.003464, 11.49157)) .setProfile(profile) - .setPathDetails(Arrays.asList(KVStorage.KeyValue.STREET_REF, "max_speed")); + .setPathDetails(Arrays.asList(STREET_REF, "max_speed")); req.putHint("elevation", true); GHResponse rsp = hopper.route(req); @@ -2292,7 +2266,7 @@ public void simplifyWithInstructionsAndPathDetails() { ResponsePath path = rsp.getBest(); // check path was simplified (without it would be more like 58) - assertEquals(41, path.getPoints().size()); + assertEquals(55, path.getPoints().size()); // check instructions InstructionList instructions = path.getInstructions(); @@ -2300,43 +2274,43 @@ public void simplifyWithInstructionsAndPathDetails() { for (Instruction instruction : instructions) { totalLength += instruction.getLength(); } - assertEquals(40, totalLength); + assertEquals(54, totalLength); assertInstruction(instructions.get(0), "KU 11", "[0, 4[", 4, 4); - assertInstruction(instructions.get(1), "B 85", "[4, 16[", 12, 12); + assertInstruction(instructions.get(1), "B 85", "[4, 24[", 20, 20); // via instructions have length = 0, but the point list must not be empty! - assertInstruction(instructions.get(2), null, "[16, 17[", 0, 1); - assertInstruction(instructions.get(3), "B 85", "[16, 32[", 16, 16); - assertInstruction(instructions.get(4), null, "[32, 34[", 2, 2); - assertInstruction(instructions.get(5), "KU 18", "[34, 37[", 3, 3); - assertInstruction(instructions.get(6), "St 2189", "[37, 38[", 1, 1); - assertInstruction(instructions.get(7), null, "[38, 40[", 2, 2); + assertInstruction(instructions.get(2), null, "[24, 25[", 0, 1); + assertInstruction(instructions.get(3), "B 85", "[24, 45[", 21, 21); + assertInstruction(instructions.get(4), null, "[45, 48[", 3, 3); + assertInstruction(instructions.get(5), "KU 18", "[48, 51[", 3, 3); + assertInstruction(instructions.get(6), "St 2189", "[51, 52[", 1, 1); + assertInstruction(instructions.get(7), null, "[52, 54[", 2, 2); // finish instructions have length = 0, but the point list must not be empty! - assertInstruction(instructions.get(8), null, "[40, 41[", 0, 1); + assertInstruction(instructions.get(8), null, "[54, 55[", 0, 1); // check max speeds List speeds = path.getPathDetails().get("max_speed"); assertDetail(speeds.get(0), "null [0, 4]"); - assertDetail(speeds.get(1), "70.0 [4, 6]"); - assertDetail(speeds.get(2), "100.0 [6, 16]"); - assertDetail(speeds.get(3), "100.0 [16, 31]"); // we do not merge path details at via points - assertDetail(speeds.get(4), "80.0 [31, 32]"); - assertDetail(speeds.get(5), "null [32, 37]"); - assertDetail(speeds.get(6), "50.0 [37, 38]"); - assertDetail(speeds.get(7), "null [38, 40]"); + assertDetail(speeds.get(1), "70.0 [4, 8]"); + assertDetail(speeds.get(2), "100.0 [8, 24]"); + assertDetail(speeds.get(3), "100.0 [24, 42]"); // we do not merge path details at via points + assertDetail(speeds.get(4), "80.0 [42, 45]"); + assertDetail(speeds.get(5), "null [45, 51]"); + assertDetail(speeds.get(6), "50.0 [51, 52]"); + assertDetail(speeds.get(7), "null [52, 54]"); // check street names - List streetNames = path.getPathDetails().get(KVStorage.KeyValue.STREET_REF); + List streetNames = path.getPathDetails().get(STREET_REF); assertDetail(streetNames.get(0), "KU 11 [0, 4]"); - assertDetail(streetNames.get(1), "B 85 [4, 16]"); - assertDetail(streetNames.get(2), "B 85 [16, 32]"); - assertDetail(streetNames.get(3), "null [32, 34]"); - assertDetail(streetNames.get(4), "KU 18 [34, 37]"); - assertDetail(streetNames.get(5), "St 2189 [37, 38]"); - assertDetail(streetNames.get(6), "null [38, 40]"); + assertDetail(streetNames.get(1), "B 85 [4, 24]"); + assertDetail(streetNames.get(2), "B 85 [24, 45]"); + assertDetail(streetNames.get(3), "null [45, 48]"); + assertDetail(streetNames.get(4), "KU 18 [48, 51]"); + assertDetail(streetNames.get(5), "St 2189 [51, 52]"); + assertDetail(streetNames.get(6), "null [52, 54]"); } private void assertInstruction(Instruction instruction, String expectedRef, String expectedInterval, int expectedLength, int expectedPoints) { - assertEquals(expectedRef, instruction.getExtraInfoJSON().get(KVStorage.KeyValue.STREET_REF)); + assertEquals(expectedRef, instruction.getExtraInfoJSON().get(STREET_REF)); assertEquals(expectedInterval, ((ShallowImmutablePointList) instruction.getPoints()).getIntervalString()); assertEquals(expectedLength, instruction.getLength()); assertEquals(expectedPoints, instruction.getPoints().size()); @@ -2352,7 +2326,8 @@ public void simplifyKeepsWaypoints(boolean elevation, boolean instructions) { GraphHopper h = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(MONACO). - setProfiles(new Profile("car").setVehicle("car")); + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car")); if (elevation) h.setElevationProvider(new SRTMProvider(DIR)); h.importOrLoad(); @@ -2372,7 +2347,7 @@ public void simplifyKeepsWaypoints(boolean elevation, boolean instructions) { req.putHint("instructions", instructions); GHResponse res = h.route(req); assertFalse(res.hasErrors()); - assertEquals(elevation ? 1828 : 1793, res.getBest().getDistance(), 1); + assertEquals(elevation ? 1828 : 1794, res.getBest().getDistance(), 1); PointList points = res.getBest().getPoints(); PointList wayPoints = res.getBest().getWaypoints(); assertEquals(reqPoints.size(), wayPoints.size()); @@ -2395,9 +2370,7 @@ private static void assertPointlistContainsSublist(PointList pointList, PointLis @Test public void testNoLoad() { String profile = "profile"; - String vehicle = "car"; - final GraphHopper hopper = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)); + final GraphHopper hopper = new GraphHopper().setProfiles(TestProfiles.constantSpeed(profile)); IllegalStateException e = assertThrows(IllegalStateException.class, () -> hopper.route(new GHRequest(42, 10.4, 42, 10).setProfile(profile))); assertTrue(e.getMessage().startsWith("Do a successful call to load or importOrLoad before routing"), e.getMessage()); } @@ -2405,15 +2378,14 @@ public void testNoLoad() { @Test public void connectionNotFound() { final String profile = "profile"; - final String vehicle = "car"; - GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile("profile").setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setStoreOnFlush(true); hopper.getCHPreparationHandler() - .setCHProfiles(new CHProfile("profile")); + .setCHProfiles(new CHProfile(profile)); hopper.setMinNetworkSize(0); hopper.importOrLoad(); // here from and to both snap to small subnetworks that are disconnected from the main graph and @@ -2439,7 +2411,7 @@ void interpolateBridgesTunnelsAndFerries() { }. setGraphHopperLocation(GH_LOCATION). setOSMFile(BAYREUTH). - setProfiles(new Profile("profile")). + setProfiles(new Profile("profile").setCustomModel(new CustomModel().addToSpeed(If("true", LIMIT, "100")))). setElevation(true). setStoreOnFlush(true); hopper.importOrLoad(); @@ -2455,7 +2427,7 @@ void interpolateBridgesTunnelsAndFerries() { super.interpolateBridgesTunnelsAndFerries(); } }. - setProfiles(new Profile("profile")). + setProfiles(new Profile("profile").setCustomModel(new CustomModel().addToSpeed(If("true", LIMIT, "100")))). setElevation(true). setGraphHopperLocation(GH_LOCATION); hopper.load(); @@ -2466,11 +2438,11 @@ void interpolateBridgesTunnelsAndFerries() { @Test public void issue2306_1() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). - setProfiles(new Profile("profile").setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setMinNetworkSize(200); hopper.importOrLoad(); Weighting weighting = hopper.createWeighting(hopper.getProfile(profile), new PMap()); @@ -2489,11 +2461,11 @@ public void issue2306_2() { // is a meta-parameter that could go away at some point, I say that _if_ we find a match, // it should be a close one. (And not a far away one, as happened in issue2306.) final String profile = "profile"; - final String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). - setProfiles(new Profile("profile").setVehicle(vehicle)). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car")). setMinNetworkSize(200); hopper.importOrLoad(); Weighting weighting = hopper.createWeighting(hopper.getProfile(profile), new PMap()); @@ -2509,11 +2481,12 @@ public void testBarriers() { GraphHopper hopper = new GraphHopper(). setGraphHopperLocation(GH_LOCATION). setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). - setVehiclesString("car|block_private=false"). + setEncodedValuesString("car_access|block_private=false,road_access,road_environment," + + "car_average_speed, bike_access, bike_priority, bike_average_speed, foot_access, foot_priority, foot_average_speed"). setProfiles( - new Profile("car").setVehicle("car"), - new Profile("bike").setVehicle("bike"), - new Profile("foot").setVehicle("foot") + TestProfiles.accessAndSpeed("car"), + TestProfiles.accessSpeedAndPriority("bike"), + TestProfiles.accessSpeedAndPriority("foot") ). setMinNetworkSize(0); hopper.importOrLoad(); @@ -2594,21 +2567,38 @@ public void testBarriers() { rsp = hopper.route(new GHRequest(51.327411, 12.429598, 51.32723, 12.429979). setCustomModel(new CustomModel().addToPriority(If("road_access == PRIVATE", MULTIPLY, "0"))). setProfile("car")); - assertFalse(rsp.hasErrors()); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); assertEquals(20, rsp.getBest().getDistance(), 1); } } + @Test + public void turnRestrictionWithSnapToViaEdge_issue2996() { + final String profile = "profile"; + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed(profile, "car").setTurnCostsConfig(TurnCostsConfig.car())). + setMinNetworkSize(200); + hopper.importOrLoad(); + // doing a simple left-turn is allowed + GHResponse res = hopper.route(new GHRequest(51.34665, 12.391847, 51.346254, 12.39256).setProfile(profile)); + assertEquals(81, res.getBest().getDistance(), 1); + // if we stop right after the left-turn on the via-edge the turn should still be allowed of course (there should be no detour that avoids the turn) + res = hopper.route(new GHRequest(51.34665, 12.391847, 51.346306, 12.392091).setProfile(profile)); + assertEquals(48, res.getBest().getDistance(), 1); + } + @Test public void germanyCountryRuleAvoidsTracks() { final String profile = "profile"; + Profile p = TestProfiles.accessAndSpeed(profile, "car"); + p.getCustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1")); - // first we try without country rules (the default) GraphHopper hopper = new GraphHopper() - .setProfiles(new Profile(profile) - .setCustomModel(new CustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1"))) - .setVehicle("car")) - .setCountryRuleFactory(null) + .setEncodedValuesString("car_access, car_average_speed, road_access") + .setProfiles(p) .setGraphHopperLocation(GH_LOCATION) .setOSMFile(BAYREUTH); hopper.importOrLoad(); @@ -2617,34 +2607,15 @@ public void germanyCountryRuleAvoidsTracks() { GHResponse response = hopper.route(request); assertFalse(response.hasErrors()); double distance = response.getBest().getDistance(); - // The route takes a shortcut through the forest - assertEquals(1447, distance, 1); - - // this time we enable country rules - hopper.clean(); - hopper = new GraphHopper() - .setProfiles(new Profile(profile) - .setCustomModel(new CustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1"))) - .setVehicle("car")) - .setGraphHopperLocation(GH_LOCATION) - .setCountryRuleFactory(new CountryRuleFactory()) - .setOSMFile(BAYREUTH); - hopper.importOrLoad(); - request = new GHRequest(50.010373, 11.51792, 50.005146, 11.516633); - request.setProfile(profile); - response = hopper.route(request); - assertFalse(response.hasErrors()); - distance = response.getBest().getDistance(); - // since GermanyCountryRule avoids TRACK roads the route will now be much longer as it goes around the forest + // by default TRACK roads are avoided in Germany assertEquals(4186, distance, 1); } @Test void curbsideWithSubnetwork_issue2502() { - final String profile = "profile"; GraphHopper hopper = new GraphHopper() - .setProfiles(new Profile(profile).setVehicle("car").setTurnCosts(true) - .setTurnCosts(true).putHint(U_TURN_COSTS, 80)) + .setEncodedValuesString("car_access, car_average_speed") + .setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())) .setGraphHopperLocation(GH_LOCATION) .setMinNetworkSize(200) .setOSMFile(DIR + "/one_way_dead_end.osm.pbf"); @@ -2654,7 +2625,7 @@ void curbsideWithSubnetwork_issue2502() { { // A->B GHRequest request = new GHRequest(pointA, pointB); - request.setProfile(profile); + request.setProfile("car"); request.setCurbsides(Arrays.asList("right", "right")); GHResponse response = hopper.route(request); assertFalse(response.hasErrors(), response.getErrors().toString()); @@ -2667,7 +2638,7 @@ void curbsideWithSubnetwork_issue2502() { // when the curbside constraints are evaluated. this should make the snap a tower snap such that the curbside // constraint won't result in a connection not found error GHRequest request = new GHRequest(pointB, pointA); - request.setProfile(profile); + request.setProfile("car"); request.setCurbsides(Arrays.asList("right", "right")); GHResponse response = hopper.route(request); assertFalse(response.hasErrors(), response.getErrors().toString()); @@ -2678,9 +2649,9 @@ void curbsideWithSubnetwork_issue2502() { @Test void averageSpeedPathDetailBug() { - final String profile = "profile"; GraphHopper hopper = new GraphHopper() - .setProfiles(new Profile(profile).setVehicle("car").setTurnCosts(true).putHint(U_TURN_COSTS, 80)) + .setEncodedValuesString("car_access, car_average_speed") + .setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())) .setGraphHopperLocation(GH_LOCATION) .setMinNetworkSize(200) .setOSMFile(BAYREUTH); @@ -2689,7 +2660,7 @@ void averageSpeedPathDetailBug() { GHPoint pointB = new GHPoint(50.019935, 11.500567); GHPoint pointC = new GHPoint(50.022027, 11.498255); GHRequest request = new GHRequest(Arrays.asList(pointA, pointB, pointC)); - request.setProfile(profile); + request.setProfile("car"); request.setPathDetails(Collections.singletonList("average_speed")); // this used to fail, because we did not wrap the weighting for query graph and so we tried calculating turn costs for virtual nodes GHResponse response = hopper.route(request); @@ -2700,9 +2671,9 @@ void averageSpeedPathDetailBug() { @Test void timeDetailBug() { - final String profile = "profile"; GraphHopper hopper = new GraphHopper() - .setProfiles(new Profile(profile).setVehicle("car").setTurnCosts(true).putHint(U_TURN_COSTS, 80)) + .setEncodedValuesString("car_access, car_average_speed") + .setProfiles(TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())) .setGraphHopperLocation(GH_LOCATION) .setMinNetworkSize(200) .setOSMFile(BAYREUTH); @@ -2711,7 +2682,7 @@ void timeDetailBug() { new GHPoint(50.020838, 11.494918), new GHPoint(50.024795, 11.498973), new GHPoint(50.023141, 11.496441))); - request.setProfile(profile); + request.setProfile("car"); request.getHints().putObject("instructions", true); request.setPathDetails(Arrays.asList("distance", "time")); GHResponse response = hopper.route(request); @@ -2751,8 +2722,8 @@ private void consistenceCheck(ResponsePath path) { public void testLoadGraph_implicitEncodedValues_issue1862() { GraphHopper hopper = new GraphHopper() .setProfiles( - new Profile("p_car").setVehicle("car"), - new Profile("p_bike").setVehicle("bike") + TestProfiles.constantSpeed("p_car"), + TestProfiles.constantSpeed("p_bike") ) .setGraphHopperLocation(GH_LOCATION) .setOSMFile(BAYREUTH); @@ -2760,26 +2731,23 @@ public void testLoadGraph_implicitEncodedValues_issue1862() { int nodes = hopper.getBaseGraph().getNodes(); hopper.close(); - // load without configured graph.vehicles - hopper = new GraphHopper(); - hopper.setProfiles(Arrays.asList( - new Profile("p_car").setVehicle("car"), - new Profile("p_bike").setVehicle("bike")) - ); - hopper.setGraphHopperLocation(GH_LOCATION); + hopper = new GraphHopper() + .setProfiles( + TestProfiles.constantSpeed("p_car"), + TestProfiles.constantSpeed("p_bike") + ) + .setGraphHopperLocation(GH_LOCATION); assertTrue(hopper.load()); hopper.getBaseGraph(); assertEquals(nodes, hopper.getBaseGraph().getNodes()); hopper.close(); - // load via explicitly configured graph.vehicles - hopper = new GraphHopper(); - hopper.setVehiclesString("car,bike"); - hopper.setProfiles(Arrays.asList( - new Profile("p_car").setVehicle("car"), - new Profile("p_bike").setVehicle("bike")) - ); - hopper.setGraphHopperLocation(GH_LOCATION); + hopper = new GraphHopper() + .setProfiles( + TestProfiles.constantSpeed("p_car"), + TestProfiles.constantSpeed("p_bike") + ) + .setGraphHopperLocation(GH_LOCATION); assertTrue(hopper.load()); assertEquals(nodes, hopper.getBaseGraph().getNodes()); hopper.close(); @@ -2789,52 +2757,85 @@ public void testLoadGraph_implicitEncodedValues_issue1862() { void testLoadingWithAnotherSpeedFactorWorks() { { GraphHopper hopper = new GraphHopper() - .setVehiclesString("car|speed_factor=3") - .setProfiles(new Profile("car").setVehicle("car")) + .setEncodedValuesString("car_average_speed|speed_factor=3, car_access") + .setProfiles(TestProfiles.accessAndSpeed("car")) .setGraphHopperLocation(GH_LOCATION) .setOSMFile(BAYREUTH); hopper.importOrLoad(); } { - // now we use another speed_factor, but changing the flag encoder string has no effect when we are loading + // now we use another speed_factor, but changing the encoded value string has no effect when we are loading // a graph. This API is a bit confusing, but we have been mixing configuration options that only matter // during import with those that only matter when routing for some time already. At some point we should // separate the 'import' from the 'routing' config (and split the GraphHopper class). GraphHopper hopper = new GraphHopper() - .setVehiclesString("car|speed_factor=9") - .setProfiles(new Profile("car").setVehicle("car")) + .setEncodedValuesString("car_average_speed|speed_factor=9") + .setProfiles(TestProfiles.accessAndSpeed("car")) .setGraphHopperLocation(GH_LOCATION); hopper.load(); assertEquals(2969, hopper.getBaseGraph().getNodes()); } } - @Test - void testGetVehiclePropsByVehicle() { - Profile car = new Profile("car").setVehicle("car").setTurnCosts(false); - Profile carTC = new Profile("car_tc").setVehicle("car").setTurnCosts(true); - - assertTurnCostsProp("", List.of(car), null); - assertTurnCostsProp("car", List.of(car), null); - assertTurnCostsProp("car|turn_costs=false", List.of(car), false); - assertTurnCostsProp("car|turn_costs=true", List.of(car), true); - - assertTurnCostsProp("", List.of(car, carTC), true); - assertTurnCostsProp("car", List.of(car, carTC), true); - assertTrue(assertThrows(IllegalArgumentException.class, () -> assertTurnCostsProp("car|turn_costs=false", List.of(car, carTC), true)) - .getMessage().contains("turn_costs=false was set explicitly for vehicle 'car', but profile 'car_tc' using it uses turn costs")); - assertTurnCostsProp("car|turn_costs=true", List.of(car, carTC), true); - } - - private void assertTurnCostsProp(String vehicleStr, List profiles, Boolean turnCosts) { - Map p = GraphHopper.getVehiclePropsByVehicle(vehicleStr, profiles); - assertEquals(1, p.size()); - PMap props = p.get("car"); - if (turnCosts == null) - assertFalse(props.has("turn_costs")); - else - assertEquals(turnCosts, props.getBool("turn_costs", false)); + @ParameterizedTest() + @ValueSource(booleans = {true, false}) + void legDistanceWithDuplicateEndpoint(boolean simplifyResponse) { + // see #3007 + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car")). + importOrLoad(); + hopper.getRouterConfig().setSimplifyResponse(simplifyResponse); + GHRequest request = new GHRequest().setProfile("car"); + request.addPoint(new GHPoint(43.732496, 7.427231)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.setPathDetails(List.of("leg_distance")); + GHResponse routeRsp = hopper.route(request); + assertEquals(4, routeRsp.getBest().getPoints().size()); + assertEquals(40.080, routeRsp.getBest().getDistance(), 1.e-3); + List p = routeRsp.getBest().getPathDetails().get("leg_distance"); + // there should be two consecutive leg_distance intervals, even though the second is empty: [0,3] and [3,3], see #2915 + assertEquals(2, p.size()); + assertEquals(0, p.get(0).getFirst()); + assertEquals(3, p.get(0).getLast()); + assertEquals(40.080, (double) p.get(0).getValue(), 1.e-3); + assertEquals(3, p.get(1).getFirst()); + assertEquals(3, p.get(1).getLast()); + assertEquals(0.0, (double) p.get(1).getValue(), 1.e-3); + } + + @ParameterizedTest() + @ValueSource(booleans = {true, false}) + void legDistanceWithDuplicateEndpoint_onlyTwoPoints(boolean simplifyResponse) { + // see #3007 + GraphHopper hopper = new GraphHopper(). + setGraphHopperLocation(GH_LOCATION). + setOSMFile(MONACO). + setEncodedValuesString("car_access, car_average_speed"). + setProfiles(TestProfiles.accessAndSpeed("car")). + importOrLoad(); + hopper.getRouterConfig().setSimplifyResponse(simplifyResponse); + GHRequest request = new GHRequest().setProfile("car"); + // special case where the points are so close to each other that the resulting route contains only two points total + request.addPoint(new GHPoint(43.732399, 7.426658)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.addPoint(new GHPoint(43.732499, 7.426758)); + request.setPathDetails(List.of("leg_distance")); + GHResponse routeRsp = hopper.route(request); + assertEquals(2, routeRsp.getBest().getPoints().size()); + assertEquals(10.439, routeRsp.getBest().getDistance(), 1.e-3); + List p = routeRsp.getBest().getPathDetails().get("leg_distance"); + // there should be two consecutive leg_distance intervals, even though the second is empty: [0,3] and [3,3], see #2915 + assertEquals(2, p.size()); + assertEquals(0, p.get(0).getFirst()); + assertEquals(1, p.get(0).getLast()); + assertEquals(10.439, (double) p.get(0).getValue(), 1.e-3); + assertEquals(1, p.get(1).getFirst()); + assertEquals(1, p.get(1).getLast()); + assertEquals(0.0, (double) p.get(1).getValue(), 1.e-3); } } - diff --git a/core/src/test/java/com/graphhopper/coll/GHLongLongBTreeTest.java b/core/src/test/java/com/graphhopper/coll/GHLongLongBTreeTest.java index 8aadeb1df32..aeeac5045a7 100644 --- a/core/src/test/java/com/graphhopper/coll/GHLongLongBTreeTest.java +++ b/core/src/test/java/com/graphhopper/coll/GHLongLongBTreeTest.java @@ -216,6 +216,57 @@ public void testLargeValue() { } } + @Test + public void testPutOrCompute() { + GHLongLongBTree instance = new GHLongLongBTree(3, 4, -1); + + // When key is absent, insert valueIfAbsent + long result = instance.putOrCompute(1, 10, old -> old + 100); + assertEquals(-1, result); // returns empty value when absent + assertEquals(10, instance.get(1)); // valueIfAbsent was inserted + assertEquals(1, instance.getSize()); + + // When key is present, apply computeIfPresent + result = instance.putOrCompute(1, 10, old -> old + 100); + assertEquals(10, result); // returns the old value + assertEquals(110, instance.get(1)); // old + 100 + assertEquals(1, instance.getSize()); // size unchanged + + // Verify it works with multiple keys and tree splits + for (int i = 2; i <= 20; i++) { + instance.putOrCompute(i, i, old -> old * 2); + } + assertEquals(20, instance.getSize()); + + // Update all values + for (int i = 1; i <= 20; i++) { + instance.putOrCompute(i, 0, old -> old + 1); + } + assertEquals(20, instance.getSize()); + + // Verify final values + assertEquals(111, instance.get(1)); // 110 + 1 + for (int i = 2; i <= 20; i++) { + assertEquals(i + 1, instance.get(i)); // i + 1 + } + } + + @Test + public void testPutOrComputeValidation() { + GHLongLongBTree instance = new GHLongLongBTree(3, 4, -1); + + // valueIfAbsent cannot be the empty value + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, + () -> instance.putOrCompute(1, -1, old -> old)); + assertTrue(ex.getMessage().contains("Value cannot be the 'empty value' -1")); + + // computed value cannot be the empty value + instance.put(1, 10); + ex = assertThrows(IllegalArgumentException.class, + () -> instance.putOrCompute(1, 5, old -> -1)); + assertTrue(ex.getMessage().contains("Computed value cannot be the 'empty value' -1")); + } + @Test public void testRandom() { final long seed = System.nanoTime(); diff --git a/core/src/test/java/com/graphhopper/isochrone/algorithm/ShortestPathTreeTest.java b/core/src/test/java/com/graphhopper/isochrone/algorithm/ShortestPathTreeTest.java index dd4187a8f3c..a1d7a3cdfcd 100644 --- a/core/src/test/java/com/graphhopper/isochrone/algorithm/ShortestPathTreeTest.java +++ b/core/src/test/java/com/graphhopper/isochrone/algorithm/ShortestPathTreeTest.java @@ -1,5 +1,6 @@ package com.graphhopper.isochrone.algorithm; +import com.graphhopper.json.Statement; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; @@ -73,6 +74,20 @@ public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { private final EncodingManager encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).add(ferryEnc).build(); private BaseGraph graph; + private Weighting createWeighting() { + return createWeighting(TurnCostProvider.NO_TURN_COST_PROVIDER); + } + + private Weighting createWeighting(TurnCostProvider turnCostProvider) { + return CustomModelParser.createWeighting(encodingManager, turnCostProvider, createBaseCustomModel()); + } + + private CustomModel createBaseCustomModel() { + CustomModel customModel = new CustomModel(); + customModel.addToPriority(If("!" + accessEnc.getName(), Statement.Op.MULTIPLY, "0")); + customModel.addToSpeed(If("true", Statement.Op.LIMIT, speedEnc.getName())); + return customModel; + } @BeforeEach public void setUp() { @@ -127,7 +142,7 @@ public void tearDown() { @Test public void testSPTAndIsochrone25Seconds() { List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager), false, TraversalMode.NODE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(), false, TraversalMode.NODE_BASED); instance.setTimeLimit(25_000); instance.search(0, result::add); assertEquals(3, result.size()); @@ -143,7 +158,7 @@ public void testSPTAndIsochrone25Seconds() { @Test public void testSPT26Seconds() { List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager), false, TraversalMode.NODE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(), false, TraversalMode.NODE_BASED); instance.setTimeLimit(26_000); instance.search(0, result::add); assertEquals(4, result.size()); @@ -158,7 +173,7 @@ public void testSPT26Seconds() { @Test public void testNoTimeLimit() { List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager), false, TraversalMode.NODE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(), false, TraversalMode.NODE_BASED); instance.setTimeLimit(Double.MAX_VALUE); instance.search(0, result::add); assertEquals(9, result.size()); @@ -185,9 +200,9 @@ public void testFerry() { edge.set(ferryEnc, true); List result = new ArrayList<>(); - CustomModel cm = new CustomModel(); - cm.addToPriority(If("ferry", MULTIPLY, "0.005")); - CustomWeighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, encodingManager, TurnCostProvider.NO_TURN_COST_PROVIDER, cm); + CustomModel customModel = createBaseCustomModel(); + customModel.addToPriority(If("ferry", MULTIPLY, "0.005")); + CustomWeighting weighting = CustomModelParser.createWeighting(encodingManager, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); ShortestPathTree instance = new ShortestPathTree(graph, weighting, false, TraversalMode.NODE_BASED); instance.setTimeLimit(30_000); instance.search(0, result::add); @@ -205,7 +220,7 @@ public void testFerry() { @Test public void testEdgeBasedWithFreeUTurns() { List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager), false, TraversalMode.EDGE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(), false, TraversalMode.EDGE_BASED); instance.setTimeLimit(Double.MAX_VALUE); instance.search(0, result::add); // The origin, and every end of every directed edge, are traversed. @@ -237,7 +252,7 @@ public void testEdgeBasedWithFreeUTurns() { @Test public void testEdgeBasedWithForbiddenUTurns() { - Weighting fastestWeighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, encodingManager, FORBIDDEN_UTURNS, new CustomModel()); + Weighting fastestWeighting = createWeighting(FORBIDDEN_UTURNS); List result = new ArrayList<>(); ShortestPathTree instance = new ShortestPathTree(graph, fastestWeighting, false, TraversalMode.EDGE_BASED); instance.setTimeLimit(Double.MAX_VALUE); @@ -271,7 +286,7 @@ public void testEdgeBasedWithForbiddenUTurns() { @Test public void testEdgeBasedWithFinitePositiveUTurnCost() { TimeBasedUTurnCost turnCost = new TimeBasedUTurnCost(80000); - Weighting fastestWeighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, encodingManager, turnCost, new CustomModel()); + Weighting fastestWeighting = createWeighting(turnCost); List result = new ArrayList<>(); ShortestPathTree instance = new ShortestPathTree(graph, fastestWeighting, false, TraversalMode.EDGE_BASED); instance.setTimeLimit(Double.MAX_VALUE); @@ -306,9 +321,8 @@ public void testEdgeBasedWithFinitePositiveUTurnCost() { @Test public void testEdgeBasedWithSmallerUTurnCost() { TimeBasedUTurnCost turnCost = new TimeBasedUTurnCost(20000); - Weighting fastestWeighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, encodingManager, turnCost, new CustomModel()); List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, fastestWeighting, false, TraversalMode.EDGE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(turnCost), false, TraversalMode.EDGE_BASED); instance.setTimeLimit(Double.MAX_VALUE); instance.search(0, result::add); // Something in between @@ -341,7 +355,7 @@ public void testEdgeBasedWithSmallerUTurnCost() { @Test public void testSearchByDistance() { List result = new ArrayList<>(); - ShortestPathTree instance = new ShortestPathTree(graph, CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager), false, TraversalMode.NODE_BASED); + ShortestPathTree instance = new ShortestPathTree(graph, createWeighting(), false, TraversalMode.NODE_BASED); instance.setDistanceLimit(110.0); instance.search(5, result::add); assertEquals(6, result.size()); diff --git a/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java index e62213bf86b..e3daf7bd2e2 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/CGIARProviderTest.java @@ -22,11 +22,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.SocketTimeoutException; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; @@ -36,10 +38,13 @@ public class CGIARProviderTest { private double precision = .1; CGIARProvider instance; + @TempDir + Path tempDir; @BeforeEach public void setUp() { - instance = new CGIARProvider(); + instance = new CGIARProvider(tempDir.toString()); + instance.init(); } @AfterEach @@ -57,6 +62,13 @@ public void testDown() { assertEquals(-10, instance.down(-5.1)); assertEquals(50, instance.down(50)); assertEquals(45, instance.down(49)); + + // values after 7-decimal truncation at negative tile boundaries + assertEquals(-20, instance.down(-15.0000001)); + assertEquals(-15, instance.down(-15.0)); + assertEquals(-15, instance.down(-14.9999999)); + assertEquals(-5, instance.down(-0.0000001)); + assertEquals(40, instance.down(44.9999999)); } @Test @@ -81,7 +93,7 @@ public void testFileNotFound() { file.delete(); zipFile.delete(); - instance.setDownloader(new Downloader("test GH") { + instance.setDownloader(new Downloader() { @Override public void downloadFile(String url, String toFile) throws IOException { throw new FileNotFoundException("xyz"); @@ -93,7 +105,7 @@ public void downloadFile(String url, String toFile) throws IOException { assertTrue(file.exists()); assertEquals(1048676, file.length()); - instance.setDownloader(new Downloader("test GH") { + instance.setDownloader(new Downloader() { @Override public void downloadFile(String url, String toFile) throws IOException { throw new SocketTimeoutException("xyz"); @@ -135,6 +147,31 @@ public void testGetEle() { assertEquals(0, instance.getEle(60.251, 18.805), precision); } + @Test + public void testFileNameConsistentWithMinLatLon() { + // Verify getFileName is consistent with getMinLatForTile/getMinLonForTile at tile boundaries. + // With 7-decimal truncation (as done in getEle), values like 44.9999999 are within floating-point + // rounding distance of the 45-degree tile boundary. getFileName must place them in the same tile + // as getMinLatForTile, otherwise a cached tile from a previous query can cause: + // "latitude not in boundary of this file" + double[] boundaryLats = {44.9999999, 49.9999999, 39.9999999, -0.0000001, -5.0000001, -50.0000001}; + double[] boundaryLons = {-101.2347957, -100.0000001, -105.0000001, 9.9999999, -0.0000001}; + for (double lat : boundaryLats) { + for (double lon : boundaryLons) { + if (instance.isOutsideSupportedArea(lat, lon)) continue; + int minLat = instance.down(lat); + int minLon = instance.down(lon); + int expectedLatInt = (60 - minLat) / instance.LAT_DEGREE; + int expectedLonInt = 1 + (minLon + 180) / instance.LAT_DEGREE; + String expectedLon = (expectedLonInt < 10 ? "0" : "") + expectedLonInt; + String expectedLat = (expectedLatInt < 10 ? "_0" : "_") + expectedLatInt; + String expected = "srtm_" + expectedLon + expectedLat; + assertEquals(expected, instance.getFileName(lat, lon), + "getFileName inconsistent with down() for lat=" + lat + " lon=" + lon); + } + } + } + @Disabled @Test public void testGetEleVerticalBorder() { diff --git a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java index fc7302f40d4..751e25c19df 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationInterpolatorTest.java @@ -45,7 +45,7 @@ public abstract class EdgeElevationInterpolatorTest { public void setUp() { accessEnc = new SimpleBooleanEncodedValue("access", true); speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).add(RoadEnvironment.create()).build(); graph = new BaseGraph.Builder(encodingManager).set3D(true).create(); roadEnvEnc = encodingManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); edgeElevationInterpolator = createEdgeElevationInterpolator(); diff --git a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamerTest.java b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamerTest.java index 6aa66093400..f3f50d46f92 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamerTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/EdgeElevationSmoothingRamerTest.java @@ -50,8 +50,8 @@ public void smoothRamer2() { pl2.add(0.002, 0.002, 20); EdgeElevationSmoothingRamer.smooth(pl2, 100); assertEquals(5, pl2.size()); - assertEquals(190, pl2.getEle(1), 1); // modify as too small in interval [0,4] - assertEquals(210, pl2.getEle(2), 1); // modify as too small in interval [0,4] + assertEquals(182.5, pl2.getEle(1), 1); // modify as too small in interval [0,4] + assertEquals(201.3, pl2.getEle(2), 1); // modify as too small in interval [0,4] assertEquals(220, pl2.getEle(3), .1); // keep as it is bigger than maxElevationDelta in interval [0,4] } diff --git a/core/src/test/java/com/graphhopper/reader/dem/EdgeSamplingTest.java b/core/src/test/java/com/graphhopper/reader/dem/EdgeSamplingTest.java index c6cd888518c..831ede9f86a 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/EdgeSamplingTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/EdgeSamplingTest.java @@ -7,7 +7,13 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class EdgeSamplingTest { - private final ElevationProvider elevation = new ElevationProvider() { + private final ElevationProvider CONST_ELE = new ElevationProvider() { + + @Override + public ElevationProvider init() { + return this; + } + @Override public double getEle(double lat, double lon) { return 10; @@ -45,7 +51,7 @@ public void doesNotAddExtraPointBelowThreshold() { in, DistanceCalcEarth.METERS_PER_DEGREE, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(0.0,0.0,0.0), (1.4,0.0,0.0)", round(out).toString()); @@ -61,7 +67,7 @@ public void addsExtraPointAboveThreshold() { in, DistanceCalcEarth.METERS_PER_DEGREE / 2, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(0.0,0.0,0.0), (0.4,0.0,10.0), (0.8,0.0,0.0)", round(out).toString()); @@ -77,7 +83,7 @@ public void addsExtraPointBelowSecondThreshold() { in, DistanceCalcEarth.METERS_PER_DEGREE / 3, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(0.0,0.0,0.0), (0.4,0.0,10.0), (0.8,0.0,0.0)", round(out).toString()); @@ -93,7 +99,7 @@ public void addsTwoPointsAboveThreshold() { in, DistanceCalcEarth.METERS_PER_DEGREE / 4, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(0.0,0.0,0.0), (0.25,0.0,10.0), (0.5,0.0,10.0), (0.75,0.0,0.0)", round(out).toString()); @@ -109,7 +115,7 @@ public void doesntAddPointsCrossingInternationalDateLine() { in, DistanceCalcEarth.METERS_PER_DEGREE, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(0.0,-178.5,0.0), (0.0,-179.5,10.0), (0.0,179.5,10.0), (0.0,178.5,0.0)", round(out).toString()); @@ -125,10 +131,10 @@ public void usesGreatCircleInterpolationOnLongPaths() { in, DistanceCalcEarth.METERS_PER_DEGREE, new DistanceCalcEarth(), - elevation + CONST_ELE ); assertEquals("(88.5,-90.0,0.0), (89.5,-90.0,10.0), (89.5,90.0,10.0), (88.5,90.0,0.0)", round(out).toString()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java index 64eb6c1c005..0404dceeef4 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/GMTEDProviderTest.java @@ -22,11 +22,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.SocketTimeoutException; +import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; @@ -36,10 +38,13 @@ public class GMTEDProviderTest { private double precision = .1; GMTEDProvider instance; + @TempDir + Path tempDir; @BeforeEach public void setUp() { - instance = new GMTEDProvider(); + instance = new GMTEDProvider(tempDir.toString()); + instance.init(); } @AfterEach @@ -88,7 +93,7 @@ public void testFileNotFound() { file.delete(); zipFile.delete(); - instance.setDownloader(new Downloader("test GH") { + instance.setDownloader(new Downloader() { @Override public void downloadFile(String url, String toFile) throws IOException { throw new FileNotFoundException("xyz"); @@ -100,7 +105,7 @@ public void downloadFile(String url, String toFile) throws IOException { assertTrue(file.exists()); assertEquals(1048676, file.length()); - instance.setDownloader(new Downloader("test GH") { + instance.setDownloader(new Downloader() { @Override public void downloadFile(String url, String toFile) throws IOException { throw new SocketTimeoutException("xyz"); diff --git a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java index 02a70c42f99..6593584420e 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/HeightTileTest.java @@ -17,11 +17,14 @@ */ package com.graphhopper.reader.dem; +import com.graphhopper.storage.DAType; import com.graphhopper.storage.DataAccess; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.GHDirectory; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Peter Karich @@ -33,11 +36,12 @@ public void testGetHeight() { // But HeightTile has lat,lon system ('mathematically') int width = 10; int height = 20; - HeightTile instance = new HeightTile(0, 0, width, height, 1e-6, 10, 20); - DataAccess heights = new RAMDirectory().create("tmp"); + double precision = 1e6; + HeightTile instance = new HeightTile(0, 0, width, height, precision, 10, 20); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); heights.create(2 * width * height); instance.setHeights(heights); - init(heights, width, height, 1); + fillGrid(heights, width, height, (short) 1); // x,y=1,7 heights.setShort(2 * (17 * width + 1), (short) 70); @@ -56,10 +60,6 @@ public void testGetHeight() { assertEquals(90, instance.getHeight(0.5, 2.5), 1e-3); assertEquals(90, instance.getHeight(0.0, 2.5), 1e-3); assertEquals(1, instance.getHeight(+0.0, 3), 1e-3); - assertEquals(1, instance.getHeight(-0.5, 3.5), 1e-3); - assertEquals(1, instance.getHeight(-0.5, 3.0), 1e-3); - // fall back to "2,9" if within its boundaries - assertEquals(90, instance.getHeight(-0.5, 2.5), 1e-3); assertEquals(1, instance.getHeight(0, 0), 1e-3); assertEquals(1, instance.getHeight(9, 10), 1e-3); @@ -68,19 +68,56 @@ public void testGetHeight() { // no error assertEquals(1, instance.getHeight(10.5, 5), 1e-3); - assertEquals(1, instance.getHeight(-0.5, 5), 1e-3); - assertEquals(1, instance.getHeight(1, -0.5), 1e-3); - assertEquals(1, instance.getHeight(1, 10.5), 1e-3); + } + + + @Test + public void testPrecision() { + int width = 10; + int height = 10; + // Tile covers 0 to 10 degrees lat/lon + HeightTile instance = new HeightTile(0, 0, width, height, 1e7, 10, 10); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); + heights.create(2 * width * height); + instance.setHeights(heights); + fillGrid(heights, width, height, (short) 0); + + // Set unique values at the physical storage corners + // Remember: y=0 is North (Top), y=height-1 is South (Bottom) + + // 1. North-East Pixel (Array: x=9, y=0) + // Corresponds to Lat ~ 10, Lon ~ 10 + set(heights, width, 9, 0, (short) 100); + + + // Test overshoot in middle of row or column + assertEquals(0, instance.getHeight(-0.0000001, 5), 1e-3, "Small overshoot should be clamped"); + assertEquals(0, instance.getHeight(5, -0.0000001), 1e-3, "Small overshoot should be clamped"); + assertEquals(0, instance.getHeight(10.00000001, 5), 1e-3, "Small overshoot should be clamped"); + assertEquals(0, instance.getHeight(5, 10.00000001), 1e-3, "Small overshoot should be clamped"); + + + // Test overshoot in at corners of array + assertEquals(0, instance.getHeight(0, -0.00000001), 1e-3, "Small overshoot should be clamped"); + assertEquals(0, instance.getHeight(-0.00000001, 0.0), 1e-3, "Small overshoot should be clamped"); + assertEquals(0, instance.getHeight(-0.00000001, -0.00000001), 1e-3, "Small overshoot should be clamped"); + + + assertEquals(100, instance.getHeight(10, 10.00000001), 1e-3, "Small overshoot should be clamped"); + assertEquals(100, instance.getHeight(10.00000001, 10.0), 1e-3, "Small overshoot should be clamped"); + assertEquals(100, instance.getHeight(10.00000001, 10.00000001), 1e-3, "Small overshoot should be clamped"); + + } @Test public void testGetHeightForNegativeTile() { int width = 10; - HeightTile instance = new HeightTile(-20, -20, width, width, 1e-6, 10, 10); - DataAccess heights = new RAMDirectory().create("tmp"); + HeightTile instance = new HeightTile(-20, -20, width, width, 1e6, 10, 10); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); heights.create(2 * 10 * 10); instance.setHeights(heights); - init(heights, width, width, 1); + fillGrid(heights, width, width, (short) 1); // x,y=1,7 heights.setShort(2 * (7 * width + 1), (short) 70); @@ -95,16 +132,68 @@ public void testGetHeightForNegativeTile() { assertEquals(70, instance.getHeight(-18, -19), 1e-3); } + @Test + public void testOutOfBoundsPositiveCoordsThrowsException() { + int width = 10; + // Tile starting at lat 10, lon 10 + HeightTile instance = new HeightTile(10, 10, width, width, 1e6, 10, 10); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); + heights.create(2 * width * width); + instance.setHeights(heights); + + // This should correctly fail + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(9.5, 10.5); // 9.5 is below minLat 10 + }); + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(10.5, 9.5); // 9.5 is below minLon 10 + }); + + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(9.5, 20.5); + }); + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(20.5, 9.5); + }); + } + + @Test + public void testOutOfBoundsNegativeCoordsThrowsException() { + int width = 10; + // Tile starting at lat 10, lon 10 + HeightTile instance = new HeightTile(-10, -10, width, width, 1e6, 10, 10); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); + heights.create(2 * width * width); + instance.setHeights(heights); + + // This should correctly fail + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(-9.5, -10.5); // -10.5 is below minLat -10 + }); + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(-10.5, -9.5); // -10.5 is below minLon -10 + }); + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(-9.5, 0.5); + }); + + assertThrows(IllegalStateException.class, () -> { + instance.getHeight(0.5, -9.5); + }); + } + @Test public void testInterpolate() { - HeightTile instance = new HeightTile(0, 0, 2, 2, 1e-6, 10, 10).setInterpolate(true); - DataAccess heights = new RAMDirectory().create("tmp"); + HeightTile instance = new HeightTile(0, 0, 2, 2, 1e6, 10, 10).setInterpolate(true); + DataAccess heights = new GHDirectory("", DAType.RAM).create("tmp"); heights.create(2 * 2 * 2); instance.setHeights(heights); - double topLeft = 0; - double topRight = 1; - double bottomLeft = 2; - double bottomRight = 3; + double topLeft = 0, topRight = 1, bottomLeft = 2, bottomRight = 3; set(heights, 2, 0, 0, (short) topLeft); set(heights, 2, 1, 0, (short) topRight); set(heights, 2, 0, 1, (short) bottomLeft); @@ -136,10 +225,10 @@ public void testInterpolate() { assertEquals(Double.NaN, instance.getHeight(5, 5), 1e-3); } - private void init(DataAccess da, int width, int height, int i) { + private void fillGrid(DataAccess da, int width, int height, short defaultHeight) { for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { - set(da, width, x, y, (short) 1); + set(da, width, x, y, defaultHeight); } } } @@ -155,4 +244,4 @@ private double avg(double... ns) { } return sum / ns.length; } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/reader/dem/MultiSource3ElevationProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/MultiSource3ElevationProviderTest.java new file mode 100644 index 00000000000..c7dc3e2fd92 --- /dev/null +++ b/core/src/test/java/com/graphhopper/reader/dem/MultiSource3ElevationProviderTest.java @@ -0,0 +1,99 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Robin Boldt + */ +public class MultiSource3ElevationProviderTest { + MultiSource3ElevationProvider instance; + + @AfterEach + public void tearDown() { + instance.release(); + } + + @Test + public void testGetEleMocked() { + instance = new MultiSource3ElevationProvider( + (CGIARProvider) new CGIARProvider() { + @Override + public double getEle(double lat, double lon) { + return 1; + } + }.init(), + (GMTEDProvider) new GMTEDProvider() { + @Override + public double getEle(double lat, double lon) { + return 2; + } + }.init(), + (SonnyProvider) new SonnyProvider() { + @Override + public double getEle(double lat, double lon) { + return 3; + } + }.init() + ); + + assertEquals(3, instance.getEle(0, 0), .1); + assertEquals(3, instance.getEle(60.0001, 0), .1); + assertEquals(3, instance.getEle(-56.0001, 0), .1); + } + + /* + Enabling this test requires you to change the pom.xml and increase the memory limit for running tests. + Change to: -Xmx500m -Xms500m + Additionally, the Sonny DTM 1" data files need to manually installed into the cache directory /tmp/sonny! + */ + @Disabled + @Test + public void testGetEle() { + instance = new MultiSource3ElevationProvider(); + double precision = .1; + // The first part is copied from the SRTMGL1ProviderTest + assertEquals(338, instance.getEle(49.949784, 11.57517), precision); + assertEquals(462, instance.getEle(49.968668, 11.575127), precision); + assertEquals(462, instance.getEle(49.968682, 11.574842), precision); + assertEquals(3130, instance.getEle(-22.532854, -65.110474), precision); + assertEquals(123, instance.getEle(38.065392, -87.099609), precision); + assertEquals(1616, instance.getEle(40, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.99999999, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.9999999, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.999999, -105.2277023), precision); + assertEquals(982, instance.getEle(47.468668, 14.575127), precision); + assertEquals(1094, instance.getEle(47.467753, 14.573911), precision); + assertEquals(1925, instance.getEle(46.468835, 12.578777), precision); + assertEquals(834, instance.getEle(48.469123, 9.576393), precision); + // The file for this coordinate does not exist, but there is a ferry tagged in OSM + assertEquals(0, instance.getEle(56.4787319, 17.6118363), precision); + assertEquals(0, instance.getEle(56.4787319, 17.6118363), precision); + // The second part is copied from the GMTEDProviderTest + // Outside of SRTM covered area + assertEquals(118, instance.getEle(60.0000001, 16), precision); + assertEquals(0, instance.getEle(60.0000001, 19), precision); + // Stor Roten + assertEquals(4, instance.getEle(60.251, 18.805), precision); + } +} diff --git a/core/src/test/java/com/graphhopper/reader/dem/MultiSourceElevationProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/MultiSourceElevationProviderTest.java index f0c44ad2b12..ae0f3b90c5c 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/MultiSourceElevationProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/MultiSourceElevationProviderTest.java @@ -37,18 +37,18 @@ public void tearDown() { @Test public void testGetEleMocked() { instance = new MultiSourceElevationProvider( - new CGIARProvider() { + (TileBasedElevationProvider) new CGIARProvider() { @Override public double getEle(double lat, double lon) { return 1; } - }, - new GMTEDProvider() { + }.init(), + (GMTEDProvider) new GMTEDProvider() { @Override public double getEle(double lat, double lon) { return 2; } - } + }.init() ); assertEquals(1, instance.getEle(0, 0), .1); @@ -67,18 +67,18 @@ public void testGetEle() { double precision = .1; // The first part is copied from the SRTMGL1ProviderTest assertEquals(338, instance.getEle(49.949784, 11.57517), precision); - assertEquals(468, instance.getEle(49.968668, 11.575127), precision); - assertEquals(467, instance.getEle(49.968682, 11.574842), precision); - assertEquals(3110, instance.getEle(-22.532854, -65.110474), precision); - assertEquals(120, instance.getEle(38.065392, -87.099609), precision); - assertEquals(1617, instance.getEle(40, -105.2277023), precision); - assertEquals(1617, instance.getEle(39.99999999, -105.2277023), precision); - assertEquals(1617, instance.getEle(39.9999999, -105.2277023), precision); - assertEquals(1617, instance.getEle(39.999999, -105.2277023), precision); - assertEquals(1015, instance.getEle(47.468668, 14.575127), precision); - assertEquals(1107, instance.getEle(47.467753, 14.573911), precision); - assertEquals(1930, instance.getEle(46.468835, 12.578777), precision); - assertEquals(844, instance.getEle(48.469123, 9.576393), precision); + assertEquals(456, instance.getEle(49.968668, 11.575127), precision); + assertEquals(450, instance.getEle(49.968682, 11.574842), precision); + assertEquals(3130, instance.getEle(-22.532854, -65.110474), precision); + assertEquals(123, instance.getEle(38.065392, -87.099609), precision); + assertEquals(1616, instance.getEle(40, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.99999999, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.9999999, -105.2277023), precision); + assertEquals(1616, instance.getEle(39.999999, -105.2277023), precision); + assertEquals(1046, instance.getEle(47.468668, 14.575127), precision); + assertEquals(1131, instance.getEle(47.467753, 14.573911), precision); + assertEquals(1915, instance.getEle(46.468835, 12.578777), precision); + assertEquals(841, instance.getEle(48.469123, 9.576393), precision); // The file for this coordinate does not exist, but there is a ferry tagged in OSM assertEquals(0, instance.getEle(56.4787319, 17.6118363), precision); assertEquals(0, instance.getEle(56.4787319, 17.6118363), precision); diff --git a/core/src/test/java/com/graphhopper/reader/dem/PMTilesElevationProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/PMTilesElevationProviderTest.java new file mode 100644 index 00000000000..ca1e135a432 --- /dev/null +++ b/core/src/test/java/com/graphhopper/reader/dem/PMTilesElevationProviderTest.java @@ -0,0 +1,217 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.reader.dem; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.ShortBuffer; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests for PMTilesElevationProvider and PMTilesReader. + */ +public class PMTilesElevationProviderTest { + + private ElevationProvider instance; + + @AfterEach + public void tearDown() { + if (instance != null) + instance.release(); + } + + @Test + public void testHilbertRoundTrip() { + // verify zxy -> tileId -> zxy round-trips correctly + int[][] cases = {{0, 0, 0}, {1, 0, 0}, {1, 1, 1}, {5, 17, 11}, {10, 512, 300}, {11, 1024, 600}, {12, 2048, 1200}}; + for (int[] c : cases) { + long tileId = PMTilesReader.zxyToTileId(c[0], c[1], c[2]); + int[] result = PMTilesReader.tileIdToZxy(tileId); + assertArrayEquals(c, result, "round-trip failed for z=" + c[0] + " x=" + c[1] + " y=" + c[2]); + } + } + + @Test + public void testTileIdZoom0() { + assertEquals(0, PMTilesReader.zxyToTileId(0, 0, 0)); + assertArrayEquals(new int[]{0, 0, 0}, PMTilesReader.tileIdToZxy(0)); + } + + @Test + public void testTileIdOrdering() { + // tile IDs at higher zoom levels should be larger than all tile IDs at lower zoom levels + long maxZ10 = 0; + for (int x = 0; x < (1 << 10); x += 100) { + for (int y = 0; y < (1 << 10); y += 100) { + maxZ10 = Math.max(maxZ10, PMTilesReader.zxyToTileId(10, x, y)); + } + } + long minZ11 = Long.MAX_VALUE; + for (int x = 0; x < (1 << 11); x += 100) { + for (int y = 0; y < (1 << 11); y += 100) { + minZ11 = Math.min(minZ11, PMTilesReader.zxyToTileId(11, x, y)); + } + } + assertTrue(maxZ10 < minZ11, "all zoom 10 tile IDs should be less than all zoom 11 tile IDs"); + } + + @Test + public void testGunzip() throws Exception { + byte[] original = "hello pmtiles".getBytes(); + java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream(); + try (java.util.zip.GZIPOutputStream gos = new java.util.zip.GZIPOutputStream(baos)) { + gos.write(original); + } + byte[] decompressed = PMTilesReader.gunzip(baos.toByteArray()); + assertArrayEquals(original, decompressed); + } + + @Test + public void testRealPMTilesWithZoom10() { + instance = new PMTilesElevationProvider("./files/near-badschandau-z10-11.pmtiles", + PMTilesElevationProvider.TerrainEncoding.TERRARIUM, false, 10, + null).init(); + // Elbe + assertEquals(118, instance.getEle(50.905488, 14.204129), 1); + + // Schrammsteine + assertEquals(384, instance.getEle(50.912142, 14.2076), 1); + // Very close but on the path + assertEquals(377, instance.getEle(50.911849, 14.208042), 1); + assertEquals(384, instance.getEle(50.9119, 14.207466), 1); + } + + @Test + public void testRealPMTilesInterpolate() { + instance = new PMTilesElevationProvider("./files/near-badschandau-z10-11.pmtiles", + PMTilesElevationProvider.TerrainEncoding.TERRARIUM, true, 10, + null).init(); + // Elbe + assertEquals(118, instance.getEle(50.905488, 14.204129), 1); + + // Schrammsteine + assertEquals(386, instance.getEle(50.912142, 14.2076), 1); + // Very close but on the path + assertEquals(370, instance.getEle(50.911849, 14.208042), 1); + assertEquals(370, instance.getEle(50.9119, 14.207466), 1); + } + + @Test + public void testRealPMTilesWithZoom11() { + // created this file via pmtiles and --bbox=14.203657,50.905387,14.208871,50.912341 --minzoom=10 --maxzoom=11 + instance = new PMTilesElevationProvider("./files/near-badschandau-z10-11.pmtiles", + PMTilesElevationProvider.TerrainEncoding.TERRARIUM, false, 11, null). + init(); + + // Elbe + assertEquals(118, instance.getEle(50.905488, 14.204129), 1); + + // Schrammsteine + assertEquals(390, instance.getEle(50.912142, 14.2076), 1); + // Very close but on the path + assertEquals(381, instance.getEle(50.911849, 14.208042), 1); + assertEquals(361, instance.getEle(50.9119, 14.207466), 1); + } + + @Test + public void testOutsideArea() { + instance = new PMTilesElevationProvider("./files/near-badschandau-z10-11.pmtiles", + PMTilesElevationProvider.TerrainEncoding.TERRARIUM, false, 10, null). + init(); + + // Point far outside the extract — should return NaN + double ele = instance.getEle(0, 0); + assertTrue(Double.isNaN(ele), "expected NaN for point outside extract, got " + ele); + } + + @Test + public void testFillGaps() { + // 4x4 grid of shorts, with two gap pixels + // 100 100 MIN 100 + // 100 MIN 100 100 + // 100 100 100 100 + // 100 100 100 100 + int w = 4, h = 4; + short[][] grid = { + {100, 100, Short.MIN_VALUE, 100}, + {100, Short.MIN_VALUE, 100, 100}, + {100, 100, 100, 100}, + {100, 100, 100, 100}, + }; + byte[] data = new byte[w * h * 2]; + ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + bb.putShort((y * w + x) * 2, grid[y][x]); + + PMTilesElevationProvider.fillGaps(data, w, 0, 0, 1); + + ShortBuffer shorts = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + // Both gap pixels should now be 100 (average of all-100 neighbors) + assertEquals(100, shorts.get(0 * w + 2), "gap at (2,0) should be filled"); + assertEquals(100, shorts.get(1 * w + 1), "gap at (1,1) should be filled"); + // All other pixels unchanged + for (int y = 0; y < h; y++) + for (int x = 0; x < w; x++) + if (!((x == 2 && y == 0) || (x == 1 && y == 1))) + assertEquals(100, shorts.get(y * w + x), "pixel (" + x + "," + y + ") should be unchanged"); + } + + @Test + public void testFillGapsPropagatesToInterior() { + // 5x1 strip: 200 MIN MIN MIN 200 + // After fill: 200 200 200 200 200 + int w = 5; + byte[] data = new byte[w * 2]; + ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + bb.putShort(0, (short) 200); + bb.putShort(2, Short.MIN_VALUE); + bb.putShort(4, Short.MIN_VALUE); + bb.putShort(6, Short.MIN_VALUE); + bb.putShort(8, (short) 200); + + PMTilesElevationProvider.fillGaps(data, w, 0, 0, 1); + + ShortBuffer shorts = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + assertEquals(200, shorts.get(0)); + assertEquals(200, shorts.get(1)); // filled from left neighbor + assertEquals(200, shorts.get(2)); // filled from both propagated neighbors + assertEquals(200, shorts.get(3)); // filled from right neighbor + assertEquals(200, shorts.get(4)); + } + + @Test + public void testFillGapsIsolatedGapUnchanged() { + // 3x3 grid, all MIN_VALUE — no valid data to propagate from + int w = 3; + byte[] data = new byte[w * w * 2]; + ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + for (int i = 0; i < w * w; i++) + bb.putShort(i * 2, Short.MIN_VALUE); + + PMTilesElevationProvider.fillGaps(data, w, 0, 0, 1); + + ShortBuffer shorts = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer(); + for (int i = 0; i < w * w; i++) + assertEquals(Short.MIN_VALUE, shorts.get(i), "isolated gap at index " + i + " should remain MIN_VALUE"); + } +} diff --git a/core/src/test/java/com/graphhopper/reader/dem/PackedTileCodecTest.java b/core/src/test/java/com/graphhopper/reader/dem/PackedTileCodecTest.java new file mode 100644 index 00000000000..eef605bf138 --- /dev/null +++ b/core/src/test/java/com/graphhopper/reader/dem/PackedTileCodecTest.java @@ -0,0 +1,93 @@ +package com.graphhopper.reader.dem; + +import org.junit.jupiter.api.Test; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +import static org.junit.jupiter.api.Assertions.*; + +public class PackedTileCodecTest { + + @Test + public void testPackedRoundTripBlockTypes() { + int tileSize = 32; + short[] values = new short[tileSize * tileSize]; + + // Block (0,0): SEA + fillBlock(values, tileSize, 0, 0, 16, 16, (short) 0); + + // Block (1,0): CONST + fillBlock(values, tileSize, 16, 0, 16, 16, (short) 1234); + + // Block (0,1): DELTA8 full unsigned range [0,255] + for (int y = 16; y < 32; y++) { + for (int x = 0; x < 16; x++) { + int idx = y * tileSize + x; + values[idx] = (short) (500 + (y - 16) * 16 + x); + } + } + + // Block (1,1): RAW16 (large range) + for (int y = 16; y < 32; y++) { + for (int x = 16; x < 32; x++) { + int idx = y * tileSize + x; + values[idx] = (short) ((x - 16) * 1000 - (y - 16) * 900); + } + } + + byte[] raw = toLeBytes(values); + byte[] packed = PackedTileCodec.encodePacked(raw, tileSize, 16); + ByteBuffer packedBuf = ByteBuffer.wrap(packed).order(ByteOrder.LITTLE_ENDIAN); + assertTrue(PackedTileCodec.isPackedTile(packedBuf)); + PackedTileCodec.PackedHeader h = PackedTileCodec.readPackedHeader(packedBuf); + + assertEquals(tileSize, h.tileSize()); + assertEquals(16, h.blockSize()); + assertEquals(2, h.blocksPerAxis()); + + // decode all samples via same read logic as provider + for (int y = 0; y < tileSize; y++) { + for (int x = 0; x < tileSize; x++) { + short actual = samplePacked(packedBuf, h, x, y); + short expected = values[y * tileSize + x]; + assertEquals(expected, actual, "Mismatch at x=" + x + ", y=" + y); + } + } + } + + private static void fillBlock(short[] values, int tileSize, int x0, int y0, int bw, int bh, short val) { + for (int y = y0; y < y0 + bh; y++) { + for (int x = x0; x < x0 + bw; x++) { + values[y * tileSize + x] = val; + } + } + } + + private static byte[] toLeBytes(short[] values) { + ByteBuffer bb = ByteBuffer.allocate(values.length * 2).order(ByteOrder.LITTLE_ENDIAN); + for (short v : values) bb.putShort(v); + return bb.array(); + } + + private static short samplePacked(ByteBuffer data, PackedTileCodec.PackedHeader h, int x, int y) { + int blockX = x / h.blockSize(); + int blockY = y / h.blockSize(); + int blockIndex = blockY * h.blocksPerAxis() + blockX; + int blockStart = h.payloadOffset() + h.blockOffsets()[blockIndex]; + int localX = x - blockX * h.blockSize(); + int localY = y - blockY * h.blockSize(); + int idx = localY * h.blockSize() + localX; + int type = data.get(blockStart) & 0xFF; + + if (type == PackedTileCodec.TYPE_SEA) return 0; + if (type == PackedTileCodec.TYPE_CONST) return data.getShort(blockStart + 1); + if (type == PackedTileCodec.TYPE_DELTA8) { + short base = data.getShort(blockStart + 1); + int delta = data.get(blockStart + 3 + idx) & 0xFF; + return (short) (base + delta); + } + if (type == PackedTileCodec.TYPE_RAW16) return data.getShort(blockStart + 1 + idx * 2); + throw new IllegalStateException("Unknown type " + type); + } +} diff --git a/core/src/test/java/com/graphhopper/reader/dem/SRTMProviderTest.java b/core/src/test/java/com/graphhopper/reader/dem/SRTMProviderTest.java index 95685a762d6..c742b2f38a4 100644 --- a/core/src/test/java/com/graphhopper/reader/dem/SRTMProviderTest.java +++ b/core/src/test/java/com/graphhopper/reader/dem/SRTMProviderTest.java @@ -36,6 +36,7 @@ public class SRTMProviderTest { @BeforeEach public void setUp() { instance = new SRTMProvider(); + instance.init(); } @AfterEach @@ -57,8 +58,9 @@ public void testGetFileString() { } @Test - public void testGetHeight() throws IOException { + public void testGetHeight() { instance = new SRTMProvider("./files/"); + instance.init(); // easy to verify orientation of tile: // instance.getEle(43, 13); @@ -86,16 +88,17 @@ public void testGetHeight() throws IOException { } @Test - public void testGetHeight_issue545() throws IOException { + public void testGetHeight_issue545() { instance = new SRTMProvider("./files/"); - + instance.init(); // test different precision of the elevation file (3600) assertEquals(84, instance.getEle(48.003878, -124.660492), 1e-1); } @Test - public void testGetHeightMMap() throws IOException { + public void testGetHeightMMap() { instance = new SRTMProvider("./files/"); + instance.init(); assertEquals(161, instance.getEle(55.8943144, -3), 1e-1); } @@ -103,6 +106,7 @@ public void testGetHeightMMap() throws IOException { @Test public void testGetEle() { instance = new SRTMProvider(); + instance.init(); assertEquals(337, instance.getEle(49.949784, 11.57517), precision); assertEquals(466, instance.getEle(49.968668, 11.575127), precision); assertEquals(466, instance.getEle(49.968682, 11.574842), precision); @@ -129,6 +133,7 @@ public void testGetEle() { @Test public void testGetEleVerticalBorder() { instance = new SRTMProvider(); + instance.init(); // Border between the tiles N42E011 and N43E011 assertEquals("Eurasia/N42E011", instance.getFileName(42.999999, 11.48)); assertEquals(419, instance.getEle(42.999999, 11.48), precision); @@ -140,6 +145,7 @@ public void testGetEleVerticalBorder() { @Test public void testGetEleHorizontalBorder() { instance = new SRTMProvider(); + instance.init(); // Border between the tiles N42E011 and N42E012 assertEquals("Eurasia/N42E011", instance.getFileName(42.1, 11.999999)); assertEquals(324, instance.getEle(42.1, 11.999999), precision); @@ -151,6 +157,7 @@ public void testGetEleHorizontalBorder() { @Test public void testDownloadIssue_1274() { instance = new SRTMProvider(); + instance.init(); // The file is incorrectly named on the sever: N55W061hgt.zip (it should be N55W061.hgt.zip) assertEquals("North_America/N55W061", instance.getFileName(55.055,-60.541)); assertEquals(204, instance.getEle(55.055,-60.541), .1); diff --git a/core/src/test/java/com/graphhopper/reader/osm/ExtractMembersTest.java b/core/src/test/java/com/graphhopper/reader/osm/ExtractMembersTest.java index 2a9c1c3155c..000e6ebe8d7 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/ExtractMembersTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/ExtractMembersTest.java @@ -41,7 +41,7 @@ void simpleViaNode() throws OSMRestrictionException { relation.add(new ReaderRelation.Member(WAY, 1, "from")); relation.add(new ReaderRelation.Member(NODE, 2, "via")); relation.add(new ReaderRelation.Member(WAY, 3, "to")); - RestrictionMembers restrictionMembers = RestrictionConverter.extractMembers(relation); + RestrictionMembers restrictionMembers = OSMRestrictionConverter.extractMembers(relation); assertEquals(LongArrayList.from(1), restrictionMembers.getFromWays()); assertEquals(2, restrictionMembers.getViaOSMNode()); assertEquals(LongArrayList.from(3), restrictionMembers.getToWays()); @@ -52,7 +52,7 @@ void simpleViaNode() throws OSMRestrictionException { void noVia() { relation.add(new ReaderRelation.Member(WAY, 1, "from")); relation.add(new ReaderRelation.Member(WAY, 2, "to")); - OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> RestrictionConverter.extractMembers(relation)); + OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> OSMRestrictionConverter.extractMembers(relation)); assertTrue(e.getMessage().contains("has no member with role 'via'"), e.getMessage()); } @@ -62,7 +62,7 @@ void multipleViaNodes() { relation.add(new ReaderRelation.Member(NODE, 2, "via")); relation.add(new ReaderRelation.Member(NODE, 3, "via")); relation.add(new ReaderRelation.Member(WAY, 4, "to")); - OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> RestrictionConverter.extractMembers(relation)); + OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> OSMRestrictionConverter.extractMembers(relation)); assertTrue(e.getMessage().contains("has multiple members with role 'via' and type 'node'"), e.getMessage()); } @@ -72,7 +72,7 @@ void multipleFromButNotNoEntry() { relation.add(new ReaderRelation.Member(WAY, 2, "from")); relation.add(new ReaderRelation.Member(NODE, 3, "via")); relation.add(new ReaderRelation.Member(WAY, 4, "to")); - OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> RestrictionConverter.extractMembers(relation)); + OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> OSMRestrictionConverter.extractMembers(relation)); assertTrue(e.getMessage().contains("has multiple members with role 'from' even though it is not a 'no_entry' restriction"), e.getMessage()); } @@ -83,7 +83,7 @@ void noEntry() throws OSMRestrictionException { relation.add(new ReaderRelation.Member(WAY, 2, "from")); relation.add(new ReaderRelation.Member(NODE, 3, "via")); relation.add(new ReaderRelation.Member(WAY, 4, "to")); - RestrictionMembers res = RestrictionConverter.extractMembers(relation); + RestrictionMembers res = OSMRestrictionConverter.extractMembers(relation); assertEquals(LongArrayList.from(1, 2), res.getFromWays()); assertEquals(3, res.getViaOSMNode()); assertEquals(LongArrayList.from(4), res.getToWays()); @@ -96,7 +96,7 @@ void multipleToButNoNoExit() { relation.add(new ReaderRelation.Member(NODE, 2, "via")); relation.add(new ReaderRelation.Member(WAY, 3, "to")); relation.add(new ReaderRelation.Member(WAY, 4, "to")); - OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> RestrictionConverter.extractMembers(relation)); + OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> OSMRestrictionConverter.extractMembers(relation)); assertTrue(e.getMessage().contains("has multiple members with role 'to' even though it is not a 'no_exit' restriction"), e.getMessage()); } @@ -107,10 +107,10 @@ void noExit() throws OSMRestrictionException { relation.add(new ReaderRelation.Member(NODE, 2, "via")); relation.add(new ReaderRelation.Member(WAY, 3, "to")); relation.add(new ReaderRelation.Member(WAY, 4, "to")); - RestrictionMembers res = RestrictionConverter.extractMembers(relation); + RestrictionMembers res = OSMRestrictionConverter.extractMembers(relation); assertEquals(LongArrayList.from(1), res.getFromWays()); assertEquals(2, res.getViaOSMNode()); assertEquals(LongArrayList.from(3, 4), res.getToWays()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java index 8f800f218bd..aca5e87cdca 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java @@ -23,6 +23,7 @@ import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.routing.ev.EncodedValue; import com.graphhopper.routing.lm.LandmarkStorage; import com.graphhopper.routing.util.EdgeFilter; @@ -70,10 +71,9 @@ public void tearDown() { @Test public void testLoadOSM() { String profile = "car_profile"; - String vehicle = "car"; GraphHopper hopper = new GraphHopper(). setStoreOnFlush(true). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); @@ -87,7 +87,7 @@ public void testLoadOSM() { // no encoding manager necessary hopper = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setStoreOnFlush(true); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); hopper.setGraphHopperLocation(ghLoc); @@ -117,9 +117,8 @@ public void testLoadOSM() { @Test public void testLoadOSMNoCH() { final String profile = "profile"; - final String vehicle = "car"; GraphHopper gh = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setStoreOnFlush(true). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); @@ -134,7 +133,7 @@ public void testLoadOSMNoCH() { gh.close(); gh = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setStoreOnFlush(true). setGraphHopperLocation(ghLoc); assertTrue(gh.load()); @@ -146,7 +145,7 @@ public void testLoadOSMNoCH() { gh.close(); gh = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed(profile)). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); @@ -157,7 +156,7 @@ public void testLoadOSMNoCH() { @Test public void testQueryLocationIndexWithBBox() { final GraphHopper gh = new GraphHopper(). - setProfiles(new Profile("car").setVehicle("car")). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setGraphHopperLocation(ghLoc). setOSMFile("../core/files/monaco.osm.gz"); @@ -211,23 +210,23 @@ protected boolean goFurther(int nodeId) { public void testLoadingWithDifferentCHConfig_issue471_pr1488() { // when there is a single CH profile we can also load GraphHopper without it // in #471 this was forbidden, but later it was allowed again, see #1488 - final String profile = "profile"; - final String vehicle = "car"; + Profile profile = TestProfiles.constantSpeed("car"); + GraphHopper gh = new GraphHopper(). setStoreOnFlush(true). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(profile). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); - gh.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); + gh.getCHPreparationHandler().setCHProfiles(new CHProfile(profile.getName())); gh.importOrLoad(); - GHResponse rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile(profile)); + GHResponse rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile("car")); assertFalse(rsp.hasErrors()); assertEquals(3, rsp.getBest().getPoints().size()); gh.close(); // now load GH without CH profile gh = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(profile). setStoreOnFlush(true). setGraphHopperLocation(ghLoc); gh.load(); @@ -238,22 +237,22 @@ public void testLoadingWithDifferentCHConfig_issue471_pr1488() { // when there is no CH preparation yet it will be added (CH delta import) gh = new GraphHopper(). setStoreOnFlush(true). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(profile). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); gh.importOrLoad(); - rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile(profile)); + rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile("car")); assertFalse(rsp.hasErrors()); assertEquals(3, rsp.getBest().getPoints().size()); gh.close(); gh = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(new Profile(profile)). setStoreOnFlush(true); - gh.getCHPreparationHandler().setCHProfiles(new CHProfile("profile")); + gh.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); gh.setGraphHopperLocation(ghLoc); gh.importOrLoad(); - rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile(profile)); + rsp = gh.route(new GHRequest(51.2492152, 9.4317166, 51.2, 9.4).setProfile("car")); assertFalse(rsp.hasErrors()); assertEquals(3, rsp.getBest().getPoints().size()); // no error @@ -261,23 +260,22 @@ public void testLoadingWithDifferentCHConfig_issue471_pr1488() { @Test public void testAllowMultipleReadingInstances() { - String vehicle = "car"; GraphHopper instance1 = new GraphHopper(). - setProfiles(new Profile(vehicle).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); instance1.importOrLoad(); GraphHopper instance2 = new GraphHopper(). - setProfiles(new Profile(vehicle).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setOSMFile(testOsm). setGraphHopperLocation(ghLoc); instance2.load(); GraphHopper instance3 = new GraphHopper(). - setProfiles(new Profile(vehicle).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setOSMFile(testOsm). setGraphHopperLocation(ghLoc); @@ -305,7 +303,7 @@ protected void importOSM() { } }.setStoreOnFlush(true). setGraphHopperLocation(ghLoc). - setProfiles(new Profile("car").setVehicle("car")). + setProfiles(TestProfiles.constantSpeed("car")). setOSMFile(testOsm); final AtomicReference ar = new AtomicReference<>(); Thread thread = new Thread() { @@ -321,7 +319,7 @@ public void run() { thread.start(); GraphHopper instance2 = new GraphHopper(). - setProfiles(new Profile("car").setVehicle("car")). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setOSMFile(testOsm). setGraphHopperLocation(ghLoc); @@ -349,11 +347,10 @@ public void run() { @Test public void testPrepare() { final String profile = "profile"; - final String vehicle = "car"; instance = new GraphHopper(). setStoreOnFlush(false). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(TestProfiles.constantSpeed("profile")). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); instance.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); @@ -370,14 +367,13 @@ public void testPrepare() { public void testFootAndCar() { final String profile1 = "profile1"; final String profile2 = "profile2"; - final String vehicle1 = "car"; - final String vehicle2 = "foot"; // now all ways are imported instance = new GraphHopper(). + setEncodedValuesString("car_access, car_average_speed, foot_access, foot_average_speed"). setProfiles( - new Profile(profile1).setVehicle(vehicle1), - new Profile(profile2).setVehicle(vehicle2) + TestProfiles.accessAndSpeed(profile1, "car"), + TestProfiles.accessAndSpeed(profile2, "foot") ). setStoreOnFlush(false). setGraphHopperLocation(ghLoc). @@ -410,7 +406,7 @@ public void testFootAndCar() { assertFalse(grsp.hasErrors()); rsp = grsp.getBest(); assertEquals(2, rsp.getPoints().size()); - // => found a point on edge A-B + // => found a point on edge A-B assertEquals(11.680, rsp.getPoints().getLat(1), 1e-3); assertEquals(50.644, rsp.getPoints().getLon(1), 1e-3); @@ -428,69 +424,48 @@ public void testFootAndCar() { } @Test - public void testNothingHappensWhenFlagEncodersAreChangedForLoad() { + public void testUnloadProfile() { instance = new GraphHopper().init( new GraphHopperConfig(). putObject("datareader.file", testOsm3). putObject("datareader.dataaccess", "RAM"). - putObject("graph.vehicles", "foot,car"). putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car") + setProfiles(List.of( + TestProfiles.constantSpeed("foot"), + TestProfiles.constantSpeed("car") ))). setGraphHopperLocation(ghLoc); instance.importOrLoad(); assertEquals(5, instance.getBaseGraph().getNodes()); instance.close(); - // different flagEncoder list has no effect when loading, so it does not matter, but the profiles must be the same + // we can run GH without the car profile GraphHopper tmpGH = new GraphHopper().init( new GraphHopperConfig(). putObject("datareader.file", testOsm3). putObject("datareader.dataaccess", "RAM"). - putObject("graph.vehicles", "foot"). putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList( - new Profile("foot").setVehicle("foot") + setProfiles(List.of( + TestProfiles.constantSpeed("foot") ))). - setOSMFile(testOsm3). setGraphHopperLocation(ghLoc); - IllegalStateException e = assertThrows(IllegalStateException.class, tmpGH::load); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); + tmpGH.load(); + assertEquals(5, instance.getBaseGraph().getNodes()); - // different order of graph.vehicles is also fine, but profiles must be in same order - tmpGH = new GraphHopper().init(new GraphHopperConfig(). - putObject("datareader.file", testOsm3). - putObject("datareader.dataaccess", "RAM"). - putObject("graph.vehicles", "car,foot"). - putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("foot").setVehicle("foot") - ))). - setOSMFile(testOsm3) - .setGraphHopperLocation(ghLoc); - e = assertThrows(IllegalStateException.class, tmpGH::load); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); - - // different encoded values do not matter either + // different encoded values do not matter, since they are ignored when loading the graph anyway instance = new GraphHopper().init( new GraphHopperConfig(). putObject("datareader.file", testOsm3). putObject("datareader.dataaccess", "RAM"). putObject("graph.encoded_values", "road_class"). - putObject("graph.vehicles", "foot,car"). putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car") + setProfiles(List.of( + TestProfiles.constantSpeed("car") ))). - setOSMFile(testOsm3). setGraphHopperLocation(ghLoc); instance.load(); assertEquals(5, instance.getBaseGraph().getNodes()); - assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,roundabout,road_class,road_class_link,road_environment,max_speed,road_access,ferry_speed,foot_network", + assertEquals("road_class,road_environment,roundabout,car_access,road_class_link,max_speed,foot_subnetwork,car_subnetwork", instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(","))); } @@ -500,12 +475,8 @@ public void testFailsForWrongEVConfig() { new GraphHopperConfig(). putObject("datareader.file", testOsm3). putObject("datareader.dataaccess", "RAM"). - putObject("graph.vehicles", "foot,car"). putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car") - ))). + setProfiles(List.of(TestProfiles.constantSpeed("car")))). setGraphHopperLocation(ghLoc); instance.importOrLoad(); // older versions <= 0.12 did not store this property, ensure that we fail to load it @@ -521,24 +492,19 @@ public void testFailsForWrongEVConfig() { putObject("datareader.dataaccess", "RAM"). putObject("graph.location", ghLoc). putObject("graph.encoded_values", "road_environment,road_class"). - putObject("graph.vehicles", "foot,car"). putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car") - ))). + setProfiles(List.of(TestProfiles.constantSpeed("car")))). setOSMFile(testOsm3); instance.load(); assertEquals(5, instance.getBaseGraph().getNodes()); - assertEquals("foot_access,foot_average_speed,foot_priority,car_access,car_average_speed,foot_subnetwork,car_subnetwork,roundabout,road_class,road_class_link,road_environment,max_speed,road_access,ferry_speed,foot_network", instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(","))); + assertEquals("road_class,road_environment,roundabout,car_access,road_class_link,max_speed,car_subnetwork", instance.getEncodingManager().getEncodedValues().stream().map(EncodedValue::getName).collect(Collectors.joining(","))); } @Test public void testNoNPE_ifLoadNotSuccessful() { String profile = "profile"; - String vehicle = "car"; instance = new GraphHopper(). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setProfiles(new Profile(profile)). setStoreOnFlush(true). setGraphHopperLocation(ghLoc); try { @@ -555,7 +521,7 @@ public void testNoNPE_ifLoadNotSuccessful() { @Test public void testDoesNotCreateEmptyFolderIfLoadingFromNonExistingPath() { instance = new GraphHopper(); - instance.setProfiles(new Profile("car").setVehicle("car")); + instance.setProfiles(new Profile("car")); instance.setGraphHopperLocation(ghLoc); assertFalse(instance.load()); assertFalse(new File(ghLoc).exists()); @@ -576,7 +542,7 @@ public void testFailsForMissingParameters() { // missing OSM file to import instance = new GraphHopper(). - setProfiles(new Profile("car").setVehicle("car")). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(true). setGraphHopperLocation(ghLoc); ex = assertThrows(IllegalStateException.class, instance::importOrLoad); @@ -593,7 +559,7 @@ public void testFailsForMissingParameters() { // Import is possible even if no storeOnFlush is specified BUT here we miss the OSM file instance = new GraphHopper(). - setProfiles(new Profile("car").setVehicle("car")). + setProfiles(TestProfiles.constantSpeed("car")). setStoreOnFlush(false). setGraphHopperLocation(ghLoc); ex = assertThrows(IllegalStateException.class, instance::importOrLoad); @@ -605,14 +571,14 @@ public void testFailsForMissingParameters() { public void testFootOnly() { // now only footable ways are imported => no A D C and B D E => the other both ways have pillar nodes! final String profile = "foot_profile"; - final String vehicle = "foot"; instance = new GraphHopper(). setStoreOnFlush(false). - setProfiles(new Profile(profile).setVehicle(vehicle)). + setEncodedValuesString("foot_access, foot_priority, foot_average_speed"). + setProfiles(TestProfiles.accessSpeedAndPriority(profile, "foot")). setGraphHopperLocation(ghLoc). setOSMFile(testOsm3); // exclude motorways which aren't accessible for foot - instance.getReaderConfig().setIgnoredHighways(Arrays.asList("motorway")); + instance.getReaderConfig().setIgnoredHighways(List.of("motorway")); instance.getCHPreparationHandler().setCHProfiles(new CHProfile(profile)); instance.importOrLoad(); @@ -632,14 +598,14 @@ public void testFootOnly() { @Test public void testVia() { final String profile = "profile"; - final String vehicle = "car"; + instance = new GraphHopper().setStoreOnFlush(true). init(new GraphHopperConfig(). putObject("datareader.file", testOsm3). putObject("prepare.min_network_size", 0). - putObject("graph.vehicles", vehicle). + putObject("graph.encoded_values", "car_access, car_average_speed"). putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList(new Profile(profile).setVehicle(vehicle))). + setProfiles(List.of(TestProfiles.accessAndSpeed(profile, "car"))). setCHProfiles(Collections.singletonList(new CHProfile(profile))) ). setGraphHopperLocation(ghLoc); @@ -673,19 +639,19 @@ public void testMultipleCHPreparationsInParallel() { GraphHopper hopper = new GraphHopper(). setStoreOnFlush(false). setProfiles( - new Profile("car_profile").setVehicle("car"), - new Profile("mtb_profile").setVehicle("mtb"), - new Profile("bike_profile").setVehicle("racingbike"), - new Profile("foot_profile").setVehicle("foot") + TestProfiles.constantSpeed("p1", 60), + TestProfiles.constantSpeed("p2", 70), + TestProfiles.constantSpeed("p3", 80), + TestProfiles.constantSpeed("p4", 90) ). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); hopper.getCHPreparationHandler() .setCHProfiles( - new CHProfile("car_profile"), - new CHProfile("mtb_profile"), - new CHProfile("bike_profile"), - new CHProfile("foot_profile") + new CHProfile("p1"), + new CHProfile("p2"), + new CHProfile("p3"), + new CHProfile("p4") ) .setPreparationThreads(threadCount); @@ -720,19 +686,19 @@ public void testMultipleLMPreparationsInParallel() { GraphHopper hopper = new GraphHopper(). setStoreOnFlush(false). setProfiles(Arrays.asList( - new Profile("car_profile").setVehicle("car"), - new Profile("mtb_profile").setVehicle("mtb"), - new Profile("bike_profile").setVehicle("racingbike"), - new Profile("foot_profile").setVehicle("foot") + TestProfiles.constantSpeed("p1", 60), + TestProfiles.constantSpeed("p2", 70), + TestProfiles.constantSpeed("p3", 80), + TestProfiles.constantSpeed("p4", 90) )). setGraphHopperLocation(ghLoc). setOSMFile(testOsm); hopper.getLMPreparationHandler(). setLMProfiles( - new LMProfile("car_profile"), - new LMProfile("mtb_profile"), - new LMProfile("bike_profile"), - new LMProfile("foot_profile") + new LMProfile("p1"), + new LMProfile("p2"), + new LMProfile("p3"), + new LMProfile("p4") ). setPreparationThreads(threadCount); @@ -760,11 +726,11 @@ public void testMultipleLMPreparationsInParallel() { } @Test - public void testGetMultipleWeightingsForCH() { + public void testMultipleProfilesForCH() { GraphHopper hopper = new GraphHopper(). setProfiles( - new Profile("profile1").setVehicle("car"), - new Profile("profile2").setCustomModel(new CustomModel().setDistanceInfluence(1000d)).setVehicle("car") + TestProfiles.constantSpeed("profile1", 60), + TestProfiles.constantSpeed("profile2", 100) ). setStoreOnFlush(false). setGraphHopperLocation(ghLoc). @@ -779,61 +745,51 @@ public void testGetMultipleWeightingsForCH() { @Test public void testProfilesMustNotBeChanged() { { - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("custom").setCustomModel(new CustomModel().setDistanceInfluence(3d)).setVehicle("car") + GraphHopper hopper = createHopperWithProfiles(List.of( + TestProfiles.constantSpeed("bike1", 60), + TestProfiles.constantSpeed("bike2", 120) )); hopper.importOrLoad(); hopper.close(); } { // load without problem - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("custom").setCustomModel(new CustomModel().setDistanceInfluence(3d)).setVehicle("car") + GraphHopper hopper = createHopperWithProfiles(List.of( + TestProfiles.constantSpeed("bike1", 60), + TestProfiles.constantSpeed("bike2", 120) )); hopper.importOrLoad(); hopper.close(); } - { - // problem: the vehicle was changed. this is not allowed. - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("bike"), - new Profile("custom").setCustomModel(new CustomModel().setDistanceInfluence(3d)).setVehicle("car") - )); - IllegalStateException e = assertThrows(IllegalStateException.class, hopper::importOrLoad); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); - hopper.close(); - } { // problem: the profile changed (slightly). we do not allow this because we would potentially need to re-calculate the subnetworks - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("custom").setCustomModel(new CustomModel().setDistanceInfluence(80d)).setVehicle("car") + GraphHopper hopper = createHopperWithProfiles(List.of( + TestProfiles.constantSpeed("bike1", 60), + TestProfiles.constantSpeed("bike2", 110) )); IllegalStateException e = assertThrows(IllegalStateException.class, hopper::importOrLoad); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); + assertTrue(e.getMessage().contains("Profile 'bike2' does not match"), e.getMessage()); hopper.close(); } { - // problem: we add another profile, which is not allowed, because there would be no subnetwork ev for it - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("custom").setCustomModel(new CustomModel().setDistanceInfluence(3d)).setVehicle("car"), - new Profile("car2").setVehicle("car") + // problem: we add another profile, which is not allowed either, because there would be no subnetwork ev for it + GraphHopper hopper = createHopperWithProfiles(List.of( + TestProfiles.constantSpeed("bike1", 60), + TestProfiles.constantSpeed("bike2", 120), + TestProfiles.constantSpeed("bike3", 110) )); IllegalStateException e = assertThrows(IllegalStateException.class, hopper::importOrLoad); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); + assertTrue(e.getMessage().contains("You cannot add new profiles to the loaded graph. Profile 'bike3' is new"), e.getMessage()); hopper.close(); } { - // problem: we remove a profile, which would technically be possible, but does not save memory either. it - // could be useful to disable a profile, but currently we just force a new import. - GraphHopper hopper = createHopperWithProfiles(Arrays.asList( - new Profile("car").setVehicle("car") + // disabling a profile by not 'loading' it is ok + GraphHopper hopper = createHopperWithProfiles(List.of( + TestProfiles.constantSpeed("bike2", 120) )); - IllegalStateException e = assertThrows(IllegalStateException.class, hopper::importOrLoad); - assertTrue(e.getMessage().contains("Profiles do not match"), e.getMessage()); + hopper.importOrLoad(); + assertEquals(1, hopper.getProfiles().size()); + assertEquals("bike2", hopper.getProfiles().get(0).getName()); hopper.close(); } } @@ -851,18 +807,18 @@ private GraphHopper createHopperWithProfiles(List profiles) { @Test public void testLoadingLMAndCHProfiles() { + Profile profile = TestProfiles.constantSpeed("car"); GraphHopper hopper = new GraphHopper() .setGraphHopperLocation(ghLoc) .setOSMFile(testOsm) - .setProfiles(new Profile("car").setVehicle("car")); + .setProfiles(profile); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.importOrLoad(); hopper.close(); // load without problem - hopper = new GraphHopper() - .setProfiles(new Profile("car").setVehicle("car")); + hopper = new GraphHopper().setProfiles(profile); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.setGraphHopperLocation(ghLoc); @@ -878,8 +834,7 @@ public void testLoadingLMAndCHProfiles() { props.flush(); // problem: LM version does not match the actual profile - hopper = new GraphHopper() - .setProfiles(new Profile("car").setVehicle("car")); + hopper = new GraphHopper().setProfiles(profile); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("car")); hopper.setGraphHopperLocation(ghLoc); IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, hopper::load); @@ -887,8 +842,7 @@ public void testLoadingLMAndCHProfiles() { hopper.close(); // problem: CH version does not match the actual profile - hopper = new GraphHopper() - .setProfiles(new Profile("car").setVehicle("car")); + hopper = new GraphHopper().setProfiles(profile); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.setGraphHopperLocation(ghLoc); ex = assertThrows(IllegalArgumentException.class, hopper::load); diff --git a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java index 5f98c2d4ada..a35087dceef 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/OSMReaderTest.java @@ -28,18 +28,18 @@ import com.graphhopper.reader.dem.ElevationProvider; import com.graphhopper.reader.dem.SRTMProvider; import com.graphhopper.routing.OSMReaderConfig; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.*; -import com.graphhopper.routing.util.countryrules.CountryRuleFactory; import com.graphhopper.routing.util.parsers.CountryParser; import com.graphhopper.routing.util.parsers.OSMBikeNetworkTagParser; -import com.graphhopper.routing.util.parsers.OSMMtbNetworkTagParser; import com.graphhopper.routing.util.parsers.OSMRoadAccessParser; import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; import com.graphhopper.util.details.PathDetail; +import com.graphhopper.util.shapes.GHPoint3D; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -51,6 +51,10 @@ import java.util.HashMap; import java.util.List; +import static com.graphhopper.json.Statement.Else; +import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.Op.LIMIT; +import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.util.GHUtility.readCountries; import static org.junit.jupiter.api.Assertions.*; @@ -197,29 +201,63 @@ public void testOneWay() { @Test public void testFerry() { - GraphHopper hopper = new GraphHopperFacade(file2).importOrLoad(); + GraphHopper hopper = new GraphHopperFacade(file2) { + @Override + public void cleanUp() { + } + }.importOrLoad(); Graph graph = hopper.getBaseGraph(); int n40 = AbstractGraphStorageTester.getIdOf(graph, 54.0); - int n50 = AbstractGraphStorageTester.getIdOf(graph, 55.0); - assertEquals(GHUtility.asSet(n40), GHUtility.getNeighbors(carAllExplorer.setBaseNode(n50))); - // no duration is given => slow speed only! + DecimalEncodedValue ferrySpeedEnc = hopper.getEncodingManager().getDecimalEncodedValue(FerrySpeed.KEY); + + // no duration is given => speed depends on length int n80 = AbstractGraphStorageTester.getIdOf(graph, 54.1); EdgeIterator iter = carOutExplorer.setBaseNode(n80); iter.next(); - assertEquals(6, iter.get(carSpeedEnc), 1e-1); + assertEquals(30, iter.get(ferrySpeedEnc), 1e-1); // duration 01:10 is given => more precise speed calculation! // ~111km (from 54.0,10.1 to 55.0,10.2) in duration=70 minutes => 95km/h => / 1.4 => 68km/h iter = carOutExplorer.setBaseNode(n40); iter.next(); - assertEquals(62, iter.get(carSpeedEnc), 1e-1); + assertEquals(62, iter.get(ferrySpeedEnc), 1e-1); + } + + @Test + public void testPathDetailsOfFerry() { + GraphHopper hopper = new GraphHopperFacade(file2) { + @Override + protected List createProfiles() { + return List.of(new Profile("car").setCustomModel(new CustomModel(). + addToPriority(If("!car_access", MULTIPLY, "0")). + addToSpeed(If("road_environment == FERRY", LIMIT, "ferry_speed")). + addToSpeed(Else(LIMIT, "car_average_speed")))); + } + }.importOrLoad(); + + GHResponse rsp = hopper.route(new GHRequest(55.0, 10.2, 54.0, 10.1). + setProfile("car"). + setPathDetails(List.of("average_speed", "car_average_speed", "road_environment"))); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + List list = rsp.getBest().getPathDetails().get("average_speed"); + assertEquals(62.0, list.get(0).getValue()); + + list = rsp.getBest().getPathDetails().get("car_average_speed"); + assertEquals(0.0, list.get(0).getValue()); + + list = rsp.getBest().getPathDetails().get("road_environment"); + assertEquals("ferry", list.get(0).getValue()); } @Test public void testMaxSpeed() { - GraphHopper hopper = new GraphHopperFacade(file2).importOrLoad(); + GraphHopper hopper = new GraphHopperFacade(file2) { + @Override + public void cleanUp() { + } + }.importOrLoad(); Graph graph = hopper.getBaseGraph(); int n60 = AbstractGraphStorageTester.getIdOf(graph, 56.0); @@ -244,7 +282,7 @@ public void testWayReferencesNotExistingAdjNode_issue19() { @Test public void testDoNotRejectEdgeIfFirstNodeIsMissing_issue2221() { - GraphHopper hopper = new GraphHopperFacade("test-osm9.xml").importOrLoad(); + GraphHopper hopper = new GraphHopperFacade("test-osm9.xml").setSortGraph(false).importOrLoad(); BaseGraph graph = hopper.getBaseGraph(); assertEquals(2, graph.getNodes()); assertEquals(1, graph.getEdges()); @@ -264,7 +302,7 @@ public void testDoNotRejectEdgeIfFirstNodeIsMissing_issue2221() { @Test public void test_edgeDistanceWhenFirstNodeIsMissing_issue2221() { - GraphHopper hopper = new GraphHopperFacade("test-osm10.xml").importOrLoad(); + GraphHopper hopper = new GraphHopperFacade("test-osm10.xml").setSortGraph(false).importOrLoad(); BaseGraph graph = hopper.getBaseGraph(); assertEquals(3, graph.getNodes()); assertEquals(3, graph.getEdges()); @@ -272,7 +310,7 @@ public void test_edgeDistanceWhenFirstNodeIsMissing_issue2221() { while (iter.next()) { assertEquals(DistanceCalcEarth.DIST_EARTH.calcDistance(iter.fetchWayGeometry(FetchMode.ALL)), iter.getDistance(), 1.e-3); } - assertEquals(35.609, graph.getEdgeIteratorState(0, Integer.MIN_VALUE).getDistance(), 1.e-3); + assertEquals(35.612, graph.getEdgeIteratorState(0, Integer.MIN_VALUE).getDistance(), 1.e-3); assertEquals(75.256, graph.getEdgeIteratorState(1, Integer.MIN_VALUE).getDistance(), 1.e-3); assertEquals(143.332, graph.getEdgeIteratorState(2, Integer.MIN_VALUE).getDistance(), 1.e-3); } @@ -313,6 +351,7 @@ public void testNegativeIds() { @Test public void testBarriers() { GraphHopper hopper = new GraphHopperFacade(fileBarriers). + setSortGraph(false). setMinNetworkSize(0). importOrLoad(); @@ -370,33 +409,6 @@ public void testBarrierBetweenWays() { assertEquals(5, loops); } - @Test - public void testFords() { - GraphHopper hopper = new GraphHopper(); - hopper.setVehiclesString("car|block_fords=true"); - hopper.setOSMFile(getClass().getResource("test-barriers3.xml").getFile()). - setGraphHopperLocation(dir). - setProfiles(new Profile("car").setVehicle("car")). - setMinNetworkSize(0). - importOrLoad(); - Graph graph = hopper.getBaseGraph(); - // our way is split into five edges, because there are two ford nodes - assertEquals(5, graph.getEdges()); - BooleanEncodedValue accessEnc = hopper.getEncodingManager().getBooleanEncodedValue(VehicleAccess.key("car")); - int blocked = 0; - int notBlocked = 0; - AllEdgesIterator edge = graph.getAllEdges(); - while (edge.next()) { - if (!edge.get(accessEnc)) - blocked++; - else - notBlocked++; - } - // two blocked edges and three accessible edges - assertEquals(2, blocked); - assertEquals(3, notBlocked); - } - @Test public void avoidsLoopEdges_1525() { // loops in OSM should be avoided by adding additional tower node (see #1525, #1531) @@ -469,8 +481,8 @@ public void testBikeAndMtbRelation() { EnumEncodedValue mtbNetworkEnc = new EnumEncodedValue<>(MtbNetwork.KEY, RouteNetwork.class); EncodingManager manager = new EncodingManager.Builder().add(mtbNetworkEnc).add(bikeNetworkEnc).build(); OSMParsers osmParsers = new OSMParsers() - .addRelationTagParser(relConf -> new OSMBikeNetworkTagParser(bikeNetworkEnc, relConf)) - .addRelationTagParser(relConf -> new OSMMtbNetworkTagParser(mtbNetworkEnc, relConf)); + .addRelationTagParser(relConf -> new OSMBikeNetworkTagParser(bikeNetworkEnc, relConf, "bicycle")) + .addRelationTagParser(relConf -> new OSMBikeNetworkTagParser(mtbNetworkEnc, relConf, "mtb")); ReaderRelation osmRel = new ReaderRelation(1); osmRel.add(new ReaderRelation.Member(ReaderElement.Type.WAY, 1, "")); @@ -508,7 +520,7 @@ public void testBikeAndMtbRelation() { // this is pretty ugly: the mtb network parser writes to the edge flags we pass into it, but at a location we // don't know, so we need to get the internal enc to read the flags below - transformEnc = ((OSMMtbNetworkTagParser) osmParsers.getRelationTagParsers().get(1)).getTransformerRouteRelEnc(); + transformEnc = ((OSMBikeNetworkTagParser) osmParsers.getRelationTagParsers().get(1)).getTransformerRouteRelEnc(); osmRel.setTag("route", "mtb"); osmRel.setTag("network", "lcn"); @@ -531,7 +543,7 @@ public void testBikeAndMtbRelation() { @Test public void testTurnRestrictionsFromXML() { String fileTurnRestrictions = "test-restrictions.xml"; - GraphHopper hopper = new GraphHopperFacade(fileTurnRestrictions, true, ""). + GraphHopper hopper = new GraphHopperFacade(fileTurnRestrictions, ""). importOrLoad(); Graph graph = hopper.getBaseGraph(); @@ -602,7 +614,7 @@ public void testTurnRestrictionsFromXML() { @Test public void testTurnRestrictionsViaHgvTransportationMode() { String fileTurnRestrictions = "test-restrictions.xml"; - GraphHopper hopper = new GraphHopperFacade(fileTurnRestrictions, true, ""). + GraphHopper hopper = new GraphHopperFacade(fileTurnRestrictions, ""). importOrLoad(); Graph graph = hopper.getBaseGraph(); @@ -618,17 +630,16 @@ public void testTurnRestrictionsViaHgvTransportationMode() { int edge3_8 = GHUtility.getEdge(graph, n3, n8).getEdge(); BooleanEncodedValue carTCEnc = hopper.getEncodingManager().getTurnBooleanEncodedValue(TurnRestriction.key("car")); - BooleanEncodedValue roadsTCEnc = hopper.getEncodingManager().getTurnBooleanEncodedValue(TurnRestriction.key("roads")); + BooleanEncodedValue truckTCEnc = hopper.getEncodingManager().getTurnBooleanEncodedValue(TurnRestriction.key("truck")); assertFalse(tcStorage.get(carTCEnc, edge9_3, n3, edge3_8)); - assertTrue(tcStorage.get(roadsTCEnc, edge9_3, n3, edge3_8)); + assertTrue(tcStorage.get(truckTCEnc, edge9_3, n3, edge3_8)); } @Test public void testRoadAttributes() { String fileRoadAttributes = "test-road-attributes.xml"; GraphHopper hopper = new GraphHopperFacade(fileRoadAttributes); - hopper.setEncodedValuesString("max_width,max_height,max_weight"); hopper.importOrLoad(); DecimalEncodedValue widthEnc = hopper.getEncodingManager().getDecimalEncodedValue(MaxWidth.KEY); @@ -698,18 +709,19 @@ public void testReadEleFromDataProvider() { @Test public void testTurnFlagCombination() { GraphHopper hopper = new GraphHopper(); + hopper.setEncodedValuesString("car_average_speed,car_access,bike_access,bike_average_speed,bike_priority"); hopper.setOSMFile(getClass().getResource("test-multi-profile-turn-restrictions.xml").getFile()). setGraphHopperLocation(dir). - setVehiclesString("roads|transportation_mode=HGV|turn_costs=true"). setProfiles( - new Profile("bike").setVehicle("bike").setTurnCosts(true), - new Profile("car").setVehicle("car").setTurnCosts(true), - new Profile("truck").setVehicle("roads").setTurnCosts(true) + TestProfiles.accessAndSpeed("bike").setTurnCostsConfig(new TurnCostsConfig(List.of("bicycle"))), + TestProfiles.accessAndSpeed("car").setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"))), + TestProfiles.accessAndSpeed("truck", "car").setTurnCostsConfig(new TurnCostsConfig(List.of("hgv", "motor_vehicle"))) ). + setSortGraph(false). importOrLoad(); EncodingManager manager = hopper.getEncodingManager(); BooleanEncodedValue carTCEnc = manager.getTurnBooleanEncodedValue(TurnRestriction.key("car")); - BooleanEncodedValue truckTCEnc = manager.getTurnBooleanEncodedValue(TurnRestriction.key("roads")); + BooleanEncodedValue truckTCEnc = manager.getTurnBooleanEncodedValue(TurnRestriction.key("truck")); BooleanEncodedValue bikeTCEnc = manager.getTurnBooleanEncodedValue(TurnRestriction.key("bike")); Graph graph = hopper.getBaseGraph(); @@ -739,7 +751,7 @@ public void testTurnFlagCombination() { @Test public void testConditionalTurnRestriction() { String fileConditionalTurnRestrictions = "test-conditional-turn-restrictions.xml"; - GraphHopper hopper = new GraphHopperFacade(fileConditionalTurnRestrictions, true, ""). + GraphHopper hopper = new GraphHopperFacade(fileConditionalTurnRestrictions, ""). setMinNetworkSize(0). importOrLoad(); @@ -808,7 +820,7 @@ public void testConditionalTurnRestriction() { @Test public void testMultipleTurnRestrictions() { String fileMultipleConditionalTurnRestrictions = "test-multiple-conditional-turn-restrictions.xml"; - GraphHopper hopper = new GraphHopperFacade(fileMultipleConditionalTurnRestrictions, true, ""). + GraphHopper hopper = new GraphHopperFacade(fileMultipleConditionalTurnRestrictions, ""). importOrLoad(); Graph graph = hopper.getBaseGraph(); @@ -850,7 +862,7 @@ public void testMultipleTurnRestrictions() { @Test public void testPreferredLanguage() { - GraphHopper hopper = new GraphHopperFacade(file1, false, "de"). + GraphHopper hopper = new GraphHopperFacade(file1, "de"). importOrLoad(); BaseGraph graph = hopper.getBaseGraph(); int n20 = AbstractGraphStorageTester.getIdOf(graph, 52); @@ -858,7 +870,7 @@ public void testPreferredLanguage() { assertTrue(iter.next()); assertEquals("straße 123, B 122", iter.getName()); - hopper = new GraphHopperFacade(file1, false, "el"). + hopper = new GraphHopperFacade(file1, "el"). importOrLoad(); graph = hopper.getBaseGraph(); n20 = AbstractGraphStorageTester.getIdOf(graph, 52); @@ -894,20 +906,24 @@ public void testCrossBoundary_issue667() { } @Test - public void testRoadClassInfo() { + public void testRoadClassInfoAndFerry() { GraphHopper gh = new GraphHopper() { @Override protected File _getOSMFile() { return new File(getClass().getResource(file2).getFile()); } }.setOSMFile("dummy"). - setProfiles(new Profile("profile").setVehicle("car")). + setEncodedValuesString("car_access,car_average_speed,road_environment,ferry_speed"). + setProfiles(new Profile("car").setCustomModel(new CustomModel(). + addToPriority(If("!car_access", MULTIPLY, "0")). + addToSpeed(If("road_environment == FERRY", LIMIT, "ferry_speed")). + addToSpeed(Else(LIMIT, "car_average_speed")))). setMinNetworkSize(0). setGraphHopperLocation(dir). importOrLoad(); GHResponse response = gh.route(new GHRequest(51.2492152, 9.4317166, 52.133, 9.1) - .setProfile("profile") + .setProfile("car") .setPathDetails(Collections.singletonList(RoadClass.KEY))); assertFalse(response.hasErrors(), response.getErrors().toString()); List list = response.getBest().getPathDetails().get(RoadClass.KEY); @@ -915,7 +931,7 @@ protected File _getOSMFile() { assertEquals("motorway", list.get(0).getValue()); response = gh.route(new GHRequest(51.2492152, 9.4317166, 52.133, 9.1) - .setProfile("profile") + .setProfile("car") .setPathDetails(Arrays.asList(Toll.KEY, Country.KEY))); Throwable ex = response.getErrors().get(0); assertEquals("Cannot find the path details: [toll, country]", ex.getMessage()); @@ -923,13 +939,12 @@ protected File _getOSMFile() { @Test public void testCountries() throws IOException { - EncodingManager em = new EncodingManager.Builder().build(); - EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); + EnumEncodedValue roadAccessEnc = RoadAccess.create(); + EncodingManager em = new EncodingManager.Builder().add(roadAccessEnc).build(); OSMParsers osmParsers = new OSMParsers(); - osmParsers.addWayTagParser(new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR))); + osmParsers.addWayTagParser(OSMRoadAccessParser.forCar(roadAccessEnc)); BaseGraph graph = new BaseGraph.Builder(em).create(); OSMReader reader = new OSMReader(graph, osmParsers, new OSMReaderConfig()); - reader.setCountryRuleFactory(new CountryRuleFactory()); reader.setAreaIndex(createCountryIndex()); // there are two edges, both with highway=track, one in Berlin, one in Paris reader.setFile(new File(getClass().getResource("test-osm11.xml").getFile())); @@ -955,14 +970,13 @@ public void testCurvedWayAlongBorder() throws IOException { // see https://discuss.graphhopper.com/t/country-of-way-is-wrong-on-road-near-border-with-curvature/6908/2 EnumEncodedValue countryEnc = Country.create(); EncodingManager em = EncodingManager.start() - .add(VehicleEncodedValues.car(new PMap())) + .add(VehicleSpeed.create("car", 5, 5, false)).add(VehicleAccess.create("car")) .add(countryEnc) .build(); OSMParsers osmParsers = new OSMParsers() .addWayTagParser(new CountryParser(countryEnc)); BaseGraph graph = new BaseGraph.Builder(em).create(); OSMReader reader = new OSMReader(graph, osmParsers, new OSMReaderConfig()); - reader.setCountryRuleFactory(new CountryRuleFactory()); reader.setAreaIndex(createCountryIndex()); reader.setFile(new File(getClass().getResource("test-osm12.xml").getFile())); reader.readGraph(); @@ -978,29 +992,64 @@ public void testFixWayName() { assertEquals("B8, B12", OSMReader.fixWayName("B8; B12")); } + @Test + public void testCalc2DDistanceWithMixedElevation() { + // Simulate the scenario where tower nodes have elevation but pillar nodes return NaN + // (because pillar elevation is deferred to edge creation time). + // calc2DDistance should use 2D distance and not throw. + ReaderWay way = new ReaderWay(1); + way.getNodes().add(1, 2, 3); + + // node 1 = tower (has elevation), node 2 = pillar (NaN elevation), node 3 = tower (has elevation) + GHPoint3D tower1 = new GHPoint3D(49.0, 11.0, 400.0); + GHPoint3D pillar = new GHPoint3D(49.001, 11.001, Double.NaN); + GHPoint3D tower2 = new GHPoint3D(49.002, 11.002, 410.0); + + double distance = OSMReader.calc2DDistance(way, osmNodeId -> { + if (osmNodeId == 1) return tower1; + if (osmNodeId == 2) return pillar; + if (osmNodeId == 3) return tower2; + return null; + }); + + // Should be a valid 2D distance, not NaN and not throw + assertFalse(Double.isNaN(distance)); + assertTrue(distance > 0); + + // Verify it returns NaN when a node is missing + ReaderWay way2 = new ReaderWay(2); + way2.getNodes().add(1, 99); + double distMissing = OSMReader.calc2DDistance(way2, osmNodeId -> osmNodeId == 1 ? tower1 : null); + assertTrue(Double.isNaN(distMissing)); + } + private AreaIndex createCountryIndex() { return new AreaIndex<>(readCountries()); } class GraphHopperFacade extends GraphHopper { public GraphHopperFacade(String osmFile) { - this(osmFile, false, ""); + this(osmFile, ""); } - public GraphHopperFacade(String osmFile, boolean turnCosts, String prefLang) { + public GraphHopperFacade(String osmFile, String prefLang) { setStoreOnFlush(false); setOSMFile(osmFile); setGraphHopperLocation(dir); - if (turnCosts) setVehiclesString("roads|turn_costs=true|transportation_mode=HGV"); - setProfiles( - new Profile("foot").setVehicle("foot"), - new Profile("car").setVehicle("car").setTurnCosts(turnCosts), - new Profile("bike").setVehicle("bike").setTurnCosts(turnCosts), - new Profile("roads").setVehicle("roads").setTurnCosts(turnCosts) - ); + setEncodedValuesString("max_width,max_height,max_weight,road_environment," + + "foot_access, foot_priority, foot_average_speed, " + + "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed, ferry_speed"); + setProfiles(createProfiles()); getReaderConfig().setPreferredLanguage(prefLang); } + protected List createProfiles() { + return List.of(TestProfiles.accessSpeedAndPriority("foot"), + TestProfiles.accessAndSpeed("car").setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"))), + TestProfiles.accessSpeedAndPriority("bike").setTurnCostsConfig(new TurnCostsConfig(List.of("bicycle"))), + TestProfiles.constantSpeed("truck", 100).setTurnCostsConfig(new TurnCostsConfig(List.of("hgv", "motor_vehicle")))); + } + @Override protected void importOSM() { BaseGraph baseGraph = new BaseGraph.Builder(getEncodingManager()).set3D(hasElevation()).withTurnCosts(getEncodingManager().needsTurnCostsSupport()).build(); diff --git a/core/src/test/java/com/graphhopper/reader/osm/WayToEdgeConverterTest.java b/core/src/test/java/com/graphhopper/reader/osm/WayToEdgeConverterTest.java index d6a2cdc69eb..f6e23dd165d 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/WayToEdgeConverterTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/WayToEdgeConverterTest.java @@ -43,24 +43,147 @@ void convertForViaWays() throws OSMRestrictionException { } @Test - void convertForViaWays_throwsIfViaWayIsSplitIntoMultipleEdges() { + void convertForViaWays_multipleEdgesForViaWay() throws OSMRestrictionException { BaseGraph graph = new BaseGraph.Builder(1).create(); graph.edge(0, 1); graph.edge(1, 2); graph.edge(2, 3); graph.edge(3, 4); LongFunction> edgesByWay = way -> { - // way 0 and 2 simply correspond to edges 0 and 3, but way 1 is split into the two edges 1 and 2 - if (way == 1) return IntArrayList.from(1, 2).iterator(); - else return IntArrayList.from(Math.toIntExact(way)).iterator(); + if (way == 0) return IntArrayList.from(0).iterator(); + // way 1 is split into the two edges 1 and 2 + else if (way == 1) return IntArrayList.from(1, 2).iterator(); + else if (way == 2) return IntArrayList.from(3).iterator(); + else throw new IllegalArgumentException(); }; - OSMRestrictionException e = assertThrows(OSMRestrictionException.class, - () -> new WayToEdgeConverter(graph, edgesByWay).convertForViaWays(ways(0), ways(1), ways(2))); - assertTrue(e.getMessage().contains("has via member way that isn't split at adjacent ways"), e.getMessage()); + WayToEdgeConverter.EdgeResult edgeResult = new WayToEdgeConverter(graph, edgesByWay).convertForViaWays(ways(0), ways(1), ways(2)); + assertEquals(IntArrayList.from(1, 2), edgeResult.getViaEdges()); + assertEquals(IntArrayList.from(1, 2, 3), edgeResult.getNodes()); + } + + @Test + void convertForViaWays_multipleEdgesForViaWay_oppositeDirection() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(0, 1); + graph.edge(1, 2); + graph.edge(2, 3); + graph.edge(3, 4); + LongFunction> edgesByWay = way -> { + if (way == 0) return IntArrayList.from(0).iterator(); + // way 1 is split into the two edges 2, 1 (the wrong order) + // Accepting an arbitrary order is important, because OSM ways are generally split into multiple edges + // and a via-way might be pointing in the 'wrong' direction. + else if (way == 1) return IntArrayList.from(2, 1).iterator(); + else if (way == 2) return IntArrayList.from(3).iterator(); + else throw new IllegalArgumentException(); + }; + WayToEdgeConverter.EdgeResult edgeResult = new WayToEdgeConverter(graph, edgesByWay).convertForViaWays(ways(0), ways(1), ways(2)); + assertEquals(IntArrayList.from(1, 2), edgeResult.getViaEdges()); + assertEquals(IntArrayList.from(1, 2, 3), edgeResult.getNodes()); + } + + @Test + void convertForViaWays_reorderEdges() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(0, 1); + graph.edge(1, 2); + // the next two edges are given in the 'wrong' order + graph.edge(3, 4); + graph.edge(2, 3); + graph.edge(4, 5); + graph.edge(5, 6); + LongFunction> edgesByWay = way -> { + // way 1 is split into the four edges 1-4 + if (way == 1) return IntArrayList.from(1, 2, 3, 4).iterator(); + else if (way == 0) return IntArrayList.from(0).iterator(); + else if (way == 2) return IntArrayList.from(5).iterator(); + else throw new IllegalArgumentException(); + }; + WayToEdgeConverter.EdgeResult edgeResult = new WayToEdgeConverter(graph, edgesByWay).convertForViaWays(ways(0), ways(1), ways(2)); + assertEquals(IntArrayList.from(1, 3, 2, 4), edgeResult.getViaEdges()); + assertEquals(IntArrayList.from(1, 2, 3, 4, 5), edgeResult.getNodes()); + } + + @Test + void convertForViaWays_loop() { + BaseGraph graph = new BaseGraph.Builder(1).create(); + // 4 + // | + // 0-1-2 + // |/ + // 3 + graph.edge(0, 1); + graph.edge(1, 2); + graph.edge(2, 3); + graph.edge(3, 1); + graph.edge(1, 4); + LongFunction> edgesByWay = way -> IntArrayList.from(Math.toIntExact(way)).iterator(); + OSMRestrictionException e = assertThrows(OSMRestrictionException.class, () -> + new WayToEdgeConverter(graph, edgesByWay).convertForViaWays(ways(0), ways(1, 2, 3), ways(4))); + // So far we allow the via ways/edges to be in an arbitrary order, but do not allow multiple solutions. + assertTrue(e.getMessage().contains("has member ways that do not form a unique path"), e.getMessage()); + } + + @Test + void convertForViaNode_multipleFrom() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(1, 0); + graph.edge(2, 0); + graph.edge(3, 0); + graph.edge(0, 4); + WayToEdgeConverter.NodeResult nodeResult = new WayToEdgeConverter(graph, way -> IntArrayList.from(Math.toIntExact(way)).iterator()) + .convertForViaNode(ways(0, 1, 2), 0, ways(3)); + assertEquals(IntArrayList.from(0, 1, 2), nodeResult.getFromEdges()); + assertEquals(IntArrayList.from(3), nodeResult.getToEdges()); + } + + @Test + void convertForViaNode_multipleTo() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(1, 0); + graph.edge(2, 0); + graph.edge(3, 0); + graph.edge(0, 4); + WayToEdgeConverter.NodeResult nodeResult = new WayToEdgeConverter(graph, way -> IntArrayList.from(Math.toIntExact(way)).iterator()) + .convertForViaNode(ways(3), 0, ways(0, 1, 2)); + assertEquals(IntArrayList.from(3), nodeResult.getFromEdges()); + assertEquals(IntArrayList.from(0, 1, 2), nodeResult.getToEdges()); + } + + @Test + void convertForViaWay_multipleFrom() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(1, 0); + graph.edge(2, 0); + graph.edge(3, 0); + graph.edge(0, 4); + graph.edge(4, 5); + WayToEdgeConverter.EdgeResult edgeResult = new WayToEdgeConverter(graph, way -> IntArrayList.from(Math.toIntExact(way)).iterator()) + .convertForViaWays(ways(0, 1, 2), ways(3), ways(4)); + assertEquals(IntArrayList.from(0, 1, 2), edgeResult.getFromEdges()); + assertEquals(IntArrayList.from(3), edgeResult.getViaEdges()); + assertEquals(IntArrayList.from(4), edgeResult.getToEdges()); + assertEquals(IntArrayList.from(0, 4), edgeResult.getNodes()); + } + + @Test + void convertForViaWay_multipleTo() throws OSMRestrictionException { + BaseGraph graph = new BaseGraph.Builder(1).create(); + graph.edge(1, 0); + graph.edge(2, 0); + graph.edge(3, 0); + graph.edge(0, 4); + graph.edge(4, 5); + WayToEdgeConverter.EdgeResult edgeResult = new WayToEdgeConverter(graph, way -> IntArrayList.from(Math.toIntExact(way)).iterator()) + .convertForViaWays(ways(4), ways(3), ways(0, 1, 2)); + assertEquals(IntArrayList.from(0, 1, 2), edgeResult.getToEdges()); + assertEquals(IntArrayList.from(3), edgeResult.getViaEdges()); + assertEquals(IntArrayList.from(4), edgeResult.getFromEdges()); + assertEquals(IntArrayList.from(4, 0), edgeResult.getNodes()); } private LongArrayList ways(long... ways) { return LongArrayList.from(ways); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/reader/osm/conditional/CalendarBasedTest.java b/core/src/test/java/com/graphhopper/reader/osm/conditional/CalendarBasedTest.java deleted file mode 100644 index b6da0f73ee0..00000000000 --- a/core/src/test/java/com/graphhopper/reader/osm/conditional/CalendarBasedTest.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.conditional; - -import java.util.Calendar; - -/** - * Base Test for calendar based tasks. - *

    - * - * @author Robin Boldt - */ -public abstract class CalendarBasedTest { - protected Calendar getCalendar(int year, int month, int day) { - Calendar calendar = DateRangeParser.createCalendar(); - calendar.set(Calendar.YEAR, year); - calendar.set(Calendar.MONTH, month); - calendar.set(Calendar.DAY_OF_MONTH, day); - return calendar; - } -} diff --git a/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspectorTest.java b/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspectorTest.java deleted file mode 100644 index 5efca154e75..00000000000 --- a/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalOSMTagInspectorTest.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.conditional; - -import com.graphhopper.reader.ReaderWay; -import org.junit.jupiter.api.Test; - -import java.util.*; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Robin Boldt - */ -public class ConditionalOSMTagInspectorTest extends CalendarBasedTest { - private static Set getSampleRestrictedValues() { - Set restrictedValues = new HashSet<>(); - restrictedValues.add("private"); - restrictedValues.add("agricultural"); - restrictedValues.add("forestry"); - restrictedValues.add("no"); - restrictedValues.add("restricted"); - restrictedValues.add("delivery"); - restrictedValues.add("military"); - restrictedValues.add("emergency"); - return restrictedValues; - } - - private static Set getSamplePermissiveValues() { - Set permissiveValues = new HashSet<>(); - permissiveValues.add("yes"); - permissiveValues.add("permissive"); - return permissiveValues; - } - - private static List getSampleConditionalTags() { - List conditionalTags = new ArrayList<>(); - conditionalTags.add("vehicle"); - conditionalTags.add("access"); - return conditionalTags; - } - - @Test - public void testConditionalAccept() { - Calendar cal = getCalendar(2014, Calendar.MARCH, 10); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "no @ (Aug 10-Aug 14)"); - assertFalse(acceptor.isPermittedWayConditionallyRestricted(way)); - } - - @Test - public void testConditionalAcceptNextYear() { - Calendar cal = getCalendar(2014, Calendar.MARCH, 10); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "no @ (2013 Mar 1-2013 Mar 31)"); - assertFalse(acceptor.isPermittedWayConditionallyRestricted(way)); - } - - @Test - public void testConditionalReject() { - Calendar cal = getCalendar(2014, Calendar.MARCH, 10); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "no @ (Mar 10-Aug 14)"); - assertTrue(acceptor.isPermittedWayConditionallyRestricted(way)); - } - - @Test - public void testConditionalAllowance() { - Calendar cal = getCalendar(2014, Calendar.MARCH, 10); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "yes @ (Mar 10-Aug 14)"); - assertTrue(acceptor.isRestrictedWayConditionallyPermitted(way)); - } - - @Test - public void testConditionalAllowanceReject() { - Calendar cal = getCalendar(2014, Calendar.MARCH, 10); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "no @ (Mar 10-Aug 14)"); - assertTrue(acceptor.isPermittedWayConditionallyRestricted(way)); - } - - @Test - public void testConditionalSingleDay() { - Calendar cal = getCalendar(2015, Calendar.DECEMBER, 27); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "no @ (Su)"); - assertTrue(acceptor.isPermittedWayConditionallyRestricted(way)); - } - - @Test - public void testConditionalAllowanceSingleDay() { - Calendar cal = getCalendar(2015, Calendar.DECEMBER, 27); - ConditionalTagInspector acceptor = new ConditionalOSMTagInspector(cal, getSampleConditionalTags(), getSampleRestrictedValues(), getSamplePermissiveValues()); - ReaderWay way = new ReaderWay(1); - way.setTag("vehicle:conditional", "yes @ (Su)"); - assertTrue(acceptor.isRestrictedWayConditionallyPermitted(way)); - } - -} diff --git a/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalParserTest.java b/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalParserTest.java deleted file mode 100644 index 4052554bf76..00000000000 --- a/core/src/test/java/com/graphhopper/reader/osm/conditional/ConditionalParserTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.reader.osm.conditional; - -import org.junit.jupiter.api.Test; - -import java.text.ParseException; -import java.util.Calendar; -import java.util.HashSet; -import java.util.Set; - -import static org.junit.jupiter.api.Assertions.*; - -/** - * @author Robin Boldt - */ -public class ConditionalParserTest extends CalendarBasedTest { - - private final HashSet restrictedValues = new HashSet<>(); - - public ConditionalParserTest() { - restrictedValues.add("private"); - restrictedValues.add("agricultural"); - restrictedValues.add("forestry"); - restrictedValues.add("no"); - restrictedValues.add("restricted"); - restrictedValues.add("delivery"); - restrictedValues.add("military"); - restrictedValues.add("emergency"); - } - - ConditionalParser createParser(Calendar date) { - return new ConditionalParser(restrictedValues).addConditionalValueParser(new DateRangeParser(date)); - } - - @Test - public void testParseConditional() throws ParseException { - String str = "no @ (2015 Sep 1-2015 Sep 30)"; - assertFalse(createParser(getCalendar(2015, Calendar.AUGUST, 31)).checkCondition(str)); - assertTrue(createParser(getCalendar(2015, Calendar.SEPTEMBER, 30)).checkCondition(str)); - } - - @Test - public void testParseAllowingCondition() throws ParseException { - assertFalse(createParser(getCalendar(2015, Calendar.JANUARY, 12)). - checkCondition("yes @ (2015 Sep 1-2015 Sep 30)")); - } - - @Test - public void testParsingOfLeading0() throws ParseException { - assertTrue(createParser(getCalendar(2015, Calendar.DECEMBER, 2)). - checkCondition("no @ (01.11. - 31.03.)")); - - assertTrue(createParser(getCalendar(2015, Calendar.DECEMBER, 2)). - checkCondition("no @ (01.11 - 31.03)")); - } - - @Test - public void testGetRange() throws Exception { - assertTrue(ConditionalParser.createNumberParser("weight", 11).checkCondition("weight > 10").isCheckPassed()); - assertFalse(ConditionalParser.createNumberParser("weight", 10).checkCondition("weight > 10").isCheckPassed()); - assertFalse(ConditionalParser.createNumberParser("weight", 9).checkCondition("weight > 10").isCheckPassed()); - assertFalse(ConditionalParser.createNumberParser("xy", 9).checkCondition("weight > 10").isValid()); - - Set set = new HashSet<>(); - set.add("no"); - ConditionalParser instance = new ConditionalParser(set). - setConditionalValueParser(ConditionalParser.createNumberParser("weight", 11)); - assertTrue(instance.checkCondition("no @weight>10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 10)); - assertFalse(instance.checkCondition("no @weight>10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 9)); - assertFalse(instance.checkCondition("no @weight>10")); - - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 11)); - assertFalse(instance.checkCondition("no @ weight < 10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 10)); - assertFalse(instance.checkCondition("no @ weight < 10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 9)); - assertTrue(instance.checkCondition("no @ weight < 10")); - - // equals is ignored for now (not that bad for weight) - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 11)); - assertFalse(instance.checkCondition("no @ weight <= 10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 10)); - assertFalse(instance.checkCondition("no @ weight <= 10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 9)); - assertTrue(instance.checkCondition("no @ weight <= 10")); - - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 11)); - assertFalse(instance.checkCondition("no @ weight<=10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 10)); - assertFalse(instance.checkCondition("no @ weight<=10")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("weight", 9)); - assertTrue(instance.checkCondition("no @ weight<=10")); - - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 1)); - assertFalse(instance.checkCondition("no @ height > 2")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 2)); - assertFalse(instance.checkCondition("no @ height > 2")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 3)); - assertTrue(instance.checkCondition("no @ height > 2")); - - // unit is allowed according to wiki - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 1)); - assertFalse(instance.checkCondition("no @ height > 2t")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 2)); - assertFalse(instance.checkCondition("no @ height > 2t")); - instance.setConditionalValueParser(ConditionalParser.createNumberParser("height", 3)); - assertTrue(instance.checkCondition("no @ height > 2t")); - } - - @Test - public void parseNumber() { - // TODO currently no unit conversation is done which can be required if a different one is passed in checkCondition - assertEquals(3, ConditionalParser.parseNumber("3t"), .1); - assertEquals(3.1, ConditionalParser.parseNumber("3.1 t"), .1); - assertEquals(3, ConditionalParser.parseNumber("3 meters"), .1); - } -} diff --git a/core/src/test/java/com/graphhopper/reader/osm/conditional/DateRangeParserTest.java b/core/src/test/java/com/graphhopper/reader/osm/conditional/DateRangeParserTest.java index 19226795cb6..ffb3a70afe8 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/conditional/DateRangeParserTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/conditional/DateRangeParserTest.java @@ -27,7 +27,7 @@ /** * @author Robin Boldt */ -public class DateRangeParserTest extends CalendarBasedTest { +public class DateRangeParserTest { final DateRangeParser dateRangeParser = new DateRangeParser(); @Test @@ -218,4 +218,11 @@ private void assertSameDate(int year, int month, int day, String dateString) thr assertEquals(expected.get(Calendar.DAY_OF_MONTH), actual.get(Calendar.DAY_OF_MONTH)); } + protected Calendar getCalendar(int year, int month, int day) { + Calendar calendar = DateRangeParser.createCalendar(); + calendar.set(Calendar.YEAR, year); + calendar.set(Calendar.MONTH, month); + calendar.set(Calendar.DAY_OF_MONTH, day); + return calendar; + } } diff --git a/core/src/test/java/com/graphhopper/routing/AStarBidirectionTest.java b/core/src/test/java/com/graphhopper/routing/AStarBidirectionTest.java index e4815989a8e..b091c3e6f55 100644 --- a/core/src/test/java/com/graphhopper/routing/AStarBidirectionTest.java +++ b/core/src/test/java/com/graphhopper/routing/AStarBidirectionTest.java @@ -19,17 +19,14 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntArrayList; -import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.WeightApproximator; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.GHUtility; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -45,31 +42,30 @@ void infeasibleApproximator_noException() { // This means the resulting path contains the invalid search tree branch 2(old)-3-4 and is not the shortest path, // because the SPTEntry for node 3 still points to the outdated/deleted entry for node 2. // We do not expect an exception, though, because for an infeasible approximator we cannot expect optimal paths. - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); - DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 2, 1, true); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); // 0-1----2-3-4----5-6-7-8-9 // \ / // 10 - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(100)); + graph.edge(0, 1).set(speedEnc, 1, 0).setDistance(100); // the distance 1-2 is longer than 1-10-2 // we deliberately use 2-1 as storage direction, even though the edge points from 1 to 2, because this way // we can reproduce the 'Calculating time should not require to read speed from edge in wrong direction' error // from #2600 - graph.edge(2, 1).setDistance(300).set(accessEnc, false, true).set(speedEnc, 60); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(2, 3).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(3, 4).setDistance(100)); + graph.edge(2, 1).setDistance(300).set(speedEnc, 0, 1); + graph.edge(2, 3).set(speedEnc, 1, 0).setDistance(100); + graph.edge(3, 4).set(speedEnc, 1, 0).setDistance(100); // distance 4-5 is very long - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(4, 5).setDistance(10_000)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(5, 6).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(6, 7).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(7, 8).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(8, 9).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(1, 10).setDistance(100)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(10, 2).setDistance(100)); + graph.edge(4, 5).set(speedEnc, 1, 0).setDistance(10_000); + graph.edge(5, 6).set(speedEnc, 1, 0).setDistance(100); + graph.edge(6, 7).set(speedEnc, 1, 0).setDistance(100); + graph.edge(7, 8).set(speedEnc, 1, 0).setDistance(100); + graph.edge(8, 9).set(speedEnc, 1, 0).setDistance(100); + graph.edge(1, 10).set(speedEnc, 1, 0).setDistance(100); + graph.edge(10, 2).set(speedEnc, 1, 0).setDistance(100); - Weighting weighting = new ShortestWeighting(accessEnc, speedEnc); + Weighting weighting = new SpeedWeighting(speedEnc); AStarBidirection algo = new AStarBidirection(graph, weighting, TraversalMode.NODE_BASED); algo.setApproximation(new InfeasibleApproximator()); Path path = algo.calcPath(0, 9); @@ -97,7 +93,7 @@ public double approximate(int currentNode) { // long edge 4-5, but it makes the approximator infeasible, because // d(10, 2) + h(2) = 100 + 0 = 100 and h(10) = 1000, so it does not hold that d(10, 2) + h(2) >= h(10) if (currentNode == 10) - return 1000; + return 10 * 1000; else return 0; } diff --git a/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java b/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java index 8b17101880c..212d9f4f5fb 100644 --- a/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java +++ b/core/src/test/java/com/graphhopper/routing/AlternativeRouteCHTest.java @@ -19,17 +19,14 @@ import com.graphhopper.routing.ch.NodeOrderingProvider; import com.graphhopper.routing.ch.PrepareContractionHierarchies; -import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.weighting.custom.CustomModelParser; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.CHConfig; import com.graphhopper.storage.RoutingCHGraph; import com.graphhopper.storage.RoutingCHGraphImpl; -import com.graphhopper.util.GHUtility; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; @@ -38,9 +35,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; public class AlternativeRouteCHTest { - private final BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); private final DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - private final EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + private final EncodingManager em = EncodingManager.start().add(speedEnc).build(); public BaseGraph createTestGraph(EncodingManager tmpEM) { final BaseGraph graph = new BaseGraph.Builder(tmpEM).create(); @@ -59,21 +55,20 @@ public BaseGraph createTestGraph(EncodingManager tmpEM) { // has to be locally-shortest to be considered. // So we get all three alternatives. - GHUtility.setSpeed(60, 60, accessEnc, speedEnc, - graph.edge(5, 6).setDistance(10000), - graph.edge(6, 3).setDistance(10000), - graph.edge(3, 4).setDistance(10000), - graph.edge(4, 10).setDistance(10000), - graph.edge(6, 7).setDistance(10000), - graph.edge(7, 8).setDistance(10000), - graph.edge(8, 4).setDistance(10000), - graph.edge(5, 1).setDistance(10000), - graph.edge(1, 9).setDistance(10000), - graph.edge(9, 2).setDistance(10000), - graph.edge(2, 3).setDistance(10000), - graph.edge(4, 11).setDistance(9000), - graph.edge(11, 12).setDistance(9000), - graph.edge(12, 10).setDistance(10000)); + graph.edge(5, 6).setDistance(10000).set(speedEnc, 60); + graph.edge(6, 3).setDistance(10000).set(speedEnc, 60); + graph.edge(3, 4).setDistance(10000).set(speedEnc, 60); + graph.edge(4, 10).setDistance(10000).set(speedEnc, 60); + graph.edge(6, 7).setDistance(10000).set(speedEnc, 60); + graph.edge(7, 8).setDistance(10000).set(speedEnc, 60); + graph.edge(8, 4).setDistance(10000).set(speedEnc, 60); + graph.edge(5, 1).setDistance(10000).set(speedEnc, 60); + graph.edge(1, 9).setDistance(10000).set(speedEnc, 60); + graph.edge(9, 2).setDistance(10000).set(speedEnc, 60); + graph.edge(2, 3).setDistance(10000).set(speedEnc, 60); + graph.edge(4, 11).setDistance(9000).set(speedEnc, 60); + graph.edge(11, 12).setDistance(9000).set(speedEnc, 60); + graph.edge(12, 10).setDistance(10000).set(speedEnc, 60); graph.freeze(); return graph; @@ -84,7 +79,7 @@ private RoutingCHGraph prepareCH(BaseGraph graph) { // meet on all four possible paths from 5 to 10 // 5 ---> 11 will be reachable via shortcuts, as 11 is on shortest path 5 --> 12 final int[] nodeOrdering = new int[]{0, 10, 12, 4, 3, 2, 5, 1, 6, 7, 8, 9, 11}; - CHConfig chConfig = CHConfig.nodeBased("p", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); + CHConfig chConfig = CHConfig.nodeBased("p", new SpeedWeighting(speedEnc)); PrepareContractionHierarchies contractionHierarchies = PrepareContractionHierarchies.fromGraph(graph, chConfig); contractionHierarchies.useFixedNodeOrdering(NodeOrderingProvider.fromArray(nodeOrdering)); PrepareContractionHierarchies.Result res = contractionHierarchies.doWork(); diff --git a/core/src/test/java/com/graphhopper/routing/AlternativeRouteEdgeCHTest.java b/core/src/test/java/com/graphhopper/routing/AlternativeRouteEdgeCHTest.java index 9e7b563acd9..782e4d30162 100644 --- a/core/src/test/java/com/graphhopper/routing/AlternativeRouteEdgeCHTest.java +++ b/core/src/test/java/com/graphhopper/routing/AlternativeRouteEdgeCHTest.java @@ -18,6 +18,7 @@ package com.graphhopper.routing; import com.carrotsearch.hppc.IntArrayList; +import com.graphhopper.routing.ch.NodeOrderingProvider; import com.graphhopper.routing.ch.PrepareContractionHierarchies; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; @@ -25,16 +26,13 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.storage.*; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.GHUtility; -import com.graphhopper.util.PMap; +import com.graphhopper.util.*; import org.junit.jupiter.api.Test; import java.util.List; import static com.graphhopper.util.EdgeIterator.ANY_EDGE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; public class AlternativeRouteEdgeCHTest { private final DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); @@ -147,4 +145,83 @@ public void testCalcOtherAlternatives() { // The shortest path works (no restrictions on the way back } + @Test + void turnRestrictionAtConnectingNode() { + final BaseGraph graph = new BaseGraph.Builder(em).withTurnCosts(true).create(); + TurnCostStorage tcs = graph.getTurnCostStorage(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(3, 45.0, 10.0); + na.setNode(2, 45.0, 10.1); + na.setNode(1, 44.9, 10.1); + // 3-2 + // \| + // 1 + graph.edge(2, 3).setDistance(500).set(speedEnc, 60); // edgeId=0 + graph.edge(2, 1).setDistance(1000).set(speedEnc, 60); // edgeId=1 + graph.edge(3, 1).setDistance(500).set(speedEnc, 60); // edgeId=2 + // turn restriction at node 3, which must be respected by our glued-together s->u->v->t path + tcs.set(turnCostEnc, 0, 3, 2, Double.POSITIVE_INFINITY); + graph.freeze(); + CHConfig chConfig = CHConfig.edgeBased("profile", new SpeedWeighting(speedEnc, turnCostEnc, graph.getTurnCostStorage(), Double.POSITIVE_INFINITY)); + PrepareContractionHierarchies contractionHierarchies = PrepareContractionHierarchies.fromGraph(graph, chConfig); + contractionHierarchies.useFixedNodeOrdering(NodeOrderingProvider.fromArray(3, 0, 2, 1)); + PrepareContractionHierarchies.Result res = contractionHierarchies.doWork(); + RoutingCHGraph routingCHGraph = RoutingCHGraphImpl.fromGraph(graph, res.getCHStorage(), res.getCHConfig()); + final int s = 2; + final int t = 1; + DijkstraBidirectionEdgeCHNoSOD dijkstra = new DijkstraBidirectionEdgeCHNoSOD(routingCHGraph); + Path singlePath = dijkstra.calcPath(s, t); + PMap hints = new PMap(); + AlternativeRouteEdgeCH altDijkstra = new AlternativeRouteEdgeCH(routingCHGraph, hints); + List pathInfos = altDijkstra.calcAlternatives(s, t); + AlternativeRouteEdgeCH.AlternativeInfo best = pathInfos.get(0); + assertEquals(singlePath.getWeight(), best.path.getWeight()); + assertEquals(singlePath.calcNodes(), best.path.calcNodes()); + for (int j = 1; j < pathInfos.size(); j++) { + assertTrue(pathInfos.get(j).path.getWeight() >= best.path.getWeight(), "alternatives must not have lower weight than best path"); + assertEquals(IntArrayList.from(s, t), pathInfos.get(j).path.calcNodes(), "alternatives must start/end at start/end node"); + } + } + + @Test + void distanceTimeAndWeight() { + final BaseGraph graph = new BaseGraph.Builder(em).withTurnCosts(true).create(); + // 0-1-2-3---| + // \ | + // 5-6-7-8 + graph.edge(0, 1).setDistance(500).set(speedEnc, 60); + graph.edge(1, 2).setDistance(500).set(speedEnc, 60); + graph.edge(2, 3).setDistance(500).set(speedEnc, 60); + graph.edge(2, 5).setDistance(950).set(speedEnc, 60); + graph.edge(3, 7).setDistance(1500).set(speedEnc, 60); + graph.edge(5, 6).setDistance(500).set(speedEnc, 60); + graph.edge(6, 7).setDistance(500).set(speedEnc, 60); + graph.edge(7, 8).setDistance(500).set(speedEnc, 60); + graph.freeze(); + CHConfig chConfig = CHConfig.edgeBased("profile", new SpeedWeighting(speedEnc, turnCostEnc, graph.getTurnCostStorage(), Double.POSITIVE_INFINITY)); + PrepareContractionHierarchies contractionHierarchies = PrepareContractionHierarchies.fromGraph(graph, chConfig); + contractionHierarchies.useFixedNodeOrdering(NodeOrderingProvider.fromArray(7, 6, 0, 2, 4, 5, 1, 3, 8)); + PrepareContractionHierarchies.Result res = contractionHierarchies.doWork(); + RoutingCHGraph routingCHGraph = RoutingCHGraphImpl.fromGraph(graph, res.getCHStorage(), res.getCHConfig()); + final int s = 0; + final int t = 8; + PMap hints = new PMap(); + AlternativeRouteEdgeCH altDijkstra = new AlternativeRouteEdgeCH(routingCHGraph, hints); + List pathInfos = altDijkstra.calcAlternatives(s, t); + AlternativeRouteEdgeCH.AlternativeInfo best = pathInfos.get(0); + assertEquals(3450, best.path.getDistance()); + assertEquals(573, best.path.getWeight()); + assertEquals(57498, best.path.getTime(), 10); + assertEquals(IntArrayList.from(0, 1, 2, 5, 6, 7, 8), best.path.calcNodes()); + assertTrue(pathInfos.size() > 1, "the graph, contraction order and alternative route algorithm must be such that " + + "there is at least one alternative path, otherwise this test makes no sense"); + for (int j = 1; j < pathInfos.size(); j++) { + Path alternative = pathInfos.get(j).path; + assertEquals(3500, alternative.getDistance()); + assertEquals(582, alternative.getWeight()); + assertEquals(58333, alternative.getTime(), 1); + assertEquals(IntArrayList.from(0, 1, 2, 3, 7, 8), alternative.calcNodes()); + } + } + } diff --git a/core/src/test/java/com/graphhopper/routing/AlternativeRouteTest.java b/core/src/test/java/com/graphhopper/routing/AlternativeRouteTest.java index ed4196b7840..e1580ba30f0 100644 --- a/core/src/test/java/com/graphhopper/routing/AlternativeRouteTest.java +++ b/core/src/test/java/com/graphhopper/routing/AlternativeRouteTest.java @@ -77,7 +77,7 @@ public Stream provideArguments(ExtensionContext context) { } } - public static void initTestGraph(Graph graph, DecimalEncodedValue speedEnc) { + private static void initTestGraph(Graph graph, DecimalEncodedValue speedEnc) { /* 9 _/\ 1 2-3-4-10 @@ -85,17 +85,17 @@ public static void initTestGraph(Graph graph, DecimalEncodedValue speedEnc) { 5--6-7---8 */ - graph.edge(1, 9).setDistance(1).set(speedEnc, 60, 60); - graph.edge(9, 2).setDistance(1).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - graph.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 10).setDistance(1).set(speedEnc, 60, 60); - graph.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - graph.edge(6, 7).setDistance(1).set(speedEnc, 60, 60); - graph.edge(7, 8).setDistance(1).set(speedEnc, 60, 60); - graph.edge(1, 5).setDistance(2).set(speedEnc, 60, 60); - graph.edge(6, 3).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 8).setDistance(1).set(speedEnc, 60, 60); + graph.edge(1, 9).setDistance(0).set(speedEnc, 60, 60); + graph.edge(9, 2).setDistance(0).set(speedEnc, 60, 60); + graph.edge(2, 3).setDistance(0).set(speedEnc, 60, 60); + graph.edge(3, 4).setDistance(0).set(speedEnc, 60, 60); + graph.edge(4, 10).setDistance(0).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(0).set(speedEnc, 60, 60); + graph.edge(6, 7).setDistance(0).set(speedEnc, 60, 60); + graph.edge(7, 8).setDistance(0).set(speedEnc, 60, 60); + graph.edge(1, 5).setDistance(0).set(speedEnc, 60, 60); + graph.edge(6, 3).setDistance(0).set(speedEnc, 60, 60); + graph.edge(4, 8).setDistance(0).set(speedEnc, 60, 60); updateDistancesFor(graph, 5, 0.00, 0.05); updateDistancesFor(graph, 6, 0.00, 0.10); @@ -103,7 +103,7 @@ public static void initTestGraph(Graph graph, DecimalEncodedValue speedEnc) { updateDistancesFor(graph, 8, 0.00, 0.25); updateDistancesFor(graph, 1, 0.05, 0.00); - updateDistancesFor(graph, 9, 0.10, 0.05); + updateDistancesFor(graph, 9, 0.07, 0.05); updateDistancesFor(graph, 2, 0.05, 0.10); updateDistancesFor(graph, 3, 0.05, 0.15); updateDistancesFor(graph, 4, 0.05, 0.25); @@ -138,7 +138,7 @@ public void testCalcAlternatives(Fixture f) { // so which alternative is better? longer plateau.weight with bigger path.weight or smaller path.weight with smaller plateau.weight // assertEquals(IntArrayList.from(5, 1, 9, 2, 3, 4), secondAlt.calcNodes()); assertEquals(IntArrayList.from(5, 6, 7, 8, 4), secondAlt.calcNodes()); - assertEquals(463.3, secondAlt.getWeight(), .1); + assertEquals(4634, secondAlt.getWeight()); } @ParameterizedTest @@ -159,7 +159,9 @@ public void testCalcAlternatives2(Fixture f) { assertEquals(IntArrayList.from(5, 6, 3, 4), pathInfos.get(0).getPath().calcNodes()); assertEquals(IntArrayList.from(5, 6, 7, 8, 4), pathInfos.get(1).getPath().calcNodes()); assertEquals(IntArrayList.from(5, 1, 9, 2, 3, 4), pathInfos.get(2).getPath().calcNodes()); - assertEquals(671.1, pathInfos.get(2).getPath().getWeight(), .1); + assertEquals(4090, pathInfos.get(0).getPath().getWeight()); + assertEquals(4634, pathInfos.get(1).getPath().getWeight()); + assertEquals(6086, pathInfos.get(2).getPath().getWeight()); } private void checkAlternatives(List alternativeInfos) { diff --git a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java index dbfb6b63310..ba019eabbe6 100644 --- a/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java +++ b/core/src/test/java/com/graphhopper/routing/CHQueryWithTurnCostsTest.java @@ -85,7 +85,8 @@ private void freeze() { private void addShortcut(int from, int to, int firstOrigEdgeKey, int lastOrigEdgeKey, int skipped1, int skipped2, double weight, boolean reverse) { int flags = reverse ? PrepareEncoder.getScBwdDir() : PrepareEncoder.getScFwdDir(); - chBuilder.addShortcutEdgeBased(from, to, flags, weight, skipped1, skipped2, firstOrigEdgeKey, lastOrigEdgeKey); + // todo: move x10 out of here + chBuilder.addShortcutEdgeBased(from, to, flags, 10 * weight, skipped1, skipped2, firstOrigEdgeKey, lastOrigEdgeKey); } private void setIdentityLevels() { @@ -117,7 +118,8 @@ private void testPathCalculation(int from, int to, int expectedWeight, IntArrayL } private void testPathCalculation(int from, int to, int expectedEdgeWeight, IntArrayList expectedNodes, int expectedTurnCost) { - int expectedWeight = expectedEdgeWeight + expectedTurnCost; + // todo: move x10 out of here + int expectedWeight = expectedEdgeWeight * 10 + expectedTurnCost * 10; int expectedDistance = expectedEdgeWeight * 10; int expectedTime = (expectedEdgeWeight + expectedTurnCost) * 1000; AbstractBidirectionEdgeCHNoSOD algo = createAlgo(); @@ -197,7 +199,7 @@ public void testFindPathWithTurnCosts_bidirected_no_shortcuts(Fixture f) { // also check if distance and times (including turn costs) are calculated correctly Path path = f.createAlgo().calcPath(0, 1); - assertEquals(40, path.getWeight(), 1.e-3, "wrong weight"); + assertEquals(400, path.getWeight(), 1.e-3, "wrong weight"); assertEquals(260, path.getDistance(), 1.e-3, "wrong distance"); double weightPerMeter = 0.1; assertEquals((260 * weightPerMeter + 14) * 1000, path.getTime(), 1.e-3, "wrong time"); diff --git a/core/src/test/java/com/graphhopper/routing/CustomizableConditionalRestrictionsTest.java b/core/src/test/java/com/graphhopper/routing/CustomizableConditionalRestrictionsTest.java new file mode 100644 index 00000000000..999b4a5515c --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/CustomizableConditionalRestrictionsTest.java @@ -0,0 +1,69 @@ +package com.graphhopper.routing; + +import com.graphhopper.GHRequest; +import com.graphhopper.GHResponse; +import com.graphhopper.GraphHopper; +import com.graphhopper.GraphHopperConfig; +import com.graphhopper.json.Statement; +import com.graphhopper.routing.ev.FootTemporalAccess; +import com.graphhopper.util.CustomModel; +import com.graphhopper.util.Helper; +import com.graphhopper.util.details.PathDetail; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +import static com.graphhopper.json.Statement.If; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; + +public class CustomizableConditionalRestrictionsTest { + + private static final String GH_LOCATION = "target/routing-conditional-access-gh"; + + @BeforeEach + @AfterEach + public void setup() { + Helper.removeDir(new File(GH_LOCATION)); + } + + @Test + public void testConditionalAccess() { + GraphHopper hopper = new GraphHopper(). + setStoreOnFlush(false). + setEncodedValuesString(FootTemporalAccess.KEY); + + hopper.init(new GraphHopperConfig(). + setProfiles(List.of(TestProfiles.accessAndSpeed("foot", "foot"))). + putObject("graph.location", GH_LOCATION). + putObject("graph.encoded_values", "foot_temporal_access, foot_access, foot_average_speed"). + putObject("datareader.file", "../core/files/conditional-restrictions.osm.xml"). + putObject("prepare.min_network_size", "0"). + putObject("import.osm.ignored_highways", ""). + putObject("datareader.date_range_parser_day", "2023-08-01")); + hopper.importOrLoad(); + + String PD_KEY = "access_conditional"; + GHResponse rsp = hopper.route(new GHRequest(50.909136, 14.213924, 50.90918, 14.213549). + setProfile("foot"). + setPathDetails(Arrays.asList(PD_KEY))); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + List details = rsp.getBest().getPathDetails().get(PD_KEY); + assertEquals("no@(Jan15-Aug15)", details.get(0).getValue()); + assertEquals(2, details.size()); + assertEquals(32, rsp.getBest().getDistance(), 1); + + rsp = hopper.route(new GHRequest(50.909136, 14.213924, 50.90918, 14.213549). + setProfile("foot"). + setCustomModel(new CustomModel().addToPriority(If("foot_temporal_access == NO", Statement.Op.MULTIPLY, "0"))). + setPathDetails(Arrays.asList(PD_KEY))); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + assertEquals(16, rsp.getBest().getDistance(), 1); + details = rsp.getBest().getPathDetails().get(PD_KEY); + assertEquals(1, details.size()); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java b/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java index 966f6f35658..2f9bcce5fc6 100644 --- a/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java +++ b/core/src/test/java/com/graphhopper/routing/DijkstraBidirectionCHTest.java @@ -118,18 +118,18 @@ public void testBaseGraphMultipleVehicles() { @Test public void testStallingNodesReducesNumberOfVisitedNodes() { BaseGraph graph = createGHStorage(); - graph.edge(8, 9).setDistance(100).set(carSpeedEnc, 60, 0); - graph.edge(8, 3).setDistance(2).set(carSpeedEnc, 60, 0); - graph.edge(8, 5).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(8, 6).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(8, 7).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(1, 2).setDistance(2).set(carSpeedEnc, 60, 0); - graph.edge(1, 8).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(2, 3).setDistance(3).set(carSpeedEnc, 60, 0); + graph.edge(8, 9).setDistance(10000).set(carSpeedEnc, 60, 0); + graph.edge(8, 3).setDistance(200).set(carSpeedEnc, 60, 0); + graph.edge(8, 5).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(8, 6).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(8, 7).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(1, 2).setDistance(200).set(carSpeedEnc, 60, 0); + graph.edge(1, 8).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(2, 3).setDistance(300).set(carSpeedEnc, 60, 0); for (int i = 3; i < 7; ++i) - graph.edge(i, i + 1).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(9, 0).setDistance(1).set(carSpeedEnc, 60, 0); - graph.edge(3, 9).setDistance(200).set(carSpeedEnc, 60, 0); + graph.edge(i, i + 1).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(9, 0).setDistance(100).set(carSpeedEnc, 60, 0); + graph.edge(3, 9).setDistance(20000).set(carSpeedEnc, 60, 0); graph.freeze(); Weighting weighting = new SpeedWeighting(carSpeedEnc); @@ -145,14 +145,14 @@ public void testStallingNodesReducesNumberOfVisitedNodes() { // node 3 will be stalled and nodes 4-7 won't be explored --> we visit 7 nodes // note that node 9 will be visited by both forward and backward searches assertEquals(7, algo.getVisitedNodes()); - assertEquals(102, p.getDistance(), 1.e-3); + assertEquals(10200, p.getDistance(), 1.e-3); assertEquals(IntArrayList.from(1, 8, 9, 0), p.calcNodes(), p.toString()); // without stalling we visit 11 nodes RoutingAlgorithm algoNoSod = createCHAlgo(routingCHGraph, false); Path pNoSod = algoNoSod.calcPath(1, 0); assertEquals(11, algoNoSod.getVisitedNodes()); - assertEquals(102, pNoSod.getDistance(), 1.e-3); + assertEquals(10200, pNoSod.getDistance(), 1.e-3); assertEquals(IntArrayList.from(1, 8, 9, 0), pNoSod.calcNodes(), pNoSod.toString()); } @@ -179,8 +179,8 @@ public void testDirectionDependentSpeedBwdSearch() { private void runTestWithDirectionDependentEdgeSpeed(double speed, double revSpeed, int from, int to, IntArrayList expectedPath, DecimalEncodedValue speedEnc) { BaseGraph graph = createGHStorage(); - graph.edge(0, 1).setDistance(2).set(speedEnc, speed, revSpeed); - graph.edge(1, 2).setDistance(1).set(speedEnc, 20, 20); + graph.edge(0, 1).setDistance(200).set(speedEnc, speed, revSpeed); + graph.edge(1, 2).setDistance(100).set(speedEnc, 20, 20); graph.freeze(); Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased(weighting.getName(), weighting); @@ -189,7 +189,7 @@ private void runTestWithDirectionDependentEdgeSpeed(double speed, double revSpee RoutingCHGraph routingCHGraph = RoutingCHGraphImpl.fromGraph(graph, chStore, chConfig); RoutingAlgorithm algo = createCHAlgo(routingCHGraph, true); Path p = algo.calcPath(from, to); - assertEquals(3, p.getDistance(), 1.e-3); + assertEquals(300, p.getDistance(), 1.e-3); assertEquals(expectedPath, p.calcNodes(), p.toString()); } diff --git a/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java b/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java index 26bfb74a054..999a3541c48 100644 --- a/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java +++ b/core/src/test/java/com/graphhopper/routing/DijkstraOneToManyTest.java @@ -86,12 +86,12 @@ public void testIssue182() { @Test public void testIssue239_and362() { BaseGraph graph = createGHStorage(); - graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - graph.edge(2, 0).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 5).setDistance(1).set(speedEnc, 60, 60); - graph.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - graph.edge(6, 4).setDistance(1).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + graph.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + graph.edge(2, 0).setDistance(100).set(speedEnc, 60, 60); + graph.edge(4, 5).setDistance(100).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(100).set(speedEnc, 60, 60); + graph.edge(6, 4).setDistance(100).set(speedEnc, 60, 60); DijkstraOneToMany algo = createAlgo(graph); assertEquals(-1, algo.findEndNode(0, 4)); @@ -123,14 +123,14 @@ private void initGraph(Graph graph) { // | / // 7-10---- // \-8 - graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - graph.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 10).setDistance(1).set(speedEnc, 60, 60); - graph.edge(0, 7).setDistance(1).set(speedEnc, 60, 60); - graph.edge(7, 8).setDistance(1).set(speedEnc, 60, 60); - graph.edge(7, 10).setDistance(10).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + graph.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + graph.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + graph.edge(3, 4).setDistance(100).set(speedEnc, 60, 60); + graph.edge(4, 10).setDistance(100).set(speedEnc, 60, 60); + graph.edge(0, 7).setDistance(100).set(speedEnc, 60, 60); + graph.edge(7, 8).setDistance(100).set(speedEnc, 60, 60); + graph.edge(7, 10).setDistance(1000).set(speedEnc, 60, 60); } @Test @@ -139,15 +139,15 @@ public void testWeightLimit_issue380() { initGraphWeightLimit(graph, speedEnc); DijkstraOneToMany algo = createAlgo(graph); - algo.setWeightLimit(30); + algo.setWeightLimit(300); Path p = algo.calcPath(0, 4); assertTrue(p.isFound()); - assertEquals(30.0, p.getWeight(), 1e-6); + assertEquals(300.0, p.getWeight()); algo = createAlgo(graph); p = algo.calcPath(0, 3); assertTrue(p.isFound()); - assertEquals(30.0, p.getWeight(), 1e-6); + assertEquals(300.0, p.getWeight()); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java b/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java index fb15f66d09e..6953acd7ebc 100644 --- a/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java +++ b/core/src/test/java/com/graphhopper/routing/DirectedBidirectionalDijkstraTest.java @@ -14,22 +14,21 @@ import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.Graph; -import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.GHUtility; +import com.graphhopper.util.RandomGraph; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.HashSet; -import java.util.Random; +import java.util.*; import static com.graphhopper.util.EdgeIterator.ANY_EDGE; import static com.graphhopper.util.EdgeIterator.NO_EDGE; +import static com.graphhopper.util.GHUtility.updateDistancesFor; import static org.junit.jupiter.api.Assertions.*; /** @@ -65,8 +64,8 @@ private Weighting createWeighting(double uTurnCosts) { public void connectionNotFound() { // nodes 0 and 2 are not connected // 0 -> 1 2 -> 3 - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 0); - graph.edge(2, 3).setDistance(1).set(speedEnc, 10, 0); + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 0); + graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 0); Path path = calcPath(0, 3, 0, 1); assertNotFound(path); @@ -74,7 +73,7 @@ public void connectionNotFound() { @Test public void singleEdge() { - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); // source edge does not exist -> no path assertNotFound(calcPath(0, 1, 5, 0)); @@ -84,17 +83,17 @@ public void singleEdge() { assertNotFound(calcPath(0, 1, NO_EDGE, 0)); assertNotFound(calcPath(0, 1, 0, NO_EDGE)); // using ANY_EDGE -> no restriction - assertPath(calcPath(0, 1, ANY_EDGE, 0), 0.1, 1, 100, nodes(0, 1)); - assertPath(calcPath(0, 1, 0, ANY_EDGE), 0.1, 1, 100, nodes(0, 1)); + assertPath(calcPath(0, 1, ANY_EDGE, 0), 100, 100, 10000, nodes(0, 1)); + assertPath(calcPath(0, 1, 0, ANY_EDGE), 100, 100, 10000, nodes(0, 1)); // edges exist -> they are used as restrictions - assertPath(calcPath(0, 1, 0, 0), 0.1, 1, 100, nodes(0, 1)); + assertPath(calcPath(0, 1, 0, 0), 100, 100, 10000, nodes(0, 1)); } @Test public void simpleGraph() { // 0 -> 1 -> 2 - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10); + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); // source edge does not exist -> no path assertNotFound(calcPath(0, 2, 5, 0)); @@ -104,10 +103,10 @@ public void simpleGraph() { assertNotFound(calcPath(0, 2, NO_EDGE, 0)); assertNotFound(calcPath(0, 2, 0, NO_EDGE)); // using ANY_EDGE -> no restriction - assertPath(calcPath(0, 2, ANY_EDGE, 1), 0.2, 2, 200, nodes(0, 1, 2)); - assertPath(calcPath(0, 2, 0, ANY_EDGE), 0.2, 2, 200, nodes(0, 1, 2)); + assertPath(calcPath(0, 2, ANY_EDGE, 1), 200, 200, 20000, nodes(0, 1, 2)); + assertPath(calcPath(0, 2, 0, ANY_EDGE), 200, 200, 20000, nodes(0, 1, 2)); // edges exist -> they are used as restrictions - assertPath(calcPath(0, 2, 0, 1), 0.2, 2, 200, nodes(0, 1, 2)); + assertPath(calcPath(0, 2, 0, 1), 200, 200, 20000, nodes(0, 1, 2)); } @Test @@ -118,11 +117,11 @@ public void sourceEqualsTarget() { // 0 - 1 // \ | // - 2 - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(0, 2).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10); - assertPath(calcPath(0, 0, 0, 1), 0.3, 3, 300, nodes(0, 1, 2, 0)); - assertPath(calcPath(0, 0, 1, 0), 0.3, 3, 300, nodes(0, 2, 1, 0)); + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); + graph.edge(0, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); + assertPath(calcPath(0, 0, 0, 1), 300, 300, 30000, nodes(0, 1, 2, 0)); + assertPath(calcPath(0, 0, 1, 0), 300, 300, 30000, nodes(0, 2, 1, 0)); // without restrictions the weight should be zero assertPath(calcPath(0, 0, ANY_EDGE, ANY_EDGE), 0, 0, 0, nodes(0)); // in some cases no path is possible @@ -136,20 +135,20 @@ public void restrictedEdges() { // 0 = 1 - 2 - 3 = 4 // \ | / // - 5 - 6 - 7 - - int costlySource = graph.edge(0, 1).setDistance(5).set(speedEnc, 10, 10).getEdge(); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10); - graph.edge(2, 3).setDistance(1).set(speedEnc, 10, 10); - int costlyTarget = graph.edge(3, 4).setDistance(5).set(speedEnc, 10, 10).getEdge(); - int cheapSource = graph.edge(0, 5).setDistance(1).set(speedEnc, 10, 10).getEdge(); - graph.edge(5, 6).setDistance(1).set(speedEnc, 10, 10); - graph.edge(6, 7).setDistance(1).set(speedEnc, 10, 10); - int cheapTarget = graph.edge(7, 4).setDistance(1).set(speedEnc, 10, 10).getEdge(); - graph.edge(2, 6).setDistance(1).set(speedEnc, 10, 10); - - assertPath(calcPath(0, 4, cheapSource, cheapTarget), 0.4, 4, 400, nodes(0, 5, 6, 7, 4)); - assertPath(calcPath(0, 4, cheapSource, costlyTarget), 0.9, 9, 900, nodes(0, 5, 6, 2, 3, 4)); - assertPath(calcPath(0, 4, costlySource, cheapTarget), 0.9, 9, 900, nodes(0, 1, 2, 6, 7, 4)); - assertPath(calcPath(0, 4, costlySource, costlyTarget), 1.2, 12, 1200, nodes(0, 1, 2, 3, 4)); + int costlySource = graph.edge(0, 1).setDistance(500).set(speedEnc, 10, 10).getEdge(); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 10); + int costlyTarget = graph.edge(3, 4).setDistance(500).set(speedEnc, 10, 10).getEdge(); + int cheapSource = graph.edge(0, 5).setDistance(100).set(speedEnc, 10, 10).getEdge(); + graph.edge(5, 6).setDistance(100).set(speedEnc, 10, 10); + graph.edge(6, 7).setDistance(100).set(speedEnc, 10, 10); + int cheapTarget = graph.edge(7, 4).setDistance(100).set(speedEnc, 10, 10).getEdge(); + graph.edge(2, 6).setDistance(100).set(speedEnc, 10, 10); + + assertPath(calcPath(0, 4, cheapSource, cheapTarget), 400, 400, 40000, nodes(0, 5, 6, 7, 4)); + assertPath(calcPath(0, 4, cheapSource, costlyTarget), 900, 900, 90000, nodes(0, 5, 6, 2, 3, 4)); + assertPath(calcPath(0, 4, costlySource, cheapTarget), 900, 900, 90000, nodes(0, 1, 2, 6, 7, 4)); + assertPath(calcPath(0, 4, costlySource, costlyTarget), 1200, 1200, 120000, nodes(0, 1, 2, 3, 4)); } @Test @@ -160,15 +159,15 @@ public void notConnectedDueToRestrictions() { // \ / // - 3 - // we cannot go from 0 to 2 if we enforce north-south or south-north - int sourceNorth = graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10).getEdge(); - int sourceSouth = graph.edge(0, 3).setDistance(2).set(speedEnc, 10, 10).getEdge(); - int targetNorth = graph.edge(1, 2).setDistance(3).set(speedEnc, 10, 10).getEdge(); - int targetSouth = graph.edge(3, 2).setDistance(4).set(speedEnc, 10, 10).getEdge(); + int sourceNorth = graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10).getEdge(); + int sourceSouth = graph.edge(0, 3).setDistance(200).set(speedEnc, 10, 10).getEdge(); + int targetNorth = graph.edge(1, 2).setDistance(300).set(speedEnc, 10, 10).getEdge(); + int targetSouth = graph.edge(3, 2).setDistance(400).set(speedEnc, 10, 10).getEdge(); - assertPath(calcPath(0, 2, sourceNorth, targetNorth), 0.4, 4, 400, nodes(0, 1, 2)); + assertPath(calcPath(0, 2, sourceNorth, targetNorth), 400, 400, 40000, nodes(0, 1, 2)); assertNotFound(calcPath(0, 2, sourceNorth, targetSouth)); assertNotFound(calcPath(0, 2, sourceSouth, targetNorth)); - assertPath(calcPath(0, 2, sourceSouth, targetSouth), 0.6, 6, 600, nodes(0, 3, 2)); + assertPath(calcPath(0, 2, sourceSouth, targetSouth), 600, 600, 60000, nodes(0, 3, 2)); } @Test @@ -176,13 +175,13 @@ public void restrictions_one_ways() { // 0 <- 1 <- 2 // \ | / // >--3--> - graph.edge(0, 3).setDistance(1).set(speedEnc, 10, 0); - graph.edge(1, 0).setDistance(1).set(speedEnc, 10, 0); - graph.edge(3, 2).setDistance(1).set(speedEnc, 10, 0); - graph.edge(2, 1).setDistance(1).set(speedEnc, 10, 0); - graph.edge(1, 3).setDistance(1).set(speedEnc, 10, 10); + graph.edge(0, 3).setDistance(100).set(speedEnc, 10, 0); + graph.edge(1, 0).setDistance(100).set(speedEnc, 10, 0); + graph.edge(3, 2).setDistance(100).set(speedEnc, 10, 0); + graph.edge(2, 1).setDistance(100).set(speedEnc, 10, 0); + graph.edge(1, 3).setDistance(100).set(speedEnc, 10, 10); - assertPath(calcPath(0, 2, 0, 2), 0.2, 2, 200, nodes(0, 3, 2)); + assertPath(calcPath(0, 2, 0, 2), 200, 200, 20000, nodes(0, 3, 2)); assertNotFound(calcPath(0, 2, 1, 2)); assertNotFound(calcPath(0, 2, 0, 3)); assertNotFound(calcPath(0, 2, 1, 3)); @@ -197,17 +196,17 @@ public void forcingDirectionDoesNotMeanWeCannotUseEdgeAtAll() { // 2 - 3 // | | // 5 - 4 - int north = graph.edge(1, 0).setDistance(1).set(speedEnc, 10, 10).getEdge(); - int south = graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10).getEdge(); - graph.edge(2, 5).setDistance(1).set(speedEnc, 10, 0); - graph.edge(5, 4).setDistance(1).set(speedEnc, 10, 0); - graph.edge(4, 3).setDistance(1).set(speedEnc, 10, 0); - graph.edge(3, 2).setDistance(1).set(speedEnc, 10, 0); - graph.edge(1, 0).setDistance(1).set(speedEnc, 10, 0); - graph.edge(0, 6).setDistance(1).set(speedEnc, 10, 0); - int targetEdge = graph.edge(6, 7).setDistance(1).set(speedEnc, 10, 0).getEdge(); - assertPath(calcPath(1, 7, north, targetEdge), 0.3, 3, 300, nodes(1, 0, 6, 7)); - assertPath(calcPath(1, 7, south, targetEdge), 0.9, 9, 900, nodes(1, 2, 5, 4, 3, 2, 1, 0, 6, 7)); + int north = graph.edge(1, 0).setDistance(100).set(speedEnc, 10, 10).getEdge(); + int south = graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10).getEdge(); + graph.edge(2, 5).setDistance(100).set(speedEnc, 10, 0); + graph.edge(5, 4).setDistance(100).set(speedEnc, 10, 0); + graph.edge(4, 3).setDistance(100).set(speedEnc, 10, 0); + graph.edge(3, 2).setDistance(100).set(speedEnc, 10, 0); + graph.edge(1, 0).setDistance(100).set(speedEnc, 10, 0); + graph.edge(0, 6).setDistance(100).set(speedEnc, 10, 0); + int targetEdge = graph.edge(6, 7).setDistance(100).set(speedEnc, 10, 0).getEdge(); + assertPath(calcPath(1, 7, north, targetEdge), 300, 300, 30000, nodes(1, 0, 6, 7)); + assertPath(calcPath(1, 7, south, targetEdge), 900, 900, 90000, nodes(1, 2, 5, 4, 3, 2, 1, 0, 6, 7)); } @Test @@ -215,14 +214,14 @@ public void directedCircle() { // 0---6--1 -> 2 // | / // 5 <- 4 <- 3 - graph.edge(0, 6).setDistance(1).set(speedEnc, 10, 10); - graph.edge(6, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 0); - graph.edge(2, 3).setDistance(1).set(speedEnc, 10, 0); - graph.edge(3, 4).setDistance(1).set(speedEnc, 10, 0); - graph.edge(4, 5).setDistance(1).set(speedEnc, 10, 0); - graph.edge(5, 0).setDistance(1).set(speedEnc, 10, 0); - assertPath(calcPath(6, 0, 1, 6), 0.6, 6, 600, nodes(6, 1, 2, 3, 4, 5, 0)); + graph.edge(0, 6).setDistance(100).set(speedEnc, 10, 10); + graph.edge(6, 1).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 0); + graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 0); + graph.edge(3, 4).setDistance(100).set(speedEnc, 10, 0); + graph.edge(4, 5).setDistance(100).set(speedEnc, 10, 0); + graph.edge(5, 0).setDistance(100).set(speedEnc, 10, 0); + assertPath(calcPath(6, 0, 1, 6), 600, 600, 60000, nodes(6, 1, 2, 3, 4, 5, 0)); } @Test @@ -234,38 +233,36 @@ public void directedRouting() { // | / \ | // 8 = 7 6 = 5 EdgeIteratorState rightNorth, rightSouth, leftSouth, leftNorth; - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10); - graph.edge(2, 3).setDistance(1).set(speedEnc, 10, 10); - graph.edge(3, 4).setDistance(3).set(speedEnc, 10, 10); - rightNorth = graph.edge(4, 10).setDistance(1).set(speedEnc, 10, 10); - rightSouth = graph.edge(10, 5).setDistance(1).set(speedEnc, 10, 10); - graph.edge(5, 6).setDistance(2).set(speedEnc, 10, 10); - graph.edge(6, 2).setDistance(1).set(speedEnc, 10, 10); - graph.edge(2, 7).setDistance(1).set(speedEnc, 10, 10); - graph.edge(7, 8).setDistance(9).set(speedEnc, 10, 10); - leftSouth = graph.edge(8, 9).setDistance(1).set(speedEnc, 10, 10); - leftNorth = graph.edge(9, 0).setDistance(1).set(speedEnc, 10, 10); - - // make paths fully deterministic by applying some turn costs at junction node 2 - setTurnCost(7, 2, 3, 1); - setTurnCost(7, 2, 6, 3); - setTurnCost(1, 2, 3, 5); - setTurnCost(1, 2, 6, 7); - setTurnCost(1, 2, 7, 9); - - final double unitEdgeWeight = 0.1; + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 10); + graph.edge(3, 4).setDistance(300).set(speedEnc, 10, 10); + rightNorth = graph.edge(4, 10).setDistance(100).set(speedEnc, 10, 10); + rightSouth = graph.edge(10, 5).setDistance(100).set(speedEnc, 10, 10); + graph.edge(5, 6).setDistance(200).set(speedEnc, 10, 10); + graph.edge(6, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(2, 7).setDistance(100).set(speedEnc, 10, 10); + graph.edge(7, 8).setDistance(900).set(speedEnc, 10, 10); + leftSouth = graph.edge(8, 9).setDistance(100).set(speedEnc, 10, 10); + leftNorth = graph.edge(9, 0).setDistance(100).set(speedEnc, 10, 10); + + // make paths fully deterministic by adding some restrictions + setRestriction(1, 2, 3); + setRestriction(1, 2, 7); + + final double unitEdgeWeight = 100; + final long unitEdgeTime = 10; assertPath(calcPath(9, 9, leftNorth.getEdge(), leftSouth.getEdge()), - 23 * unitEdgeWeight + 5, 23, (long) ((23 * unitEdgeWeight + 5) * 1000), - nodes(9, 0, 1, 2, 3, 4, 10, 5, 6, 2, 7, 8, 9)); + 23 * unitEdgeWeight, 2300, (long) ((23 * unitEdgeTime) * 1000), + nodes(9, 0, 1, 2, 6, 5, 10, 4, 3, 2, 7, 8, 9)); assertPath(calcPath(9, 9, leftSouth.getEdge(), leftNorth.getEdge()), - 14 * unitEdgeWeight, 14, (long) ((14 * unitEdgeWeight) * 1000), + 14 * unitEdgeWeight, 1400, (long) ((14 * unitEdgeTime) * 1000), nodes(9, 8, 7, 2, 1, 0, 9)); assertPath(calcPath(9, 10, leftSouth.getEdge(), rightSouth.getEdge()), - 15 * unitEdgeWeight + 3, 15, (long) ((15 * unitEdgeWeight + 3) * 1000), + 15 * unitEdgeWeight, 1500, (long) ((15 * unitEdgeTime) * 1000), nodes(9, 8, 7, 2, 6, 5, 10)); assertPath(calcPath(9, 10, leftSouth.getEdge(), rightNorth.getEdge()), - 16 * unitEdgeWeight + 1, 16, (long) ((16 * unitEdgeWeight + 1) * 1000), + 16 * unitEdgeWeight, 1600, (long) ((16 * unitEdgeTime) * 1000), nodes(9, 8, 7, 2, 3, 4, 10)); } @@ -275,10 +272,10 @@ public void sourceAndTargetAreNeighbors() { graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 10); - assertPath(calcPath(1, 2, ANY_EDGE, ANY_EDGE), 10, 100, 10000, nodes(1, 2)); - assertPath(calcPath(1, 2, 1, ANY_EDGE), 10, 100, 10000, nodes(1, 2)); - assertPath(calcPath(1, 2, ANY_EDGE, 1), 10, 100, 10000, nodes(1, 2)); - assertPath(calcPath(1, 2, 1, 1), 10, 100, 10000, nodes(1, 2)); + assertPath(calcPath(1, 2, ANY_EDGE, ANY_EDGE), 100, 100, 10000, nodes(1, 2)); + assertPath(calcPath(1, 2, 1, ANY_EDGE), 100, 100, 10000, nodes(1, 2)); + assertPath(calcPath(1, 2, ANY_EDGE, 1), 100, 100, 10000, nodes(1, 2)); + assertPath(calcPath(1, 2, 1, 1), 100, 100, 10000, nodes(1, 2)); // this case is a bit sketchy: we may not find a valid path just because the from/to // node initialization hits the target/source node, but we have to also consider the // edge restriction at the source/target nodes @@ -287,9 +284,9 @@ public void sourceAndTargetAreNeighbors() { assertNotFound(calcPath(1, 2, 0, 2)); // if we allow u-turns it is of course different again - assertPath(calcPath(1, 2, 1, 2, createWeighting(100)), 30 + 100, 300, 130000, nodes(1, 2, 3, 2)); - assertPath(calcPath(1, 2, 0, 1, createWeighting(100)), 30 + 100, 300, 130000, nodes(1, 0, 1, 2)); - assertPath(calcPath(1, 2, 0, 2, createWeighting(100)), 50 + 200, 500, 250000, nodes(1, 0, 1, 2, 3, 2)); + assertPath(calcPath(1, 2, 1, 2, createWeighting(100)), 300 + 1000, 300, 130000, nodes(1, 2, 3, 2)); + assertPath(calcPath(1, 2, 0, 1, createWeighting(100)), 300 + 1000, 300, 130000, nodes(1, 0, 1, 2)); + assertPath(calcPath(1, 2, 0, 2, createWeighting(100)), 500 + 2000, 500, 250000, nodes(1, 0, 1, 2, 3, 2)); } @Test @@ -297,23 +294,23 @@ public void worksWithTurnCosts() { // 0 - 1 - 2 // | | | // 3 - 4 - 5 - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 4).setDistance(1).set(speedEnc, 10, 10); - graph.edge(0, 3).setDistance(1).set(speedEnc, 10, 10); - graph.edge(3, 4).setDistance(1).set(speedEnc, 10, 10); - graph.edge(4, 5).setDistance(1).set(speedEnc, 10, 10); - graph.edge(5, 2).setDistance(1).set(speedEnc, 10, 10); + graph.edge(0, 1).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 4).setDistance(100).set(speedEnc, 10, 10); + graph.edge(0, 3).setDistance(100).set(speedEnc, 10, 10); + graph.edge(3, 4).setDistance(100).set(speedEnc, 10, 10); + graph.edge(4, 5).setDistance(100).set(speedEnc, 10, 10); + graph.edge(5, 2).setDistance(100).set(speedEnc, 10, 10); setRestriction(0, 3, 4); setTurnCost(4, 5, 2, 6); // due to the restrictions we have to take the expensive path with turn costs - assertPath(calcPath(0, 2, 0, 6), 6.4, 4, 6400, nodes(0, 1, 4, 5, 2)); + assertPath(calcPath(0, 2, 0, 6), 400 + 60, 400, 40000 + 6000, nodes(0, 1, 4, 5, 2)); // enforcing going south from node 0 yields no path, because of the restricted turn 0->3->4 assertNotFound(calcPath(0, 2, 3, ANY_EDGE)); // without the restriction its possible - assertPath(calcPath(0, 2, ANY_EDGE, ANY_EDGE), 0.2, 2, 200, nodes(0, 1, 2)); + assertPath(calcPath(0, 2, ANY_EDGE, ANY_EDGE), 200, 200, 20000, nodes(0, 1, 2)); } @Test @@ -342,28 +339,42 @@ public void finiteUTurnCosts() { setRestriction(0, 1, 6); setRestriction(5, 4, 3); - assertPath(calcPath(0, 6, right0, left6), 107.0, 1070, 107000, nodes(0, 1, 2, 3, 4, 5, 2, 1, 6)); + assertPath(calcPath(0, 6, right0, left6), 1070.0, 1070, 107000, nodes(0, 1, 2, 3, 4, 5, 2, 1, 6)); // if the u-turn cost is finite it depends on its value if we rather do the p-turn or do an immediate u-turn at node 2 - assertPath(calcPath(0, 6, right0, left6, createWeighting(5000)), 107.0, 1070, 107000, nodes(0, 1, 2, 3, 4, 5, 2, 1, 6)); - assertPath(calcPath(0, 6, right0, left6, createWeighting(40)), 44, 40, 44000, nodes(0, 1, 2, 1, 6)); + assertPath(calcPath(0, 6, right0, left6, createWeighting(5000)), 1070.0, 1070, 107000, nodes(0, 1, 2, 3, 4, 5, 2, 1, 6)); + assertPath(calcPath(0, 6, right0, left6, createWeighting(40)), 440, 40, 44000, nodes(0, 1, 2, 1, 6)); - assertPath(calcPath(0, 6, left0, right6), 4, 40, 4000, nodes(0, 7, 8, 9, 6)); - assertPath(calcPath(0, 6, left0, left6), 111, 1110, 111000, nodes(0, 7, 8, 9, 6, 1, 2, 3, 4, 5, 2, 1, 6)); + assertPath(calcPath(0, 6, left0, right6), 40, 40, 4000, nodes(0, 7, 8, 9, 6)); + assertPath(calcPath(0, 6, left0, left6), 1110, 1110, 111000, nodes(0, 7, 8, 9, 6, 1, 2, 3, 4, 5, 2, 1, 6)); // if the u-turn cost is finite we do a u-turn at node 1 (not at node 7 at the beginning!) - assertPath(calcPath(0, 6, left0, left6, createWeighting(40)), 46.0, 60, 46000, nodes(0, 7, 8, 9, 6, 1, 6)); + assertPath(calcPath(0, 6, left0, left6, createWeighting(40)), 460.0, 60, 46000, nodes(0, 7, 8, 9, 6, 1, 6)); } @RepeatedTest(10) public void compare_standard_dijkstra() { - compare_with_dijkstra(weighting); + compare_with_dijkstra(weighting, false, false); } @RepeatedTest(10) public void compare_standard_dijkstra_finite_uturn_costs() { - compare_with_dijkstra(createWeighting(40)); + compare_with_dijkstra(createWeighting(40), false, false); + } + + @RepeatedTest(10) + public void compare_standard_dijkstra_strict() { + compare_with_dijkstra(weighting, false, true); } - private void compare_with_dijkstra(Weighting w) { + @RepeatedTest(10) + public void compare_standard_dijkstra_finite_uturn_costs_strict() { + // with finite u-turn costs paths on a tree are not unique: we can do a necessary u-turn in + // different tree branches. u-turns can be necessary even without restricted start/target edges due to + // one-ways or turn restrictions + compare_with_dijkstra(createWeighting(40), true, false); + } + + private void compare_with_dijkstra(Weighting w, boolean chain, boolean tree) { + assertFalse(chain && tree); // if we do not use start/target edge restrictions we should get the same result as with Dijkstra. // basically this test should cover all kinds of interesting cases except the ones where we restrict the // start/target edges. @@ -372,28 +383,18 @@ private void compare_with_dijkstra(Weighting w) { Random rnd = new Random(seed); int numNodes = 100; - GHUtility.buildRandomGraph(graph, rnd, numNodes, 2.2, true, speedEnc, null, 0.8, 0.8); + RandomGraph.start().seed(seed).nodes(numNodes).curviness(0.1).speedZero((tree || chain) ? 0 : 0.1).chain(chain).tree(tree).fill(graph, speedEnc); GHUtility.addRandomTurnCosts(graph, seed, null, turnCostEnc, maxTurnCosts, turnCostStorage); - long numStrictViolations = 0; for (int i = 0; i < numQueries; i++) { int source = rnd.nextInt(numNodes); int target = rnd.nextInt(numNodes); Path dijkstraPath = new Dijkstra(graph, w, TraversalMode.EDGE_BASED).calcPath(source, target); Path path = calcPath(source, target, ANY_EDGE, ANY_EDGE, w); assertEquals(dijkstraPath.isFound(), path.isFound(), "dijkstra found/did not find a path, from: " + source + ", to: " + target + ", seed: " + seed); - assertEquals(dijkstraPath.getWeight(), path.getWeight(), 1.e-6, "weight does not match dijkstra, from: " + source + ", to: " + target + ", seed: " + seed); - // we do not do a strict check because there can be ambiguity, for example when there are zero weight loops. - // however, when there are too many deviations we fail - if ( - Math.abs(dijkstraPath.getDistance() - path.getDistance()) > 1.e-6 - || Math.abs(dijkstraPath.getTime() - path.getTime()) > 10 - || !dijkstraPath.calcNodes().equals(path.calcNodes())) { - numStrictViolations++; - } - } - if (numStrictViolations > Math.max(1, 0.05 * numQueries)) { - fail("Too many strict violations, seed: " + seed + " - " + numStrictViolations + " / " + numQueries); + List strictViolations = GHUtility.comparePaths(dijkstraPath, path, source, target, true, seed); + if ((tree || chain) && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } } @@ -411,15 +412,15 @@ public void blockArea() { graph.edge(6, 3).setDistance(100).set(speedEnc, 10, 10); // usually we would take the direct route - assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE), 3, 30, 3000, nodes(0, 1, 2, 3)); + assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE), 30, 30, 3000, nodes(0, 1, 2, 3)); // with forced edges we might have to go around - assertPath(calcPath(0, 3, 3, ANY_EDGE), 40, 400, 40000, nodes(0, 4, 5, 6, 3)); - assertPath(calcPath(0, 3, ANY_EDGE, 6), 40, 400, 40000, nodes(0, 4, 5, 6, 3)); + assertPath(calcPath(0, 3, 3, ANY_EDGE), 400, 400, 40000, nodes(0, 4, 5, 6, 3)); + assertPath(calcPath(0, 3, ANY_EDGE, 6), 400, 400, 40000, nodes(0, 4, 5, 6, 3)); // with avoided edges we also have to take a longer route - assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE, createAvoidEdgeWeighting(edge1)), 40, 400, 40000, nodes(0, 4, 5, 6, 3)); - assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE, createAvoidEdgeWeighting(edge2)), 40, 400, 40000, nodes(0, 4, 5, 6, 3)); + assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE, createAvoidEdgeWeighting(edge1)), 400, 400, 40000, nodes(0, 4, 5, 6, 3)); + assertPath(calcPath(0, 3, ANY_EDGE, ANY_EDGE, createAvoidEdgeWeighting(edge2)), 400, 400, 40000, nodes(0, 4, 5, 6, 3)); // enforcing forbidden start/target edges still does not allow using them assertNotFound(calcPath(0, 3, edge1.getEdge(), edge2.getEdge(), createAvoidEdgeWeighting(edge1))); @@ -447,23 +448,22 @@ public void directedRouting_noUTurnAtVirtualEdge() { // 0 -- 1 -> 2 // | | // 5 <- 4 <- 3 - graph.edge(0, 1).setDistance(1).set(speedEnc, 10, 10); - graph.edge(1, 2).setDistance(1).set(speedEnc, 10, 0); - graph.edge(2, 3).setDistance(1).set(speedEnc, 10, 0); - graph.edge(3, 4).setDistance(1).set(speedEnc, 10, 0); - graph.edge(4, 5).setDistance(1).set(speedEnc, 10, 0); - graph.edge(5, 0).setDistance(1).set(speedEnc, 10, 0); - NodeAccess na = graph.getNodeAccess(); - na.setNode(0, 1, 0); - na.setNode(1, 1, 1); - na.setNode(2, 1, 2); - na.setNode(3, 0, 2); - na.setNode(4, 0, 1); - na.setNode(5, 0, 0); + graph.edge(0, 1).setDistance(0).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(0).set(speedEnc, 10, 0); + graph.edge(2, 3).setDistance(0).set(speedEnc, 10, 0); + graph.edge(3, 4).setDistance(0).set(speedEnc, 10, 0); + graph.edge(4, 5).setDistance(0).set(speedEnc, 10, 0); + graph.edge(5, 0).setDistance(0).set(speedEnc, 10, 0); + updateDistancesFor(graph, 0, 0.01, 0.00); + updateDistancesFor(graph, 1, 0.01, 0.01); + updateDistancesFor(graph, 2, 0.01, 0.02); + updateDistancesFor(graph, 3, 0.00, 0.02); + updateDistancesFor(graph, 4, 0.00, 0.01); + updateDistancesFor(graph, 5, 0.00, 0.00); LocationIndexTree locationIndex = new LocationIndexTree(graph, graph.getDirectory()); locationIndex.prepareIndex(); - Snap snap = locationIndex.findClosest(1.1, 0.5, EdgeFilter.ALL_EDGES); + Snap snap = locationIndex.findClosest(0.011, 0.005, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(graph, snap); assertEquals(Snap.Position.EDGE, snap.getSnappedPosition(), "wanted to get EDGE"); @@ -476,10 +476,10 @@ public void directedRouting_noUTurnAtVirtualEdge() { EdgeIteratorState virtualEdge = GHUtility.getEdge(queryGraph, 6, 1); int outEdge = virtualEdge.getEdge(); - EdgeToEdgeRoutingAlgorithm algo = createAlgo(queryGraph, weighting); + EdgeToEdgeRoutingAlgorithm algo = createAlgo(queryGraph, queryGraph.wrapWeighting(weighting)); Path path = algo.calcPath(6, 0, outEdge, ANY_EDGE); assertEquals(nodes(6, 1, 2, 3, 4, 5, 0), path.calcNodes()); - assertEquals(5 + virtualEdge.getDistance(), path.getDistance(), 1.e-3); + assertEquals(6115.720, path.getDistance(), 1.e-3); } private Path calcPath(int source, int target, int sourceOutEdge, int targetInEdge) { diff --git a/core/src/test/java/com/graphhopper/routing/DirectedRoutingTest.java b/core/src/test/java/com/graphhopper/routing/DirectedRoutingTest.java index c8c2c243173..40b010fafe8 100644 --- a/core/src/test/java/com/graphhopper/routing/DirectedRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/DirectedRoutingTest.java @@ -39,10 +39,7 @@ import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; -import com.graphhopper.util.EdgeExplorer; -import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.GHUtility; -import com.graphhopper.util.PMap; +import com.graphhopper.util.*; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; @@ -60,6 +57,7 @@ import static com.graphhopper.util.EdgeIterator.ANY_EDGE; import static com.graphhopper.util.EdgeIterator.NO_EDGE; +import static com.graphhopper.util.GHUtility.comparePaths; import static com.graphhopper.util.GHUtility.createRandomSnaps; import static com.graphhopper.util.Parameters.Algorithms.ASTAR_BI; import static com.graphhopper.util.Parameters.Algorithms.DIJKSTRA_BI; @@ -100,7 +98,7 @@ public Fixture(Algo algo, double uTurnCosts, boolean prepareCH, boolean prepareL this.prepareCH = prepareCH; this.prepareLM = prepareLM; - dir = new RAMDirectory(); + dir = new GHDirectory("", DAType.RAM); maxTurnCosts = 10; speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); turnCostEnc = TurnCost.create("car", maxTurnCosts); @@ -214,34 +212,42 @@ private enum Algo { @ParameterizedTest @ArgumentsSource(RepeatedFixtureProvider.class) public void randomGraph(Fixture f) { + run_randomGraph(f, false, false); + } + + @ParameterizedTest + @ArgumentsSource(RepeatedFixtureProvider.class) + public void randomGraph_strict(Fixture f) { + // with finite u-turn costs paths on a tree are not unique: we can do a necessary u-turn in + // different tree branches. u-turns can be necessary even without restricted start/target edges due to + // one-ways or turn restrictions + boolean chain = Double.isFinite(f.uTurnCosts); + boolean tree = !chain; + run_randomGraph(f, chain, tree); + } + + private void run_randomGraph(Fixture f, boolean chain, boolean tree) { final long seed = System.nanoTime(); - final int numQueries = 50; + final int numQueries = 30; Random rnd = new Random(seed); - GHUtility.buildRandomGraph(f.graph, rnd, 100, 2.2, true, f.speedEnc, null, 0.8, 0.8); + RandomGraph.start().seed(seed).nodes(100).curviness(0.1).speedZero((chain || tree) ? 0 : 0.1).chain(chain).tree(tree).fill(f.graph, f.speedEnc); GHUtility.addRandomTurnCosts(f.graph, seed, null, f.turnCostEnc, f.maxTurnCosts, f.turnCostStorage); // GHUtility.printGraphForUnitTest(f.graph, f.encoder); f.preProcessGraph(); - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { int source = f.getRandom(rnd); int target = f.getRandom(rnd); int sourceOutEdge = getSourceOutEdge(rnd, source, f.graph); int targetInEdge = getTargetInEdge(rnd, target, f.graph); // LOGGER.info("source: " + source + ", target: " + target + ", sourceOutEdge: " + sourceOutEdge + ", targetInEdge: " + targetInEdge); - Path refPath = new DijkstraBidirectionRef(f.graph, ((Graph) f.graph).wrapWeighting(f.weighting), TraversalMode.EDGE_BASED) + Path refPath = new DijkstraBidirectionRef(f.graph, f.graph.wrapWeighting(f.weighting), TraversalMode.EDGE_BASED) .calcPath(source, target, sourceOutEdge, targetInEdge); Path path = f.createAlgo() .calcPath(source, target, sourceOutEdge, targetInEdge); - // do not check nodes, because there can be ambiguity when there are zero weight loops - strictViolations.addAll(comparePaths(refPath, path, source, target, false, seed)); - } - // sometimes there are multiple best paths with different distance/time, if this happens too often something - // is wrong and we fail - if (strictViolations.size() > Math.max(1, 0.05 * numQueries)) { - for (String strictViolation : strictViolations) { - LOGGER.info("strict violation: " + strictViolation); - } - fail("Too many strict violations, with seed: " + seed + " - " + strictViolations.size() + " / " + numQueries); + + List strictViolations = comparePaths(refPath, path, source, target, false, seed); + if ((chain || tree) && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } } @@ -251,20 +257,31 @@ public void randomGraph(Fixture f) { @ParameterizedTest @ArgumentsSource(RepeatedFixtureProvider.class) public void randomGraph_withQueryGraph(Fixture f) { - final long seed = System.nanoTime(); - final int numQueries = 50; + run_randomGraph_withQueryGraph(f, false, false); + } - // we may not use an offset when query graph is involved, otherwise traveling via virtual edges will not be - // the same as taking the direct edge! - double pOffset = 0; + @ParameterizedTest + @ArgumentsSource(RepeatedFixtureProvider.class) + public void randomGraph_withQueryGraph_strict(Fixture f) { + boolean chain = Double.isFinite(f.uTurnCosts); + boolean tree = !chain; + run_randomGraph_withQueryGraph(f, chain, tree); + } + + private void run_randomGraph_withQueryGraph(Fixture f, boolean chain, boolean tree) { + final long seed = System.nanoTime(); + final int numQueries = 30; Random rnd = new Random(seed); - GHUtility.buildRandomGraph(f.graph, rnd, 50, 2.2, true, f.speedEnc, null, 0.8, pOffset); + // curviness must be zero, because directed routing can require traveling back to the start + // node for example. with curviness the sum of virtual edge distances will be smaller than + // original edge distance + double curviness = 0; + RandomGraph.start().seed(seed).nodes(50).curviness(curviness).speedZero((chain || tree) ? 0 : 0.1).chain(chain).tree(tree).fill(f.graph, f.speedEnc); GHUtility.addRandomTurnCosts(f.graph, seed, null, f.turnCostEnc, f.maxTurnCosts, f.turnCostStorage); // GHUtility.printGraphForUnitTest(f.graph, f.speedEnc); f.preProcessGraph(); LocationIndexTree index = new LocationIndexTree(f.graph, f.dir); index.prepareIndex(); - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { List snaps = createRandomSnaps(f.graph.getBounds(), index, rnd, 2, true, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(f.graph, snaps); @@ -278,18 +295,15 @@ public void randomGraph_withQueryGraph(Fixture f) { int chSourceOutEdge = getSourceOutEdge(tmpRnd2, source, queryGraph); int chTargetInEdge = getTargetInEdge(tmpRnd2, target, queryGraph); - Path refPath = new DijkstraBidirectionRef(queryGraph, ((Graph) queryGraph).wrapWeighting(f.weighting), TraversalMode.EDGE_BASED) + Path refPath = new DijkstraBidirectionRef(queryGraph, queryGraph.wrapWeighting(f.weighting), TraversalMode.EDGE_BASED) .calcPath(source, target, sourceOutEdge, targetInEdge); Path path = f.createAlgo(queryGraph) .calcPath(source, target, chSourceOutEdge, chTargetInEdge); - // do not check nodes, because there can be ambiguity when there are zero weight loops - strictViolations.addAll(comparePaths(refPath, path, source, target, false, seed)); - } - // sometimes there are multiple best paths with different distance/time, if this happens too often something - // is wrong and we fail - if (strictViolations.size() > Math.max(1, 0.05 * numQueries)) { - fail("Too many strict violations, with seed: " + seed + " - " + strictViolations.size() + " / " + numQueries); + List strictViolations = comparePaths(refPath, path, source, target, false, seed); + // trees have unique paths, so we can do strict checking to test distance/time/nodes + if ((chain || tree) && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } } @@ -339,27 +353,6 @@ public void issue1971(Fixture f) { assertTrue(comparePaths(refPath, path, source, target, false, -1).isEmpty()); } - private List comparePaths(Path refPath, Path path, int source, int target, boolean checkNodes, long seed) { - List strictViolations = new ArrayList<>(); - double refWeight = refPath.getWeight(); - double weight = path.getWeight(); - if (Math.abs(refWeight - weight) > 1.e-2) { - LOGGER.warn("expected: " + refPath.calcNodes()); - LOGGER.warn("given: " + path.calcNodes()); - fail("wrong weight: " + source + "->" + target + ", expected: " + refWeight + ", given: " + weight + ", seed: " + seed); - } - if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) { - strictViolations.add("wrong distance " + source + "->" + target + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance()); - } - if (Math.abs(path.getTime() - refPath.getTime()) > 50) { - strictViolations.add("wrong time " + source + "->" + target + ", expected: " + refPath.getTime() + ", given: " + path.getTime()); - } - if (checkNodes && !refPath.calcNodes().equals(path.calcNodes())) { - strictViolations.add("wrong nodes " + source + "->" + target + "\nexpected: " + refPath.calcNodes() + "\ngiven: " + path.calcNodes()); - } - return strictViolations; - } - private int getTargetInEdge(Random rnd, int node, Graph graph) { return getAdjEdge(rnd, node, graph); } diff --git a/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java b/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java index 7d35788bc2f..ac99486864b 100644 --- a/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/DirectionResolverOnQueryGraphTest.java @@ -26,9 +26,7 @@ import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.NodeAccess; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; @@ -39,7 +37,9 @@ import java.util.List; import static com.graphhopper.routing.DirectionResolverResult.unrestricted; +import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; import static com.graphhopper.util.EdgeIterator.NO_EDGE; +import static com.graphhopper.util.GHUtility.updateDistancesFor; import static com.graphhopper.util.Helper.createPointList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.fail; @@ -196,7 +196,8 @@ public void road_with_geometry() { // make sure graph has valid bounds addNode(2, 5, 5); - addEdge(0, 1, true).setWayGeometry(createPointList(2, 2, 2, 3)); + EdgeIteratorState edge = addEdge(0, 1, true).setWayGeometry(createPointList(2, 2, 2, 3)); + edge.setDistance(DIST_EARTH.calcDistance(edge.fetchWayGeometry(FetchMode.ALL))); init(); // pillar nodes / geometry are important to decide on which side of the road a location is. @@ -302,11 +303,14 @@ private void addNode(int nodeId, double lat, double lon) { } private EdgeIteratorState addEdge(int from, int to, boolean bothDirections) { - return GHUtility.setSpeed(60, true, bothDirections, accessEnc, speedEnc, graph.edge(from, to).setDistance(1)); + EdgeIteratorState edge = GHUtility.setSpeed(60, true, bothDirections, accessEnc, speedEnc, graph.edge(from, to).setDistance(100)); + updateDistancesFor(graph, from, graph.getNodeAccess().getLat(from), graph.getNodeAccess().getLon(from)); + updateDistancesFor(graph, to, graph.getNodeAccess().getLat(to), graph.getNodeAccess().getLon(to)); + return edge; } private void init() { - locationIndex = new LocationIndexTree(graph, new RAMDirectory()); + locationIndex = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); locationIndex.prepareIndex(); } diff --git a/core/src/test/java/com/graphhopper/routing/DirectionResolverTest.java b/core/src/test/java/com/graphhopper/routing/DirectionResolverTest.java index a0a4df3d21c..e76a1ca83dc 100644 --- a/core/src/test/java/com/graphhopper/routing/DirectionResolverTest.java +++ b/core/src/test/java/com/graphhopper/routing/DirectionResolverTest.java @@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test; import static com.graphhopper.routing.DirectionResolverResult.*; -import static com.graphhopper.util.Helper.createPointList; import static org.junit.jupiter.api.Assertions.assertEquals; /** @@ -326,7 +325,7 @@ private void addNode(int nodeId, double lat, double lon) { } private EdgeIteratorState addEdge(int from, int to, boolean bothDirections) { - return GHUtility.setSpeed(60, true, bothDirections, accessEnc, speedEnc, graph.edge(from, to).setDistance(1)); + return GHUtility.setSpeed(60, true, bothDirections, accessEnc, speedEnc, graph.edge(from, to).setDistance(100)); } private boolean isAccessible(EdgeIteratorState edge, boolean reverse) { diff --git a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java index 9ec43c85db3..e6027f2c7ff 100644 --- a/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java +++ b/core/src/test/java/com/graphhopper/routing/EdgeBasedRoutingAlgorithmTest.java @@ -30,7 +30,7 @@ import com.graphhopper.storage.Graph; import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.GHUtility; -import com.graphhopper.util.Helper; +import com.graphhopper.util.RandomGraph; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -79,16 +79,16 @@ public Stream provideArguments(ExtensionContext context) { // | | | // 5--6--7 private void initGraph(Graph graph) { - graph.edge(0, 1).setDistance(30).set(speedEnc, 10, 10); - graph.edge(0, 2).setDistance(10).set(speedEnc, 10, 10); - graph.edge(1, 3).setDistance(10).set(speedEnc, 10, 10); - graph.edge(2, 3).setDistance(10).set(speedEnc, 10, 10); - graph.edge(3, 4).setDistance(10).set(speedEnc, 10, 10); - graph.edge(2, 5).setDistance(5).set(speedEnc, 10, 10); - graph.edge(3, 6).setDistance(10).set(speedEnc, 10, 10); - graph.edge(4, 7).setDistance(10).set(speedEnc, 10, 10); - graph.edge(5, 6).setDistance(10).set(speedEnc, 10, 10); - graph.edge(6, 7).setDistance(10).set(speedEnc, 10, 10); + graph.edge(0, 1).setDistance(300).set(speedEnc, 10, 10); + graph.edge(0, 2).setDistance(100).set(speedEnc, 10, 10); + graph.edge(1, 3).setDistance(100).set(speedEnc, 10, 10); + graph.edge(2, 3).setDistance(100).set(speedEnc, 10, 10); + graph.edge(3, 4).setDistance(100).set(speedEnc, 10, 10); + graph.edge(2, 5).setDistance(50).set(speedEnc, 10, 10); + graph.edge(3, 6).setDistance(100).set(speedEnc, 10, 10); + graph.edge(4, 7).setDistance(100).set(speedEnc, 10, 10); + graph.edge(5, 6).setDistance(100).set(speedEnc, 10, 10); + graph.edge(6, 7).setDistance(100).set(speedEnc, 10, 10); } private EncodingManager createEncodingManager(boolean restrictedOnly) { @@ -150,17 +150,26 @@ private Weighting createWeighting(double uTurnCosts) { @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void testRandomGraph(String algoStr) { + run_testRandomGraph(algoStr, false); + } + + @ParameterizedTest + @ArgumentsSource(FixtureProvider.class) + public void testRandomGraph_strict(String algoStr) { + run_testRandomGraph(algoStr, true); + } + + private void run_testRandomGraph(String algoStr, boolean tree) { long seed = System.nanoTime(); final int numQueries = 100; Random rnd = new Random(seed); EncodingManager em = createEncodingManager(false); BaseGraph g = createStorage(em); - GHUtility.buildRandomGraph(g, rnd, 50, 2.2, true, speedEnc, null, 0.8, 0.8); + RandomGraph.start().seed(seed).nodes(50).curviness(0.1).speedZero(tree ? 0 : 0.1).tree(tree).fill(g, speedEnc); GHUtility.addRandomTurnCosts(g, seed, null, turnCostEnc, 3, tcs); g.freeze(); int numPathsNotFound = 0; // todo: reduce redundancy with RandomCHRoutingTest - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { int from = rnd.nextInt(g.getNodes()); int to = rnd.nextInt(g.getNodes()); @@ -177,27 +186,12 @@ public void testRandomGraph(String algoStr) { if (!path.isFound()) { fail("path not found for " + from + "->" + to + ", expected weight: " + refWeight); } - - double weight = path.getWeight(); - if (Math.abs(refWeight - weight) > 1.e-2) { - LOGGER.warn("expected: " + refPath.calcNodes()); - LOGGER.warn("given: " + path.calcNodes()); - fail("wrong weight: " + from + "->" + to + ", dijkstra: " + refWeight + " vs. " + algoStr + ": " + path.getWeight()); - } - if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) { - strictViolations.add("wrong distance " + from + "->" + to + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance()); - } - if (Math.abs(path.getTime() - refPath.getTime()) > 50) { - strictViolations.add("wrong time " + from + "->" + to + ", expected: " + refPath.getTime() + ", given: " + path.getTime()); - } + List strictViolations = GHUtility.comparePaths(refPath, path, from, to, false, seed); + if (tree && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } - if (numPathsNotFound > 0.9 * numQueries) { + if (numPathsNotFound > 0.9 * numQueries) fail("Too many paths not found: " + numPathsNotFound + "/" + numQueries); - } - if (strictViolations.size() > 0.05 * numQueries) { - fail("Too many strict violations: " + strictViolations.size() + "/" + numQueries + "\n" + - String.join("\n", strictViolations)); - } } @ParameterizedTest @@ -233,8 +227,8 @@ public void testTurnCosts_timeCalculation(String algoStr) { { // simple case where turn cost is encountered during forward search Path p14 = calcPath(graph, 1, 4, algoStr); - assertDistTimeWeight(p14, 3, distance, 6, turnCosts); - assertEquals(20, p14.getWeight(), 1.e-6); + assertEquals(180, p14.getDistance()); + assertEquals(180 + 20, p14.getWeight()); assertEquals(20000, p14.getTime()); } @@ -242,18 +236,12 @@ public void testTurnCosts_timeCalculation(String algoStr) { // this test is more involved for bidir algos: the turn costs have to be taken into account also at the // node where fwd and bwd searches meet Path p04 = calcPath(graph, 0, 4, algoStr); - assertDistTimeWeight(p04, 4, distance, 6, turnCosts); - assertEquals(26, p04.getWeight(), 1.e-6); + assertEquals(240, p04.getDistance()); + assertEquals(240 + 20, p04.getWeight()); assertEquals(26000, p04.getTime()); } } - private void assertDistTimeWeight(Path path, int numEdges, double distPerEdge, double weightPerEdge, int turnCost) { - assertEquals(numEdges * distPerEdge, path.getDistance(), 1.e-6, "wrong distance"); - assertEquals(numEdges * weightPerEdge + turnCost, path.getWeight(), 1.e-6, "wrong weight"); - assertEquals(1000 * (numEdges * weightPerEdge + turnCost), path.getTime(), 1.e-6, "wrong time"); - } - private void blockNode3(BaseGraph g) { // Totally block this node (all 9 turn restrictions) setTurnRestriction(g, 2, 3, 1); @@ -293,7 +281,7 @@ public void testUTurns(String algoStr) { initGraph(g); // force u-turn at node 3 by using finite u-turn costs - getEdge(g, 3, 6).setDistance(1); + getEdge(g, 3, 6).setDistance(10); getEdge(g, 3, 2).setDistance(8640); getEdge(g, 1, 0).setDistance(8640); @@ -302,23 +290,23 @@ public void testUTurns(String algoStr) { Path p = createAlgo(g, createWeighting(50), algoStr, EDGE_BASED).calcPath(7, 5); assertEquals(IntArrayList.from(7, 6, 3, 6, 5), p.calcNodes()); - assertEquals(20 + 2, p.getDistance(), 1.e-6); - assertEquals(2.2 + 50, p.getWeight(), 1.e-6); - assertEquals((2.2 + 50) * 1000, p.getTime(), 1.e-6); + assertEquals(200 + 20, p.getDistance(), 1.e-6); + assertEquals(220 + 500, p.getWeight()); + assertEquals((22 + 50) * 1000, p.getTime(), 1.e-6); // with default infinite u-turn costs we need to take an expensive detour p = calcPath(g, 7, 5, algoStr); assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes()); - assertEquals(10 + 1 + 8640 + 5, p.getDistance(), 1.e-6); - assertEquals(865.6, p.getWeight(), 1.e-6); + assertEquals(100 + 10 + 8640 + 50, p.getDistance(), 1.e-6); + assertEquals(8800.0, p.getWeight()); // no more u-turn 6-3-6 -> now we have to take the expensive roads even with finite u-turn costs setTurnRestriction(g, 6, 3, 6); p = createAlgo(g, createWeighting(1000), algoStr, EDGE_BASED).calcPath(7, 5); assertEquals(IntArrayList.from(7, 6, 3, 2, 5), p.calcNodes()); - assertEquals(10 + 1 + 8640 + 5, p.getDistance(), 1.e-6); - assertEquals(865.6, p.getWeight(), 1.e-6); + assertEquals(100 + 10 + 8640 + 50, p.getDistance(), 1.e-6); + assertEquals(8800.0, p.getWeight()); } @ParameterizedTest @@ -348,7 +336,7 @@ public void uTurnCostAtMeetingNode(String algoStr) { { Path path = createAlgo(g, createWeighting(67), algoStr, EDGE_BASED).calcPath(0, 5); assertEquals(600, path.getDistance(), 1.e-6); - assertEquals(60 + 67, path.getWeight(), 1.e-6); + assertEquals(600 + 670, path.getWeight()); assertEquals((60 + 67) * 1000, path.getTime(), 1.e-6); } } @@ -380,20 +368,20 @@ public void testTurnCostsBug_991(String algoStr) { // 2--3--4 // | | | // 5--6--7 - g.edge(0, 1).setDistance(3).set(speedEnc, 10, 10); - g.edge(0, 2).setDistance(1).set(speedEnc, 10, 10); - g.edge(1, 3).setDistance(1).set(speedEnc, 10, 10); - g.edge(2, 3).setDistance(1).set(speedEnc, 10, 10); - g.edge(3, 4).setDistance(1).set(speedEnc, 10, 10); - g.edge(2, 5).setDistance(0.5).set(speedEnc, 10, 10); - g.edge(3, 6).setDistance(1).set(speedEnc, 10, 10); - g.edge(4, 7).setDistance(1).set(speedEnc, 10, 10); - g.edge(5, 6).setDistance(1).set(speedEnc, 10, 10); - g.edge(6, 7).setDistance(1).set(speedEnc, 10, 10); - - setTurnCost(g, 2, 5, 2, 3); - setTurnCost(g, 2, 2, 0, 1); - setTurnCost(g, 2, 5, 6, 3); + g.edge(0, 1).setDistance(300).set(speedEnc, 10, 10); + g.edge(0, 2).setDistance(100).set(speedEnc, 10, 10); + g.edge(1, 3).setDistance(100).set(speedEnc, 10, 10); + g.edge(2, 3).setDistance(100).set(speedEnc, 10, 10); + g.edge(3, 4).setDistance(100).set(speedEnc, 10, 10); + g.edge(2, 5).setDistance(50).set(speedEnc, 10, 10); + g.edge(3, 6).setDistance(100).set(speedEnc, 10, 10); + g.edge(4, 7).setDistance(100).set(speedEnc, 10, 10); + g.edge(5, 6).setDistance(100).set(speedEnc, 10, 10); + g.edge(6, 7).setDistance(100).set(speedEnc, 10, 10); + + setTurnRestriction(g, 5, 2, 3); + setTurnRestriction(g, 5, 6, 3); + setTurnRestriction(g, 2, 0, 1); setTurnCost(g, 1, 6, 7, 4); SpeedWeighting weighting = new SpeedWeighting(speedEnc, turnCostEnc, tcs, Double.POSITIVE_INFINITY) { @@ -408,8 +396,8 @@ public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) { }; Path p = createAlgo(g, weighting, algoStr, EDGE_BASED).calcPath(5, 1); assertEquals(IntArrayList.from(5, 6, 7, 4, 3, 1), p.calcNodes()); - assertEquals(5 * 0.1 + 1, p.getWeight(), 1.e-6); - assertEquals(1500, p.getTime(), .1); + assertEquals(5 * 100 + 10, p.getWeight()); + assertEquals(5 * 10000 + 1000, p.getTime(), .1); } private void setTurnRestriction(BaseGraph g, int from, int via, int to) { diff --git a/core/src/test/java/com/graphhopper/routing/HeadingResolverTest.java b/core/src/test/java/com/graphhopper/routing/HeadingResolverTest.java index 8689b05e2a6..ed0d64f982e 100644 --- a/core/src/test/java/com/graphhopper/routing/HeadingResolverTest.java +++ b/core/src/test/java/com/graphhopper/routing/HeadingResolverTest.java @@ -34,6 +34,7 @@ import com.graphhopper.util.Helper; import org.junit.jupiter.api.Test; +import static com.graphhopper.util.GHUtility.updateDistancesFor; import static org.junit.jupiter.api.Assertions.assertEquals; class HeadingResolverTest { @@ -116,11 +117,10 @@ public void withQueryGraph() { DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - NodeAccess na = graph.getNodeAccess(); - na.setNode(0, 48.8611, 1.2194); - na.setNode(1, 48.8538, 2.3950); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(10)); + EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(0)); + updateDistancesFor(graph, 0, 48.8611, 1.2194); + updateDistancesFor(graph, 1, 48.8538, 2.3950); Snap snap = createSnap(edge, 48.859, 2.00, 0); QueryGraph queryGraph = QueryGraph.create(graph, snap); HeadingResolver resolver = new HeadingResolver(queryGraph); @@ -143,4 +143,4 @@ private Snap createSnap(EdgeIteratorState closestEdge, double lat, double lon, i return snap; } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java index 4640500d731..6bc6191db59 100644 --- a/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/HeadingRoutingTest.java @@ -18,16 +18,14 @@ package com.graphhopper.routing; +import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.ResponsePath; import com.graphhopper.config.Profile; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.NodeAccess; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.GHUtility; @@ -56,7 +54,13 @@ public void headingTest1() { // Test enforce start direction BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -72,7 +76,7 @@ public void headingTest1() { setPathDetails(Collections.singletonList("edge_key")); GHResponse response = router.route(req); assertFalse(response.hasErrors(), response.getErrors().toString()); - assertArrayEquals(new int[]{4, 5, 8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(4, 5, 8, 3, 2), calcNodes(graph, response.getAll().get(0))); } @Test @@ -80,7 +84,13 @@ public void headingTest2() { // Test enforce south start direction and east end direction BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -95,20 +105,26 @@ public void headingTest2() { setPathDetails(Collections.singletonList("edge_key")); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{4, 5, 8, 1, 2, 3}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(4, 5, 8, 1, 2, 3), calcNodes(graph, response.getAll().get(0))); // Test uni-directional case req.setAlgorithm(DIJKSTRA); response = router.route(req); assertFalse(response.hasErrors(), response.getErrors().toString()); - assertArrayEquals(new int[]{4, 5, 8, 1, 2, 3}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(4, 5, 8, 1, 2, 3), calcNodes(graph, response.getAll().get(0))); } @Test public void headingTest3() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -126,7 +142,7 @@ public void headingTest3() { setPathDetails(Collections.singletonList("edge_key")); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{4, 5, 6, 7, 7, 8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(4, 5, 6, 7, 7, 8, 3, 2), calcNodes(graph, response.getAll().get(0))); } @Test @@ -134,7 +150,13 @@ public void headingTest4() { // Test straight via routing BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -152,7 +174,7 @@ public void headingTest4() { GHResponse response = router.route(req); assertFalse(response.hasErrors()); assertEquals(1, response.getAll().size()); - assertArrayEquals(new int[]{5, 4, 3, 3, 8, 1, 2, 3}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(5, 4, 3, 3, 8, 1, 2, 3), calcNodes(graph, response.getAll().get(0))); } @Test @@ -160,7 +182,13 @@ public void headingTest5() { // Test independence of previous enforcement for subsequent paths BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -178,14 +206,20 @@ public void headingTest5() { req.putHint(Parameters.Routing.PASS_THROUGH, true); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{5, 4, 3, 8, 7, 7, 6, 5, 4, 3, 2}, calcNodes(graph, response.getBest())); + assertEquals(IntArrayList.from(5, 4, 3, 8, 7, 7, 6, 5, 4, 3, 2), calcNodes(graph, response.getBest())); } @Test public void testHeadingWithSnapFilter() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraphWithTunnel(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); // Start at 8 (slightly north to make it independent on some edge ordering and always use 8-3 or 3-8 as fallback) @@ -201,7 +235,7 @@ public void testHeadingWithSnapFilter() { req.putHint(Parameters.Routing.PASS_THROUGH, true); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(8, 3, 2), calcNodes(graph, response.getAll().get(0))); // same start + end but heading=0, parallel to 3-8-7 req = new GHRequest(). @@ -212,7 +246,7 @@ public void testHeadingWithSnapFilter() { req.putHint(Parameters.Routing.PASS_THROUGH, true); response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(8, 3, 2), calcNodes(graph, response.getAll().get(0))); // heading=90 parallel to 1->5 req = new GHRequest(). @@ -223,7 +257,7 @@ public void testHeadingWithSnapFilter() { req.putHint(Parameters.Routing.PASS_THROUGH, true); response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{1, 5, 4, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(1, 5, 4, 3, 2), calcNodes(graph, response.getAll().get(0))); for (double angle = 0; angle < 360; angle += 10) { // Ignore angles nearly parallel to 1->5. I.e. it should fallback to results with 8-3.. or 3-8.. @@ -238,9 +272,9 @@ public void testHeadingWithSnapFilter() { response = router.route(req); assertFalse(response.hasErrors()); - int[] expectedNodes = (angle >= 130 && angle <= 250) ? new int[]{3, 8, 7, 0, 1, 2, 3} : new int[]{8, 3, 2}; + IntArrayList expectedNodes = (angle >= 130 && angle <= 250) ? IntArrayList.from(3, 8, 7, 0, 1, 2, 3) : IntArrayList.from(8, 3, 2); // System.out.println(Arrays.toString(calcNodes(graph, response.getAll().get(0))) + " angle:" + angle); - assertArrayEquals(expectedNodes, calcNodes(graph, response.getAll().get(0)), "angle: " + angle); + assertEquals(expectedNodes, calcNodes(graph, response.getAll().get(0)), "angle: " + angle); } } @@ -248,7 +282,13 @@ public void testHeadingWithSnapFilter() { public void testHeadingWithSnapFilter2() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraphWithTunnel(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); // Start at 8 (slightly east to snap to edge 1->5 per default) @@ -264,7 +304,7 @@ public void testHeadingWithSnapFilter2() { req.putHint(Parameters.Routing.PASS_THROUGH, true); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(8, 3, 2), calcNodes(graph, response.getAll().get(0))); req = new GHRequest(). setPoints(Arrays.asList(start, end)). @@ -274,7 +314,7 @@ public void testHeadingWithSnapFilter2() { req.putHint(Parameters.Routing.PASS_THROUGH, true); response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{8, 3, 2}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(8, 3, 2), calcNodes(graph, response.getAll().get(0))); } @Test @@ -282,7 +322,13 @@ public void headingTest6() { // Test if snaps at tower nodes are ignored BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("profile")).build(); + EncodingManager encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc) + .add(RoadClass.create()) + .add(RoadClassLink.create()) + .add(RoadEnvironment.create()) + .add(Roundabout.create()) + .add(MaxSpeed.create()) + .add(Subnetwork.create("profile")).build(); BaseGraph graph = createSquareGraph(encodingManager, accessEnc, speedEnc); Router router = createRouter(graph, encodingManager); @@ -298,14 +344,14 @@ public void headingTest6() { setPathDetails(Collections.singletonList("edge_key")); GHResponse response = router.route(req); assertFalse(response.hasErrors()); - assertArrayEquals(new int[]{0, 1, 2, 3, 4}, calcNodes(graph, response.getAll().get(0))); + assertEquals(IntArrayList.from(0, 1, 2, 3, 4), calcNodes(graph, response.getAll().get(0))); } private Router createRouter(BaseGraph graph, EncodingManager encodingManager) { - LocationIndexTree locationIndex = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree locationIndex = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); locationIndex.prepareIndex(); Map profilesByName = new HashMap<>(); - profilesByName.put("profile", new Profile("profile").setVehicle("car")); + profilesByName.put("profile", TestProfiles.accessAndSpeed("profile", "car")); return new Router(graph.getBaseGraph(), encodingManager, locationIndex, profilesByName, new PathDetailsBuilderFactory(), new TranslationMap().doImport(), new RouterConfig(), new DefaultWeightingFactory(graph.getBaseGraph(), encodingManager), Collections.emptyMap(), Collections.emptyMap()); } @@ -379,7 +425,7 @@ private BaseGraph createSquareGraphWithTunnel(EncodingManager encodingManager, B return g; } - private int[] calcNodes(Graph graph, ResponsePath responsePath) { + private IntArrayList calcNodes(Graph graph, ResponsePath responsePath) { List edgeKeys = responsePath.getPathDetails().get("edge_key"); int[] result = new int[edgeKeys.size() + 1]; for (int i = 0; i < edgeKeys.size(); i++) { @@ -388,6 +434,6 @@ private int[] calcNodes(Graph graph, ResponsePath responsePath) { // last entry needs an additional node: if (i == edgeKeys.size() - 1) result[edgeKeys.size()] = edgeIteratorState.getAdjNode(); } - return result; + return IntArrayList.from(result); } } diff --git a/core/src/test/java/com/graphhopper/routing/PathTest.java b/core/src/test/java/com/graphhopper/routing/PathTest.java index 1ee48734d3c..623e7976794 100644 --- a/core/src/test/java/com/graphhopper/routing/PathTest.java +++ b/core/src/test/java/com/graphhopper/routing/PathTest.java @@ -17,12 +17,16 @@ */ package com.graphhopper.routing; +import com.carrotsearch.hppc.IntArrayList; +import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.util.AllEdgesIterator; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.util.parsers.OrientationCalculator; +import com.graphhopper.routing.weighting.SpeedWeighting; +import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.Graph; import com.graphhopper.storage.NodeAccess; @@ -34,8 +38,7 @@ import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.storage.AbstractGraphStorageTester.assertPList; import static com.graphhopper.util.Parameters.Details.*; import static org.junit.jupiter.api.Assertions.*; @@ -44,15 +47,23 @@ * @author Peter Karich */ public class PathTest { - private final BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("access", true); - private final DecimalEncodedValue carAvSpeedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - private final EncodingManager carManager = EncodingManager.start().add(carAccessEnc).add(carAvSpeedEnc).build(); - private final BooleanEncodedValue mixedCarAccessEnc = new SimpleBooleanEncodedValue("mixed_car_access", true); - private final DecimalEncodedValue mixedCarSpeedEnc = new DecimalEncodedValueImpl("mixed_car_speed", 5, 5, false); - private final BooleanEncodedValue mixedFootAccessEnc = new SimpleBooleanEncodedValue("mixed_foot_access", true); - private final DecimalEncodedValue mixedFootSpeedEnc = new DecimalEncodedValueImpl("mixed_foot_speed", 4, 1, false); - private final EncodingManager mixedEncodingManager = EncodingManager.start().add(mixedCarAccessEnc). - add(mixedCarSpeedEnc).add(mixedFootAccessEnc).add(mixedFootSpeedEnc).build(); + private final DecimalEncodedValue carAvSpeedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); + private final EncodingManager carManager = EncodingManager.start().add(carAvSpeedEnc). + add(Orientation.create()).add(VehicleAccess.create("car")).add(Roundabout.create()). + add(RoadClass.create()).add(RoadEnvironment.create()).add(RoadClassLink.create()). + add(MaxSpeed.create()).build(); + + private final DecimalEncodedValue mixedCarSpeedEnc = new DecimalEncodedValueImpl("mixed_car_speed", 5, 5, true); + private final BooleanEncodedValue mixedCarAccessEnc = VehicleAccess.create("car"); + private final DecimalEncodedValue mixedFootSpeedEnc = new DecimalEncodedValueImpl("mixed_foot_speed", 4, 1, true); + private final EncodingManager mixedEncodingManager = EncodingManager.start(). + add(mixedCarAccessEnc). + add(mixedCarSpeedEnc).add(mixedFootSpeedEnc). + add(RoadClass.create()). + add(RoadEnvironment.create()). + add(RoadClassLink.create()). + add(MaxSpeed.create()). + add(Roundabout.create()).build(); private final TranslationMap trMap = TranslationMapTest.SINGLETON; private final Translation tr = trMap.getWithFallBack(Locale.US); private final RoundaboutGraph roundaboutGraph = new RoundaboutGraph(); @@ -75,21 +86,21 @@ public void testWayList() { na.setNode(1, 1.0, 0.1); na.setNode(2, 2.0, 0.1); - EdgeIteratorState edge1 = g.edge(0, 1).setDistance(1000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 10.0); + EdgeIteratorState edge1 = g.edge(0, 1).setDistance(1000).set(carAvSpeedEnc, 10.0, 10.0); edge1.setWayGeometry(Helper.createPointList(8, 1, 9, 1)); - EdgeIteratorState edge2 = g.edge(2, 1).setDistance(2000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + EdgeIteratorState edge2 = g.edge(2, 1).setDistance(2000).set(carAvSpeedEnc, 50.0, 50.0); edge2.setWayGeometry(Helper.createPointList(11, 1, 10, 1)); SPTEntry e1 = new SPTEntry(edge2.getEdge(), 2, 1, new SPTEntry(edge1.getEdge(), 1, 1, new SPTEntry(0, 1))); - Weighting weighting = CustomModelParser.createFastestWeighting(carAccessEnc, carAvSpeedEnc, carManager); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path path = extractPath(g, weighting, e1); // 0-1-2 assertPList(Helper.createPointList(0, 0.1, 8, 1, 9, 1, 1, 0.1, 10, 1, 11, 1, 2, 0.1), path.calcPoints()); InstructionList instr = InstructionsFromEdges.calcInstructions(path, path.graph, weighting, carManager, tr); Instruction tmp = instr.get(0); assertEquals(3000.0, tmp.getDistance(), 0.0); - assertEquals(504000L, tmp.getTime()); + assertEquals(140000L, tmp.getTime()); assertEquals("continue", tmp.getTurnDescription(tr)); assertEquals(6, tmp.getLength()); @@ -106,9 +117,9 @@ public void testWayList() { assertEquals(path.calcPoints().size() - 1, acc); // force minor change for instructions - edge2.setKeyValues(createKV(STREET_NAME, "2")); + edge2.setKeyValues(Map.of(STREET_NAME, new KValue("2"))); na.setNode(3, 1.0, 1.0); - g.edge(1, 3).setDistance(1000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 10.0); + g.edge(1, 3).setDistance(1000).set(carAvSpeedEnc, 10.0, 10.0); e1 = new SPTEntry(edge2.getEdge(), 2, 1, new SPTEntry(edge1.getEdge(), 1, 1, @@ -120,13 +131,13 @@ public void testWayList() { tmp = instr.get(0); assertEquals(1000.0, tmp.getDistance(), 0); - assertEquals(360000L, tmp.getTime()); + assertEquals(100000L, tmp.getTime()); assertEquals("continue", tmp.getTurnDescription(tr)); assertEquals(3, tmp.getLength()); tmp = instr.get(1); assertEquals(2000.0, tmp.getDistance(), 0); - assertEquals(144000L, tmp.getTime()); + assertEquals(40000L, tmp.getTime()); assertEquals("turn sharp right onto 2", tmp.getTurnDescription(tr)); assertEquals(3, tmp.getLength()); acc = 0; @@ -144,13 +155,13 @@ public void testWayList() { instr = InstructionsFromEdges.calcInstructions(path, path.graph, weighting, carManager, tr); tmp = instr.get(0); assertEquals(2000.0, tmp.getDistance(), 0); - assertEquals(144000L, tmp.getTime()); + assertEquals(40000L, tmp.getTime()); assertEquals("continue onto 2", tmp.getTurnDescription(tr)); assertEquals(3, tmp.getLength()); tmp = instr.get(1); assertEquals(1000.0, tmp.getDistance(), 0); - assertEquals(360000L, tmp.getTime()); + assertEquals(100000L, tmp.getTime()); assertEquals("turn sharp left", tmp.getTurnDescription(tr)); assertEquals(3, tmp.getLength()); acc = 0; @@ -171,22 +182,22 @@ public void testFindInstruction() { na.setNode(4, 7.5, 0.25); na.setNode(5, 5.0, 1.0); - EdgeIteratorState edge1 = g.edge(0, 1).setDistance(1000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + EdgeIteratorState edge1 = g.edge(0, 1).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge1.setWayGeometry(Helper.createPointList()); - edge1.setKeyValues(createKV(STREET_NAME, "Street 1")); - EdgeIteratorState edge2 = g.edge(1, 2).setDistance(1000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + edge1.setKeyValues(Map.of(STREET_NAME, new KValue("Street 1"))); + EdgeIteratorState edge2 = g.edge(1, 2).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge2.setWayGeometry(Helper.createPointList()); - edge2.setKeyValues(createKV(STREET_NAME, "Street 2")); - EdgeIteratorState edge3 = g.edge(2, 3).setDistance(1000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + edge2.setKeyValues(Map.of(STREET_NAME, new KValue("Street 2"))); + EdgeIteratorState edge3 = g.edge(2, 3).setDistance(1000).set(carAvSpeedEnc, 50.0, 50.0); edge3.setWayGeometry(Helper.createPointList()); - edge3.setKeyValues(createKV(STREET_NAME, "Street 3")); - EdgeIteratorState edge4 = g.edge(3, 4).setDistance(500).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + edge3.setKeyValues(Map.of(STREET_NAME, new KValue("Street 3"))); + EdgeIteratorState edge4 = g.edge(3, 4).setDistance(500).set(carAvSpeedEnc, 50.0, 50.0); edge4.setWayGeometry(Helper.createPointList()); - edge4.setKeyValues(createKV(STREET_NAME, "Street 4")); + edge4.setKeyValues(Map.of(STREET_NAME, new KValue("Street 4"))); - g.edge(1, 5).setDistance(10000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); - g.edge(2, 5).setDistance(10000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); - g.edge(3, 5).setDistance(100000).set(carAccessEnc, true, true).set(carAvSpeedEnc, 50.0); + g.edge(1, 5).setDistance(10000).set(carAvSpeedEnc, 50.0, 50.0); + g.edge(2, 5).setDistance(10000).set(carAvSpeedEnc, 50.0, 50.0); + g.edge(3, 5).setDistance(100000).set(carAvSpeedEnc, 50.0, 50.0); SPTEntry e1 = new SPTEntry(edge4.getEdge(), 4, 1, @@ -195,7 +206,7 @@ public void testFindInstruction() { new SPTEntry(edge1.getEdge(), 1, 1, new SPTEntry(0, 1) )))); - Weighting weighting = CustomModelParser.createFastestWeighting(carAccessEnc, carAvSpeedEnc, carManager); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path path = extractPath(g, weighting, e1); InstructionList il = InstructionsFromEdges.calcInstructions(path, path.graph, weighting, carManager, tr); @@ -212,12 +223,12 @@ public void testFindInstruction() { */ @Test void testCalcInstructionsRoundabout() { - calcInstructionsRoundabout(mixedCarAccessEnc, mixedCarSpeedEnc); - calcInstructionsRoundabout(mixedFootAccessEnc, mixedFootSpeedEnc); + calcInstructionsRoundabout(mixedCarSpeedEnc); + calcInstructionsRoundabout(mixedFootSpeedEnc); } - public void calcInstructionsRoundabout(BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc) { - Weighting weighting = new ShortestWeighting(accessEnc, speedEnc); + public void calcInstructionsRoundabout(DecimalEncodedValue speedEnc) { + Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 8); assertTrue(p.isFound()); @@ -225,8 +236,8 @@ public void calcInstructionsRoundabout(BooleanEncodedValue accessEnc, DecimalEnc InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); // Test instructions List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("continue onto MainStreet 1 2", - "At roundabout, take exit 3 onto 5-8", + assertEquals(List.of("continue onto MainStreet 1 2", + "at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -239,8 +250,8 @@ public void calcInstructionsRoundabout(BooleanEncodedValue accessEnc, DecimalEnc calcPath(1, 7); wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("continue onto MainStreet 1 2", - "At roundabout, take exit 2 onto MainStreet 4 7", + assertEquals(List.of("continue onto MainStreet 1 2", + "at roundabout, take exit 2 onto MainStreet 4 7", "arrive at destination"), tmpList); // Test Radian @@ -251,13 +262,13 @@ public void calcInstructionsRoundabout(BooleanEncodedValue accessEnc, DecimalEnc @Test public void testCalcInstructionsRoundaboutBegin() { - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(2, 8); assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("At roundabout, take exit 3 onto 5-8", + assertEquals(List.of("at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); } @@ -265,14 +276,14 @@ public void testCalcInstructionsRoundaboutBegin() { @Test public void testCalcInstructionsRoundaboutDirectExit() { roundaboutGraph.inverse3to9(); - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(6, 8); assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("continue onto 3-6", - "At roundabout, take exit 3 onto 5-8", + assertEquals(List.of("continue onto 3-6", + "at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); roundaboutGraph.inverse3to9(); @@ -280,21 +291,21 @@ public void testCalcInstructionsRoundaboutDirectExit() { @Test public void testCalcAverageSpeedDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List averageSpeedDetails = details.get(AVERAGE_SPEED); assertEquals(4, averageSpeedDetails.size()); - assertEquals(45.0, averageSpeedDetails.get(0).getValue()); - assertEquals(90.0, averageSpeedDetails.get(1).getValue()); - assertEquals(10.0, averageSpeedDetails.get(2).getValue()); - assertEquals(45.0, averageSpeedDetails.get(3).getValue()); + assertEquals(162.2, (double) averageSpeedDetails.get(0).getValue(), 1.e-3); + assertEquals(327.3, (double) averageSpeedDetails.get(1).getValue(), 1.e-3); + assertEquals(36.0, (double) averageSpeedDetails.get(2).getValue(), 1.e-3); + assertEquals(162.2, (double) averageSpeedDetails.get(3).getValue(), 1.e-3); assertEquals(0, averageSpeedDetails.get(0).getFirst()); assertEquals(1, averageSpeedDetails.get(1).getFirst()); @@ -303,14 +314,123 @@ public void testCalcAverageSpeedDetails() { assertEquals(4, averageSpeedDetails.get(3).getLast()); } + @Test + public void testWeightDetailsDoNotIncludeSpuriousUTurnCosts() { + // Use a weighting with turn costs: u-turn costs = 1000s so they're easy to spot + TurnCostProvider tcp = new TurnCostProvider() { + @Override + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + if (inEdge == outEdge) return 1000; + return 0; + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return (long) (1000 * calcTurnWeight(inEdge, viaNode, outEdge)); + } + }; + Weighting weighting = new SpeedWeighting(carAvSpeedEnc, tcp); + + // Build a graph with turn costs enabled for edge-based traversal + BaseGraph graph = new BaseGraph.Builder(carManager).withTurnCosts(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(1, 52.514, 13.348); + na.setNode(2, 52.514, 13.349); + na.setNode(3, 52.514, 13.350); + na.setNode(4, 52.515, 13.349); + na.setNode(5, 52.516, 13.3452); + graph.edge(1, 2).set(carAvSpeedEnc, 45, 45).setDistance(500); + graph.edge(2, 3).set(carAvSpeedEnc, 90, 90).setDistance(500); + graph.edge(3, 4).set(carAvSpeedEnc, 45, 45).setDistance(500); + graph.edge(4, 5).set(carAvSpeedEnc, 45, 45).setDistance(500); + + Path p = new Dijkstra(graph, weighting, TraversalMode.EDGE_BASED).calcPath(1, 5); + assertTrue(p.isFound()); + + Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, + List.of(WEIGHT), new PathDetailsBuilderFactory(), 0, graph); + + List weightDetails = details.get(WEIGHT); + assertEquals(4, weightDetails.size()); + + // Weight details should not include u-turn costs (edge matching itself). + double totalWeight = 0; + for (PathDetail wd : weightDetails) { + double w = (double) wd.getValue(); + // No single edge in this small graph should have weight >= 1000 (the u-turn cost) + assertTrue(w < 1000, "Weight detail " + w + " includes spurious u-turn cost"); + totalWeight += w; + } + + // The sum of weight details should approximate the path weight + assertEquals(p.getWeight(), totalWeight, 1.e-3); + } + + @Test + public void testWeightDetailsIncludeTurnCostsOnCorrectSegment() { + // Turn cost of 50 (raw) = 500 after SpeedWeighting's 10x scaling, applied at specific transitions + final int TURN_COST_EDGE_0_TO_1 = 50; // turn from edge 0 to edge 1 + TurnCostProvider tcp = new TurnCostProvider() { + @Override + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + if (inEdge == outEdge) return 1000; + // Apply a known turn cost only for the transition from edge 0 to edge 1 + if (inEdge == 0 && outEdge == 1) return TURN_COST_EDGE_0_TO_1; + return 0; + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return (long) (1000 * calcTurnWeight(inEdge, viaNode, outEdge)); + } + }; + Weighting weighting = new SpeedWeighting(carAvSpeedEnc, tcp); + + BaseGraph graph = new BaseGraph.Builder(carManager).withTurnCosts(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(1, 52.514, 13.348); + na.setNode(2, 52.514, 13.349); + na.setNode(3, 52.514, 13.350); + na.setNode(4, 52.515, 13.349); + // All edges same speed/distance so the base edge weight is identical + graph.edge(1, 2).set(carAvSpeedEnc, 45, 45).setDistance(500); // edge 0 + graph.edge(2, 3).set(carAvSpeedEnc, 45, 45).setDistance(500); // edge 1 + graph.edge(3, 4).set(carAvSpeedEnc, 45, 45).setDistance(500); // edge 2 + + // Path: 1 -> 2 -> 3 -> 4, edges: [0, 1, 2] + Path p = new Dijkstra(graph, weighting, TraversalMode.EDGE_BASED).calcPath(1, 4); + assertTrue(p.isFound()); + + Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, + List.of(WEIGHT), new PathDetailsBuilderFactory(), 0, graph); + List weightDetails = details.get(WEIGHT); + assertEquals(3, weightDetails.size()); + + double baseEdgeWeight = (double) weightDetails.get(0).getValue(); + + // First segment (edge 0): no previous edge, so no turn cost — just edge weight + assertEquals(baseEdgeWeight, (double) weightDetails.get(0).getValue(), 1.e-6); + + // Second segment (edge 1): includes turn cost from edge 0 -> edge 1 + double expectedTurnWeight = Weighting.roundWeight(10.0 * TURN_COST_EDGE_0_TO_1); + assertEquals(baseEdgeWeight + expectedTurnWeight, (double) weightDetails.get(1).getValue(), 1.e-6); + + // Third segment (edge 2): transition from edge 1 -> edge 2 has zero turn cost + assertEquals(baseEdgeWeight, (double) weightDetails.get(2).getValue(), 1.e-6); + + // Total weight details should match path weight + double totalDetails = weightDetails.stream().mapToDouble(wd -> (double) wd.getValue()).sum(); + assertEquals(p.getWeight(), totalDetails, 1.e-3); + } + @Test public void testCalcAverageSpeedDetailsWithShortDistances_issue1848() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 6); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List averageSpeedDetails = details.get(AVERAGE_SPEED); assertEquals(4, averageSpeedDetails.size()); @@ -318,8 +438,8 @@ public void testCalcAverageSpeedDetailsWithShortDistances_issue1848() { p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(6, 1); assertTrue(p.isFound()); details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); averageSpeedDetails = details.get(AVERAGE_SPEED); assertEquals(5, averageSpeedDetails.size()); assertNull(averageSpeedDetails.get(0).getValue()); @@ -327,16 +447,16 @@ public void testCalcAverageSpeedDetailsWithShortDistances_issue1848() { @Test public void testCalcStreetNameDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(STREET_NAME), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(STREET_NAME), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List streetNameDetails = details.get(STREET_NAME); - assertTrue(details.size() == 1); + assertEquals(1, details.size()); assertEquals(4, streetNameDetails.size()); assertEquals("1-2", streetNameDetails.get(0).getValue()); @@ -353,13 +473,13 @@ public void testCalcStreetNameDetails() { @Test public void testCalcEdgeIdDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(EDGE_ID), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(EDGE_ID), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List edgeIdDetails = details.get(EDGE_ID); assertEquals(4, edgeIdDetails.size()); @@ -378,12 +498,12 @@ public void testCalcEdgeIdDetails() { @Test public void testCalcEdgeKeyDetailsForward() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(EDGE_KEY), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + List.of(EDGE_KEY), new PathDetailsBuilderFactory(), 0, pathDetailGraph); List edgeKeyDetails = details.get(EDGE_KEY); assertEquals(4, edgeKeyDetails.size()); @@ -395,12 +515,12 @@ public void testCalcEdgeKeyDetailsForward() { @Test public void testCalcEdgeKeyDetailsBackward() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(5, 1); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(EDGE_KEY), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + List.of(EDGE_KEY), new PathDetailsBuilderFactory(), 0, pathDetailGraph); List edgeKeyDetails = details.get(EDGE_KEY); assertEquals(4, edgeKeyDetails.size()); @@ -412,20 +532,21 @@ public void testCalcEdgeKeyDetailsBackward() { @Test public void testCalcTimeDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); + assertEquals(IntArrayList.from(1, 2, 3, 4, 5), p.calcNodes()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(TIME), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(TIME), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List timeDetails = details.get(TIME); assertEquals(4, timeDetails.size()); - assertEquals(400L, timeDetails.get(0).getValue()); - assertEquals(200L, timeDetails.get(1).getValue()); - assertEquals(3600L, timeDetails.get(2).getValue()); - assertEquals(400L, timeDetails.get(3).getValue()); + assertEquals(111L, timeDetails.get(0).getValue()); + assertEquals(55L, timeDetails.get(1).getValue()); + assertEquals(1000L, timeDetails.get(2).getValue()); + assertEquals(111L, timeDetails.get(3).getValue()); assertEquals(0, timeDetails.get(0).getFirst()); assertEquals(1, timeDetails.get(1).getFirst()); @@ -436,13 +557,13 @@ public void testCalcTimeDetails() { @Test public void testCalcDistanceDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(DISTANCE), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(DISTANCE), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List distanceDetails = details.get(DISTANCE); assertEquals(5D, distanceDetails.get(0).getValue()); @@ -453,47 +574,75 @@ public void testCalcDistanceDetails() { @Test public void testCalcIntersectionDetails() { - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); assertTrue(p.isFound()); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(INTERSECTION), new PathDetailsBuilderFactory(), 0, pathDetailGraph); - assertTrue(details.size() == 1); + List.of(INTERSECTION), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); List intersectionDetails = details.get(INTERSECTION); assertEquals(4, intersectionDetails.size()); Map intersectionMap = new HashMap<>(); intersectionMap.put("out", 0); - intersectionMap.put("entries", Arrays.asList(true)); - intersectionMap.put("bearings", Arrays.asList(90)); + intersectionMap.put("entries", List.of(true)); + intersectionMap.put("bearings", List.of(90)); assertEquals(intersectionMap, intersectionDetails.get(0).getValue()); intersectionMap.clear(); intersectionMap.put("out", 0); intersectionMap.put("in", 1); - intersectionMap.put("entries", Arrays.asList(true, false)); - intersectionMap.put("bearings", Arrays.asList(90, 270)); + intersectionMap.put("entries", List.of(true, false)); + intersectionMap.put("bearings", List.of(90, 270)); assertEquals(intersectionMap, intersectionDetails.get(1).getValue()); } + @Test + public void testChangeDetails() { + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); + DecimalEncodedValue orEnc = carManager.getDecimalEncodedValue(Orientation.KEY); + OrientationCalculator calc = new OrientationCalculator(orEnc); + AllEdgesIterator allEdges = pathDetailGraph.getAllEdges(); + EdgeIntAccess intAccess = pathDetailGraph.getBaseGraph().getEdgeAccess(); + while(allEdges.next()) { + ReaderWay way = new ReaderWay(allEdges.getEdge()); + way.setTag("point_list", allEdges.fetchWayGeometry(FetchMode.ALL)); + calc.handleWayTags(allEdges.getEdge(), intAccess, way, null); + } + Path path = new Dijkstra(pathDetailGraph, weighting, TraversalMode.NODE_BASED).calcPath(1, 7); + assertTrue(path.isFound()); + + Map> details = PathDetailsFromEdges.calcDetails(path, carManager, weighting, + List.of(CHANGE_ANGLE), new PathDetailsBuilderFactory(), 0, pathDetailGraph); + assertEquals(1, details.size()); + + List caDetail = details.get(CHANGE_ANGLE); + + assertEquals(4, caDetail.size()); + assertNull(caDetail.get(0).getValue()); + assertEquals(0.0, caDetail.get(1).getValue()); + assertEquals(-132.0, caDetail.get(2).getValue()); + assertEquals(72.0, caDetail.get(3).getValue()); + } + /** * case with one edge being not an exit */ @Test public void testCalcInstructionsRoundabout2() { roundaboutGraph.inverse3to6(); - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 8); assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("continue onto MainStreet 1 2", - "At roundabout, take exit 2 onto 5-8", + assertEquals(List.of("continue onto MainStreet 1 2", + "at roundabout, take exit 2 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -531,40 +680,46 @@ public void testCalcInstructionsRoundaboutIssue353() { na.setNode(10, 52.5135, 13.348); na.setNode(11, 52.514, 13.347); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(2, 1).setDistance(5)).setKeyValues(createKV(STREET_NAME, "MainStreet 2 1")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(1, 11).setDistance(5)).setKeyValues(createKV(STREET_NAME, "MainStreet 1 11")); + graph.edge(2, 1).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("MainStreet 2 1"))); + graph.edge(1, 11).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("MainStreet 1 11"))); // roundabout EdgeIteratorState tmpEdge; - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(3, 9).setDistance(2)).setKeyValues(createKV(STREET_NAME, "3-9")); + tmpEdge = graph.edge(3, 9).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue("3-9"))); BooleanEncodedValue carManagerRoundabout = carManager.getBooleanEncodedValue(Roundabout.KEY); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(9, 10).setDistance(2)).setKeyValues(createKV(STREET_NAME, "9-10")); + tmpEdge = graph.edge(9, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue("9-10"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(6, 10).setDistance(2)).setKeyValues(createKV(STREET_NAME, "6-10")); + tmpEdge = graph.edge(6, 10).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue("6-10"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(10, 1).setDistance(2)).setKeyValues(createKV(STREET_NAME, "10-1")); + tmpEdge = graph.edge(10, 1).set(carAvSpeedEnc, 60, 0).setDistance(2).setKeyValues(Map.of(STREET_NAME, new KValue("10-1"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(3, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "2-3")); + tmpEdge = graph.edge(3, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(4, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "3-4")); + tmpEdge = graph.edge(4, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(5, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "4-5")); + tmpEdge = graph.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); tmpEdge.set(carManagerRoundabout, true); - tmpEdge = GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(2, 5).setDistance(5)).setKeyValues(createKV(STREET_NAME, "5-2")); + tmpEdge = graph.edge(2, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("5-2"))); tmpEdge.set(carManagerRoundabout, true); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(4, 7).setDistance(5)).setKeyValues(createKV(STREET_NAME, "MainStreet 4 7")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(5, 8).setDistance(5)).setKeyValues(createKV(STREET_NAME, "5-8")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(3, 6).setDistance(5)).setKeyValues(createKV(STREET_NAME, "3-6")); + graph.edge(4, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("MainStreet 4 7"))); + graph.edge(5, 8).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("5-8"))); + graph.edge(3, 6).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("3-6"))); + BooleanEncodedValue carAccessEncTmp = carManager.getBooleanEncodedValue(VehicleAccess.key("car")); + AllEdgesIterator iter = graph.getAllEdges(); + while (iter.next()) { + if (iter.get(carAvSpeedEnc) > 0) iter.set(carAccessEncTmp, true); + if (iter.getReverse(carAvSpeedEnc) > 0) iter.setReverse(carAccessEncTmp, true); + } - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) .calcPath(6, 11); assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, carManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("At roundabout, take exit 1 onto MainStreet 1 11", + assertEquals(List.of("at roundabout, take exit 1 onto MainStreet 1 11", "arrive at destination"), tmpList); } @@ -572,14 +727,14 @@ public void testCalcInstructionsRoundaboutIssue353() { @Test public void testCalcInstructionsRoundaboutClockwise() { roundaboutGraph.setRoundabout(true); - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 8); assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(Arrays.asList("continue onto MainStreet 1 2", - "At roundabout, take exit 1 onto 5-8", + assertEquals(List.of("continue onto MainStreet 1 2", + "at roundabout, take exit 1 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -591,7 +746,7 @@ public void testCalcInstructionsRoundaboutClockwise() { @Test public void testCalcInstructionsIgnoreContinue() { // Follow a couple of straight edges, including a name change - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(4, 11); assertTrue(p.isFound()); @@ -604,7 +759,7 @@ public void testCalcInstructionsIgnoreContinue() { @Test public void testCalcInstructionsIgnoreTurnIfNoAlternative() { // The street turns left, but there is not turn - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(10, 12); assertTrue(p.isFound()); @@ -632,11 +787,11 @@ public void testCalcInstructionForForkWithSameName() { na.setNode(3, 48.982611, 13.121012); na.setNode(4, 48.982336, 13.121002); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Regener Weg")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(2, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Regener Weg")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(2, 3).setDistance(5)); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Regener Weg"))); + graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Regener Weg"))); + graph.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) .calcPath(1, 4); assertTrue(p.isFound()); @@ -665,11 +820,11 @@ public void testCalcInstructionForMotorwayFork() { EnumEncodedValue roadClassEnc = carManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BooleanEncodedValue roadClassLinkEnc = carManager.getBooleanEncodedValue(RoadClassLink.KEY); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(2, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(2, 3).setDistance(5)).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, true); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); + graph.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, false); + graph.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).set(roadClassEnc, RoadClass.MOTORWAY).set(roadClassLinkEnc, true); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) .calcPath(1, 4); assertTrue(p.isFound()); @@ -678,6 +833,40 @@ public void testCalcInstructionForMotorwayFork() { assertEquals(2, wayList.size()); } + @Test + public void testFerry() { + final BaseGraph graph = new BaseGraph.Builder(carManager).create(); + final NodeAccess na = graph.getNodeAccess(); + + // 1 ---- 2 ---- 3 ---- 4 + na.setNode(1, 48.909071, 8.647136); + na.setNode(2, 48.909071, 8.647978); + na.setNode(3, 48.909071, 8.648155); + na.setNode(3, 48.909071, 8.648200); + + EnumEncodedValue roadEnvEnc = carManager.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); + + graph.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).set(roadEnvEnc, RoadEnvironment.ROAD). + setKeyValues(Map.of(STREET_NAME, new KValue("A B"))); + graph.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).set(roadEnvEnc, RoadEnvironment.FERRY). + setKeyValues(Map.of(STREET_NAME, new KValue("B C"))); + graph.edge(3, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).set(roadEnvEnc, RoadEnvironment.ROAD). + setKeyValues(Map.of(STREET_NAME, new KValue("C D"))); + + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); + Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) + .calcPath(1, 4); + assertTrue(p.isFound()); + InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, carManager, tr); + assertEquals(4, wayList.size()); + assertEquals("continue onto A B", wayList.get(0).getTurnDescription(tr)); + assertEquals("Attention, take ferry (B C)", wayList.get(1).getTurnDescription(tr)); + assertEquals(Instruction.FERRY, wayList.get(1).getSign()); + assertEquals("leave ferry and turn right onto C D", wayList.get(2).getTurnDescription(tr)); + assertEquals(Instruction.TURN_RIGHT, wayList.get(2).getSign()); + assertEquals("arrive at destination", wayList.get(3).getTurnDescription(tr)); + } + @Test public void testCalcInstructionsEnterMotorway() { final BaseGraph graph = new BaseGraph.Builder(carManager).create(); @@ -693,11 +882,11 @@ public void testCalcInstructionsEnterMotorway() { na.setNode(3, 48.630558, 9.459851); na.setNode(4, 48.63054, 9.459406); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, graph.edge(4, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); + graph.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); + graph.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); + graph.edge(4, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED) .calcPath(4, 3); assertTrue(p.isFound()); @@ -722,11 +911,11 @@ public void testCalcInstructionsMotorwayJunction() { na.setNode(3, 48.706805, 9.162995); na.setNode(4, 48.706705, 9.16329); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, g.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, g.edge(2, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "A 8")); + g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); + g.edge(2, 4).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue("A 8"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 3); assertTrue(p.isFound()); @@ -752,11 +941,11 @@ public void testCalcInstructionsOntoOneway() { na.setNode(3, -33.824415, 151.188177); na.setNode(4, -33.824437, 151.187925); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, g.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Pacific Highway")); - GHUtility.setSpeed(60, true, false, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Pacific Highway")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(4, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Greenwich Road")); + g.edge(1, 2).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue("Pacific Highway"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 0).setKeyValues(Map.of(STREET_NAME, new KValue("Pacific Highway"))); + g.edge(4, 2).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Greenwich Road"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(4, 3); assertTrue(p.isFound()); @@ -787,11 +976,11 @@ public void testCalcInstructionIssue1047() { EnumEncodedValue roadClassEnc = carManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BooleanEncodedValue roadClassLinkEnc = carManager.getBooleanEncodedValue(RoadClassLink.KEY); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "B 156")).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "S 108")).set(roadClassEnc, RoadClass.SECONDARY).set(roadClassLinkEnc, false); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "B 156")).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("B 156"))).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); + g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("S 108"))).set(roadClassEnc, RoadClass.SECONDARY).set(roadClassLinkEnc, false); + g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("B 156"))).set(roadClassEnc, RoadClass.PRIMARY).set(roadClassLinkEnc, false); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 4); assertTrue(p.isFound()); @@ -821,11 +1010,11 @@ public void testCalcInstructionContinueLeavingStreet() { na.setNode(3, 48.982611, 13.121012); na.setNode(4, 48.982565, 13.121002); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Regener Weg")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 4).setDistance(5)); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Regener Weg")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Regener Weg"))); + g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5); + g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Regener Weg"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 4); assertTrue(p.isFound()); @@ -851,11 +1040,11 @@ public void testCalcInstructionSlightTurn() { na.setNode(3, 48.412034, 15.599411); na.setNode(4, 48.411927, 15.599197); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Stöhrgasse")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Stöhrgasse")); + g.edge(1, 2).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Stöhrgasse"))); + g.edge(2, 3).set(carAvSpeedEnc, 60, 60).setDistance(5); + g.edge(2, 4).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Stöhrgasse"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(4, 1); assertTrue(p.isFound()); @@ -884,16 +1073,14 @@ public void testUTurnLeft() { na.setNode(6, 48.402422, 9.996067); na.setNode(7, 48.402604, 9.994962); - GHUtility.setSpeed(60, 0, carAccessEnc, carAvSpeedEnc, - g.edge(1, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")), - g.edge(2, 3).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")), - g.edge(6, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße")), - g.edge(5, 4).setDistance(5).setKeyValues(createKV(STREET_NAME, "Olgastraße"))); - GHUtility.setSpeed(60, 60, carAccessEnc, carAvSpeedEnc, - g.edge(2, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "Neithardtstraße")), - g.edge(5, 7).setDistance(5).setKeyValues(createKV(STREET_NAME, "Neithardtstraße"))); + g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Olgastraße"))); + g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Olgastraße"))); + g.edge(6, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Olgastraße"))); + g.edge(5, 4).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Olgastraße"))); + g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Neithardtstraße"))); + g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Neithardtstraße"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 4); assertTrue(p.isFound()); @@ -922,21 +1109,22 @@ public void testUTurnRight() { na.setNode(6, -33.885692, 151.181445); na.setNode(7, -33.885692, 151.181445); - GHUtility.setSpeed(60, 0, carAccessEnc, carAvSpeedEnc, - g.edge(1, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")), - g.edge(2, 3).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")), - g.edge(4, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road")), - g.edge(5, 6).setDistance(5).setKeyValues(createKV(STREET_NAME, "Parramatta Road"))); - GHUtility.setSpeed(60, 60, carAccessEnc, carAvSpeedEnc, - g.edge(2, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "Larkin Street")), - g.edge(5, 7).setDistance(5).setKeyValues(createKV(STREET_NAME, "Larkin Street"))); + g.edge(1, 2).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Parramatta Road"))); + g.edge(2, 3).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Parramatta Road"))); + g.edge(4, 5).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Parramatta Road"))); + g.edge(5, 6).set(carAvSpeedEnc, 60, 0).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Parramatta Road"))); + g.edge(2, 5).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Larkin Street"))); + g.edge(5, 7).set(carAvSpeedEnc, 60, 60).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("Larkin Street"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 6); assertTrue(p.isFound()); + assertEquals(IntArrayList.from(1, 2, 5, 6), p.calcNodes()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, carManager, tr); + assertEquals(List.of("continue onto Parramatta Road", "make a U-turn onto Parramatta Road", "arrive at destination"), + getTurnDescriptions(wayList)); assertEquals(3, wayList.size()); assertEquals(Instruction.U_TURN_RIGHT, wayList.get(1).getSign()); } @@ -944,7 +1132,7 @@ public void testUTurnRight() { @Test public void testCalcInstructionsForTurn() { // The street turns left, but there is not turn - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(11, 13); assertTrue(p.isFound()); @@ -959,7 +1147,7 @@ public void testCalcInstructionsForTurn() { @Test public void testCalcInstructionsForSlightTurnWithOtherSlightTurn() { // Test for a fork with two slight turns. Since there are two slight turns, show the turn instruction - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(12, 16); assertTrue(p.isFound()); @@ -986,11 +1174,11 @@ public void testCalcInstructionsForSlightTurnOntoDifferentStreet() { na.setNode(3, 48.764149, 8.678926); na.setNode(4, 48.764085, 8.679183); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(1, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Talstraße, K 4313")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Calmbacher Straße, K 4312")); - GHUtility.setSpeed(60, true, true, carAccessEnc, carAvSpeedEnc, g.edge(3, 4).setDistance(5)).setKeyValues(createKV(STREET_NAME, "Calmbacher Straße, K 4312")); + g.edge(1, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Talstraße, new KValue( K 4313"))); + g.edge(2, 3).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Calmbacher Straße, new KValue( K 4312"))); + g.edge(3, 4).setDistance(5).set(carAvSpeedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Calmbacher Straße, new KValue( K 4312"))); - Weighting weighting = new ShortestWeighting(carAccessEnc, carAvSpeedEnc); + Weighting weighting = new SpeedWeighting(carAvSpeedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED) .calcPath(1, 2); assertTrue(p.isFound()); @@ -1002,8 +1190,8 @@ public void testCalcInstructionsForSlightTurnOntoDifferentStreet() { @Test public void testIgnoreInstructionsForSlightTurnWithOtherTurn() { - // Test for a fork with one sligh turn and one actual turn. We are going along the slight turn. No turn instruction needed in this case - Weighting weighting = new ShortestWeighting(mixedCarAccessEnc, mixedCarSpeedEnc); + // Test for a fork with one slight turn and one actual turn. We are going along the slight turn. No turn instruction needed in this case + Weighting weighting = new SpeedWeighting(mixedCarSpeedEnc); Path p = new Dijkstra(roundaboutGraph.g, weighting, TraversalMode.NODE_BASED) .calcPath(16, 19); assertTrue(p.isFound()); @@ -1013,6 +1201,123 @@ public void testIgnoreInstructionsForSlightTurnWithOtherTurn() { assertEquals(2, wayList.size()); } + @Test + public void testFootAndCar_issue3081() { + BooleanEncodedValue carAccessEnc = VehicleAccess.create("car"); + BooleanEncodedValue footAccessEnc = VehicleAccess.create("foot"); + BooleanEncodedValue rdEnc = Roundabout.create(); + EncodingManager manager = EncodingManager.start(). + add(carAccessEnc). + add(footAccessEnc). + add(RoadClass.create()). + add(RoadClassLink.create()). + add(RoadEnvironment.create()). + add(MaxSpeed.create()). + add(rdEnc).build(); + + final BaseGraph g = new BaseGraph.Builder(manager).create(); + final NodeAccess na = g.getNodeAccess(); + + // Actual example is here 45.7742,4.868 (but a few roads left out) + // 0 1 + // \| + // 2<-3<--4 + // / \ + // | 5-->6 + // \ / + // 7--8-->9<--10 + + na.setNode(0, 52.503809, 13.410198); + na.setNode(1, 52.503871, 13.410249); + na.setNode(2, 52.503751, 13.410377); + na.setNode(3, 52.50387, 13.410807); + na.setNode(4, 52.503989, 13.41094); + na.setNode(5, 52.503794, 13.411024); + na.setNode(6, 52.503925, 13.411034); + na.setNode(7, 52.503277, 13.41041); + na.setNode(8, 52.50344, 13.410545); + na.setNode(9, 52.503536, 13.411099); + na.setNode(10, 52.503515, 13.411178); + + g.edge(0, 2).setDistance(5).set(carAccessEnc, true, true).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Nordwest"))); + // edge 1-2 does not exist in real world, but we need it to test a few other situations + g.edge(1, 2).setDistance(5).set(carAccessEnc, false, false).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Nordwest, foot-only"))); + g.edge(4, 3).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Nordeast in"))); + g.edge(5, 6).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Nordeast out"))); + g.edge(10, 9).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Southeast in"))); + g.edge(7, 8).setDistance(5).set(carAccessEnc, true, true).set(footAccessEnc, true, true).setKeyValues(Map.of(STREET_NAME, new KValue("Southwest"))); + + g.edge(3, 2).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, false).set(rdEnc, true).setKeyValues(Map.of(STREET_NAME, new KValue("roundabout"))); + g.edge(5, 3).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, false).set(rdEnc, true).setKeyValues(Map.of(STREET_NAME, new KValue("roundabout"))); + g.edge(9, 5).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, false).set(rdEnc, true).setKeyValues(Map.of(STREET_NAME, new KValue("roundabout"))); + g.edge(8, 9).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, false).set(rdEnc, true).setKeyValues(Map.of(STREET_NAME, new KValue("roundabout"))); + g.edge(2, 8).setDistance(5).set(carAccessEnc, true, false).set(footAccessEnc, true, false).set(rdEnc, true).setKeyValues(Map.of(STREET_NAME, new KValue("roundabout"))); + + Weighting weighting = new AccessWeighting(footAccessEnc); + Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(7, 10); + assertEquals("[7, 8, 9, 10]", p.calcNodes().toString()); + InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, manager, tr); + assertEquals("at roundabout, take exit 1 onto Southeast in", wayList.get(1).getTurnDescription(tr)); + + p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(10, 1); + assertEquals("[10, 9, 5, 3, 2, 1]", p.calcNodes().toString()); + wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, manager, tr); + assertEquals("at roundabout, take exit 2 onto Nordwest, foot-only", wayList.get(1).getTurnDescription(tr)); + + p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(10, 4); + assertEquals("[10, 9, 5, 3, 4]", p.calcNodes().toString()); + wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, manager, tr); + assertEquals("at roundabout, take exit 1 onto Nordeast in", wayList.get(1).getTurnDescription(tr)); + + p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(10, 6); + assertEquals("[10, 9, 5, 6]", p.calcNodes().toString()); + wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, manager, tr); + assertEquals("at roundabout, take exit 1 onto Nordeast out", wayList.get(1).getTurnDescription(tr)); + } + + static class AccessWeighting implements Weighting { + private final BooleanEncodedValue accessEnc; + + public AccessWeighting(BooleanEncodedValue accessEnc) { + this.accessEnc = accessEnc; + } + + @Override + public double calcMinWeightPerDistance() { + throw new IllegalStateException(); + } + + @Override + public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { + return (reverse && edgeState.getReverse(accessEnc) || edgeState.get(accessEnc)) ? 1 : Double.POSITIVE_INFINITY; + } + + @Override + public long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { + return (reverse && edgeState.getReverse(accessEnc) || edgeState.get(accessEnc)) ? 1000 : 0; + } + + @Override + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + return 0; + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return 0; + } + + @Override + public boolean hasTurnCosts() { + return false; + } + + @Override + public String getName() { + return "access"; + } + } + List getTurnDescriptions(InstructionList instructionJson) { List list = new ArrayList<>(); for (Instruction instruction : instructionJson) { @@ -1025,18 +1330,24 @@ private Graph generatePathDetailsGraph() { final BaseGraph graph = new BaseGraph.Builder(carManager).create(); final NodeAccess na = graph.getNodeAccess(); + // 6 -5 \ / 7 + // 4 + // \ + // 1 - 2 - 3 na.setNode(1, 52.514, 13.348); na.setNode(2, 52.514, 13.349); na.setNode(3, 52.514, 13.350); na.setNode(4, 52.515, 13.349); na.setNode(5, 52.516, 13.3452); na.setNode(6, 52.516, 13.344); - - GHUtility.setSpeed(45, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(1, 2).setDistance(5)).setKeyValues(createKV(STREET_NAME, "1-2")); - GHUtility.setSpeed(45, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(4, 5).setDistance(5)).setKeyValues(createKV(STREET_NAME, "4-5")); - GHUtility.setSpeed(90, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(2, 3).setDistance(5)).setKeyValues(createKV(STREET_NAME, "2-3")); - GHUtility.setSpeed(9, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(3, 4).setDistance(10)).setKeyValues(createKV(STREET_NAME, "3-4")); - GHUtility.setSpeed(9, true, true, carAccessEnc, carAvSpeedEnc, graph.edge(5, 6).setDistance(0.001)).setKeyValues(createKV(STREET_NAME, "3-4")); + na.setNode(7, 52.516, 13.350); + + graph.edge(1, 2).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); + graph.edge(4, 5).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); + graph.edge(2, 3).set(carAvSpeedEnc, 90, 90).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); + graph.edge(3, 4).set(carAvSpeedEnc, 9, 9).setDistance(10).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + graph.edge(5, 6).set(carAvSpeedEnc, 9, 9).setDistance(0.001).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + graph.edge(4, 7).set(carAvSpeedEnc, 45, 45).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("4-7"))); return graph; } @@ -1082,20 +1393,20 @@ private RoundaboutGraph() { na.setNode(19, 52.515, 13.368); // roundabout - roundaboutEdges.add(g.edge(3, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "2-3"))); - roundaboutEdges.add(g.edge(4, 3).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-4"))); - roundaboutEdges.add(g.edge(5, 4).setDistance(5).setKeyValues(createKV(STREET_NAME, "4-5"))); - roundaboutEdges.add(g.edge(2, 5).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-2"))); + roundaboutEdges.add(g.edge(3, 2).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("2-3")))); + roundaboutEdges.add(g.edge(4, 3).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("3-4")))); + roundaboutEdges.add(g.edge(5, 4).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("4-5")))); + roundaboutEdges.add(g.edge(2, 5).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("5-2")))); List bothDir = new ArrayList<>(); List oneDir = new ArrayList<>(roundaboutEdges); - bothDir.add(g.edge(1, 2).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 1 2"))); - bothDir.add(g.edge(4, 7).setDistance(5).setKeyValues(createKV(STREET_NAME, "MainStreet 4 7"))); - bothDir.add(g.edge(5, 8).setDistance(5).setKeyValues(createKV(STREET_NAME, "5-8"))); + bothDir.add(g.edge(1, 2).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("MainStreet 1 2")))); + bothDir.add(g.edge(4, 7).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("MainStreet 4 7")))); + bothDir.add(g.edge(5, 8).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("5-8")))); - bothDir.add(edge3to6 = g.edge(3, 6).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-6"))); - oneDir.add(edge3to9 = g.edge(3, 9).setDistance(5).setKeyValues(createKV(STREET_NAME, "3-9"))); + bothDir.add(edge3to6 = g.edge(3, 6).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("3-6")))); + oneDir.add(edge3to9 = g.edge(3, 9).setDistance(5).setKeyValues(Map.of(STREET_NAME, new KValue("3-9")))); bothDir.add(g.edge(7, 10).setDistance(5)); bothDir.add(g.edge(10, 11).setDistance(5)); @@ -1109,12 +1420,14 @@ private RoundaboutGraph() { bothDir.add(g.edge(17, 19).setDistance(5)); for (EdgeIteratorState edge : bothDir) { - GHUtility.setSpeed(70, 70, mixedCarAccessEnc, mixedCarSpeedEnc, edge); - GHUtility.setSpeed(7, 7, mixedFootAccessEnc, mixedFootSpeedEnc, edge); + edge.set(mixedCarAccessEnc, true, true); + edge.set(mixedCarSpeedEnc, 70, 70); + edge.set(mixedFootSpeedEnc, 7, 7); } for (EdgeIteratorState edge : oneDir) { - GHUtility.setSpeed(70, 0, mixedCarAccessEnc, mixedCarSpeedEnc, edge); - GHUtility.setSpeed(7, 0, mixedFootAccessEnc, mixedFootSpeedEnc, edge); + edge.set(mixedCarAccessEnc, true); + edge.set(mixedCarSpeedEnc, 70, 0); + edge.set(mixedFootSpeedEnc, 7, 0); } setRoundabout(clockwise); inverse3to9(); @@ -1123,21 +1436,24 @@ private RoundaboutGraph() { public void setRoundabout(boolean clockwise) { BooleanEncodedValue mixedRoundabout = mixedEncodingManager.getBooleanEncodedValue(Roundabout.KEY); for (EdgeIteratorState edge : roundaboutEdges) { - edge.set(mixedCarAccessEnc, clockwise).setReverse(mixedCarAccessEnc, !clockwise); - edge.set(mixedFootAccessEnc, clockwise).setReverse(mixedFootAccessEnc, !clockwise); + edge.set(mixedCarSpeedEnc, clockwise ? 70 : 0, clockwise ? 0 : 70); + edge.set(mixedFootSpeedEnc, clockwise ? 7 : 0, clockwise ? 0 : 7); + edge.set(mixedCarAccessEnc, clockwise, !clockwise); edge.set(mixedRoundabout, true); } this.clockwise = clockwise; } public void inverse3to9() { - edge3to9.set(mixedCarAccessEnc, !edge3to9.get(mixedCarAccessEnc)).setReverse(mixedCarAccessEnc, false); - edge3to9.set(mixedFootAccessEnc, !edge3to9.get(mixedFootAccessEnc)).setReverse(mixedFootAccessEnc, false); + edge3to9.set(mixedCarAccessEnc, !edge3to9.get(mixedCarAccessEnc), false); + edge3to9.set(mixedCarSpeedEnc, edge3to9.get(mixedCarSpeedEnc) > 0 ? 0 : 70, 0); + edge3to9.set(mixedFootSpeedEnc, edge3to9.get(mixedFootSpeedEnc) > 0 ? 0 : 7, 0); } public void inverse3to6() { - edge3to6.set(mixedCarAccessEnc, !edge3to6.get(mixedCarAccessEnc)).setReverse(mixedCarAccessEnc, true); - edge3to6.set(mixedFootAccessEnc, !edge3to6.get(mixedFootAccessEnc)).setReverse(mixedFootAccessEnc, true); + edge3to6.set(mixedCarAccessEnc, !edge3to6.get(mixedCarAccessEnc), true); + edge3to6.set(mixedCarSpeedEnc, edge3to6.get(mixedCarSpeedEnc) > 0 ? 0 : 70, 70); + edge3to6.set(mixedFootSpeedEnc, edge3to6.get(mixedFootSpeedEnc) > 0 ? 0 : 7, 7); } private double getAngle(int n1, int n2, int n3, int n4) { @@ -1153,5 +1469,4 @@ private double getAngle(int n1, int n2, int n3, int n4) { private static Path extractPath(Graph graph, Weighting weighting, SPTEntry sptEntry) { return PathExtractor.extractPath(graph, weighting, sptEntry); } - } diff --git a/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java b/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java index 5f1c519173d..94556e78069 100644 --- a/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/PriorityRoutingTest.java @@ -34,16 +34,16 @@ import com.graphhopper.util.EdgeIteratorState; import org.junit.jupiter.api.Test; +import static com.graphhopper.json.Statement.If; import static org.junit.jupiter.api.Assertions.assertEquals; public class PriorityRoutingTest { @Test void testMaxPriority() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", false); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, false); DecimalEncodedValue priorityEnc = new DecimalEncodedValueImpl("priority", 4, PriorityCode.getFactor(1), false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).add(priorityEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).add(priorityEnc).add(RoadClass.create()).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); NodeAccess na = graph.getNodeAccess(); na.setNode(0, 48.0, 11.0); @@ -56,15 +56,15 @@ void testMaxPriority() { // \- 4 - 5 -/ double speed = speedEnc.getNextStorableValue(30); double dist1 = 0; - dist1 += addEdge(em, graph, 0, 1, 1.0, accessEnc, speedEnc, priorityEnc, speed).getDistance(); - dist1 += addEdge(em, graph, 1, 2, 1.0, accessEnc, speedEnc, priorityEnc, speed).getDistance(); - dist1 += addEdge(em, graph, 2, 3, 1.0, accessEnc, speedEnc, priorityEnc, speed).getDistance(); + dist1 += addEdge(em, graph, 0, 1, 1.0, speedEnc, priorityEnc, speed).getDistance(); + dist1 += addEdge(em, graph, 1, 2, 1.0, speedEnc, priorityEnc, speed).getDistance(); + dist1 += addEdge(em, graph, 2, 3, 1.0, speedEnc, priorityEnc, speed).getDistance(); final double maxPrio = PriorityCode.getFactor(PriorityCode.BEST.getValue()); double dist2 = 0; - dist2 += addEdge(em, graph, 0, 4, maxPrio, accessEnc, speedEnc, priorityEnc, speed).getDistance(); - dist2 += addEdge(em, graph, 4, 5, maxPrio, accessEnc, speedEnc, priorityEnc, speed).getDistance(); - dist2 += addEdge(em, graph, 5, 3, maxPrio, accessEnc, speedEnc, priorityEnc, speed).getDistance(); + dist2 += addEdge(em, graph, 0, 4, maxPrio, speedEnc, priorityEnc, speed).getDistance(); + dist2 += addEdge(em, graph, 4, 5, maxPrio, speedEnc, priorityEnc, speed).getDistance(); + dist2 += addEdge(em, graph, 5, 3, maxPrio, speedEnc, priorityEnc, speed).getDistance(); // the routes 0-1-2-3 and 0-4-5-3 have similar distances (and use max speed everywhere) // ... but the shorter route 0-1-2-3 has smaller priority @@ -74,9 +74,10 @@ void testMaxPriority() { // A* and Dijkstra should yield the same path (the max priority must be taken into account by weighting.getMinWeight) { CustomModel customModel = new CustomModel(); - CustomWeighting weighting = CustomModelParser.createWeighting(accessEnc, - speedEnc, priorityEnc, em, - TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); + customModel.addToPriority(If("true", Statement.Op.MULTIPLY, priorityEnc.getName())); + customModel.addToSpeed(If("true", Statement.Op.LIMIT, speedEnc.getName())); + + CustomWeighting weighting = CustomModelParser.createWeighting(em, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); Path pathDijkstra = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED).calcPath(0, 3); Path pathAStar = new AStar(graph, weighting, TraversalMode.NODE_BASED).calcPath(0, 3); assertEquals(pathDijkstra.calcNodes(), pathAStar.calcNodes()); @@ -86,9 +87,10 @@ void testMaxPriority() { { CustomModel customModel = new CustomModel(); // now we even increase the priority in the custom model, which also needs to be accounted for in weighting.getMinWeight - customModel.addToPriority(Statement.If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "3")); - CustomWeighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, - priorityEnc, em, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); + customModel.addToPriority(If("true", Statement.Op.MULTIPLY, priorityEnc.getName())); + customModel.addToPriority(If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "3")); + customModel.addToSpeed(If("true", Statement.Op.LIMIT, speedEnc.getName())); + CustomWeighting weighting = CustomModelParser.createWeighting(em, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); Path pathDijkstra = new Dijkstra(graph, weighting, TraversalMode.NODE_BASED).calcPath(0, 3); Path pathAStar = new AStar(graph, weighting, TraversalMode.NODE_BASED).calcPath(0, 3); assertEquals(pathDijkstra.calcNodes(), pathAStar.calcNodes()); @@ -96,10 +98,9 @@ void testMaxPriority() { } } - private EdgeIteratorState addEdge(EncodingManager em, BaseGraph graph, int p, int q, double prio, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, DecimalEncodedValue priorityEnc, double speed) { + private EdgeIteratorState addEdge(EncodingManager em, BaseGraph graph, int p, int q, double prio, DecimalEncodedValue speedEnc, DecimalEncodedValue priorityEnc, double speed) { EnumEncodedValue roadClassEnc = em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); return graph.edge(p, q) - .set(accessEnc, true) .set(speedEnc, speed) .set(priorityEnc, prio) .set(roadClassEnc, RoadClass.MOTORWAY) diff --git a/core/src/test/java/com/graphhopper/routing/QueryRoutingCHGraphTest.java b/core/src/test/java/com/graphhopper/routing/QueryRoutingCHGraphTest.java index 61a7507bba4..c376ea0f72d 100644 --- a/core/src/test/java/com/graphhopper/routing/QueryRoutingCHGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/QueryRoutingCHGraphTest.java @@ -377,26 +377,26 @@ public void getWeight() { CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); chBuilder.setIdentityLevels(); - chBuilder.addShortcutEdgeBased(0, 2, PrepareEncoder.getScDirMask(), 20, 0, 1, 0, 2); + chBuilder.addShortcutEdgeBased(0, 2, PrepareEncoder.getScDirMask(), 200, 0, 1, 0, 2); // without query graph RoutingCHEdgeIterator iter = routingCHGraph.createOutEdgeExplorer().setBaseNode(0); assertNextShortcut(iter, 0, 2, 0, 1); - assertEquals(20, iter.getWeight(false), 1.e-6); - assertEquals(20, iter.getWeight(true), 1.e-6); + assertEquals(200, iter.getWeight(false)); + assertEquals(200, iter.getWeight(true)); assertNextEdge(iter, 0, 1, 0); - assertEquals(238.249066, iter.getWeight(false), 1.e-6); - assertEquals(714.7472, iter.getWeight(true), 1.e-6); + assertEquals(2382, iter.getWeight(false)); + assertEquals(7147, iter.getWeight(true)); assertEnd(iter); // for incoming edges it's the same iter = routingCHGraph.createInEdgeExplorer().setBaseNode(0); assertNextShortcut(iter, 0, 2, 0, 1); - assertEquals(20, iter.getWeight(false), 1.e-6); - assertEquals(20, iter.getWeight(true), 1.e-6); + assertEquals(200, iter.getWeight(false)); + assertEquals(200, iter.getWeight(true)); assertNextEdge(iter, 0, 1, 0); - assertEquals(238.249066, iter.getWeight(false), 1.e-6); - assertEquals(714.7472, iter.getWeight(true), 1.e-6); + assertEquals(2382, iter.getWeight(false)); + assertEquals(7147, iter.getWeight(true)); assertEnd(iter); // now including virtual edges @@ -412,51 +412,51 @@ public void getWeight() { iter = queryCHGraph.createOutEdgeExplorer().setBaseNode(0); assertNextEdge(iter, 0, 3, 2); // should be about half the weight as for the original edge as the query point is in the middle of the edge - assertEquals(119.12453, iter.getWeight(false), 1.e-4); - assertEquals(357.373605, iter.getWeight(true), 1.e-4); + assertEquals(1191, iter.getWeight(false)); + assertEquals(3573, iter.getWeight(true)); assertNextShortcut(iter, 0, 2, 0, 1); - assertEquals(20, iter.getWeight(false), 1.e-6); - assertEquals(20, iter.getWeight(true), 1.e-6); + assertEquals(200, iter.getWeight(false)); + assertEquals(200, iter.getWeight(true)); assertEnd(iter); iter = queryCHGraph.createInEdgeExplorer().setBaseNode(0); assertNextEdge(iter, 0, 3, 2); - assertEquals(119.12453, iter.getWeight(false), 1.e-4); - assertEquals(357.373605, iter.getWeight(true), 1.e-4); + assertEquals(1191, iter.getWeight(false)); + assertEquals(3573, iter.getWeight(true)); assertNextShortcut(iter, 0, 2, 0, 1); - assertEquals(20, iter.getWeight(false), 1.e-6); - assertEquals(20, iter.getWeight(true), 1.e-6); + assertEquals(200, iter.getWeight(false)); + assertEquals(200, iter.getWeight(true)); assertEnd(iter); // at the virtual node iter = queryCHGraph.createOutEdgeExplorer().setBaseNode(3); assertNextEdge(iter, 3, 0, 2); - assertEquals(357.373605, iter.getWeight(false), 1.e-4); - assertEquals(119.12453, iter.getWeight(true), 1.e-4); + assertEquals(3573, iter.getWeight(false)); + assertEquals(1191, iter.getWeight(true)); assertNextEdge(iter, 3, 1, 3); - assertEquals(119.12453, iter.getWeight(false), 1.e-4); - assertEquals(357.373605, iter.getWeight(true), 1.e-4); + assertEquals(1191, iter.getWeight(false)); + assertEquals(3574, iter.getWeight(true)); assertEnd(iter); iter = queryCHGraph.createInEdgeExplorer().setBaseNode(3); assertNextEdge(iter, 3, 0, 2); - assertEquals(357.373605, iter.getWeight(false), 1.e-4); - assertEquals(119.12453, iter.getWeight(true), 1.e-4); + assertEquals(3573, iter.getWeight(false)); + assertEquals(1191, iter.getWeight(true)); assertNextEdge(iter, 3, 1, 3); - assertEquals(119.12453, iter.getWeight(false), 1.e-4); - assertEquals(357.373605, iter.getWeight(true), 1.e-4); + assertEquals(1191, iter.getWeight(false)); + assertEquals(3574, iter.getWeight(true)); assertEnd(iter); // getting a single edge RoutingCHEdgeIteratorState edgeState = queryCHGraph.getEdgeIteratorState(3, 3); assertEdgeState(edgeState, 0, 3, 2); - assertEquals(119.12453, edgeState.getWeight(false), 1.e-4); - assertEquals(357.373605, edgeState.getWeight(true), 1.e-4); + assertEquals(1191, edgeState.getWeight(false)); + assertEquals(3573, edgeState.getWeight(true)); edgeState = queryCHGraph.getEdgeIteratorState(3, 0); assertEdgeState(edgeState, 3, 0, 2); - assertEquals(357.373605, edgeState.getWeight(false), 1.e-4); - assertEquals(119.12453, edgeState.getWeight(true), 1.e-4); + assertEquals(3573, edgeState.getWeight(false)); + assertEquals(1191, edgeState.getWeight(true)); } @Test @@ -481,7 +481,7 @@ public void getTurnCost() { chBuilder.addShortcutEdgeBased(0, 2, PrepareEncoder.getScFwdDir(), 20, 0, 1, 0, 2); // without virtual nodes - assertEquals(5, routingCHGraph.getTurnWeight(0, 1, 1)); + assertEquals(50, routingCHGraph.getTurnWeight(0, 1, 1)); // with virtual nodes Snap snap1 = new Snap(50.00, 10.05); @@ -498,7 +498,7 @@ public void getTurnCost() { QueryGraph queryGraph = QueryGraph.create(graph, Arrays.asList(snap1, snap2)); QueryRoutingCHGraph queryCHGraph = new QueryRoutingCHGraph(routingCHGraph, queryGraph); - assertEquals(5, queryCHGraph.getTurnWeight(0, 1, 1)); + assertEquals(50, queryCHGraph.getTurnWeight(0, 1, 1)); // take a look at edges 3->1 and 1->4, their original edge ids are 3 and 4 (not 4 and 5) assertNodesConnected(queryCHGraph, 3, 1, true); @@ -516,7 +516,7 @@ public void getTurnCost() { assertEnd(iter); // check the turn weight between these edges - assertEquals(5, queryCHGraph.getTurnWeight(expectedEdge31, 1, expectedEdge14)); + assertEquals(50, queryCHGraph.getTurnWeight(expectedEdge31, 1, expectedEdge14)); } private void assertGetEdgeIteratorState(RoutingCHGraph graph, int base, int adj, int origEdge) { diff --git a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java index 97b7ea92228..d05a722fd13 100644 --- a/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/RandomCHRoutingTest.java @@ -1,6 +1,7 @@ package com.graphhopper.routing; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; +import com.graphhopper.routing.ch.NodeOrderingProvider; import com.graphhopper.routing.ch.PrepareContractionHierarchies; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; @@ -19,8 +20,9 @@ import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.GHUtility; -import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; +import com.graphhopper.util.RandomGraph; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -29,14 +31,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; import java.util.List; import java.util.Random; import java.util.stream.Stream; +import static com.graphhopper.util.GHUtility.comparePaths; import static com.graphhopper.util.GHUtility.createRandomSnaps; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; public class RandomCHRoutingTest { private static final Logger LOGGER = LoggerFactory.getLogger(RandomCHRoutingTest.class); @@ -94,23 +96,82 @@ public Stream provideArguments(ExtensionContext context) { @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void random(Fixture f) { + run_random(f, false, false); + } + + @ParameterizedTest + @ArgumentsSource(FixtureProvider.class) + public void random_strict(Fixture f) { + // with finite u-turn costs paths on a tree are not unique: we can do a necessary u-turn in + // different tree branches. u-turns can be necessary even without restricted start/target edges due to + // one-ways or turn restrictions, see edgeBased_turnRestriction_causes_uturn_ambiguity + boolean chain = Double.isFinite(f.uTurnCosts); + boolean tree = !chain; + run_random(f, chain, tree); + } + + private void run_random(Fixture f, boolean chain, boolean tree) { // you might have to keep this test running in an infinite loop for several minutes to find potential routing // bugs (e.g. use intellij 'run until stop/failure'). int numNodes = 50; long seed = System.nanoTime(); LOGGER.info("seed: " + seed); Random rnd = new Random(seed); - // we may not use an offset when query graph is involved, otherwise traveling via virtual edges will not be - // the same as taking the direct edge! - double pOffset = 0; - GHUtility.buildRandomGraph(f.graph, rnd, numNodes, 2.5, true, f.speedEnc, null, 0.9, pOffset); - if (f.traversalMode.isEdgeBased()) { + // curviness must be zero, because otherwise traveling via intermediate virtual nodes won't + // give the same results as using the original edge + double curviness = 0; + RandomGraph.start().seed(seed).nodes(numNodes).curviness(curviness).speedZero((chain || tree) ? 0 : 0.1).chain(chain).tree(tree).fill(f.graph, f.speedEnc); + if (f.traversalMode.isEdgeBased()) GHUtility.addRandomTurnCosts(f.graph, seed, null, f.turnCostEnc, f.maxTurnCosts, f.graph.getTurnCostStorage()); - } - runRandomTest(f, rnd); + runRandomTest(f, rnd, seed, chain || tree); + } + + /** + * On a tree with no one-ways, a turn restriction can force a U-turn detour. If two branches + * yield equal-weight detours but cover different physical distances, CH and Dijkstra may pick + * different branches — same weight, different distance. + */ + @Test + public void edgeBased_turnRestriction_causes_uturn_ambiguity() { + Fixture f = new Fixture(TraversalMode.EDGE_BASED, 40); + // 2 (north, ~111m from 1) + // | speed=10 → weight≈111 + // 0 --- 1 --- 3 + // | speed=5 → weight≈111 (half dist, half speed → same weight) + // 4 (south, ~56m from 1) + f.graph.edge(0, 1).set(f.speedEnc, 10, 10); // edge 0 + f.graph.edge(1, 2).set(f.speedEnc, 10, 10); // edge 1 + f.graph.edge(1, 3).set(f.speedEnc, 10, 10); // edge 2 + f.graph.edge(1, 4).set(f.speedEnc, 5, 5); // edge 3 + GHUtility.updateDistancesFor(f.graph, 1, 50.0, 10.0); + GHUtility.updateDistancesFor(f.graph, 0, 50.0, 9.999); + GHUtility.updateDistancesFor(f.graph, 2, 50.001, 10.0); + GHUtility.updateDistancesFor(f.graph, 3, 50.0, 10.001); + GHUtility.updateDistancesFor(f.graph, 4, 49.9995, 10.0); + + // Block the direct turn 0→1→3 (edge 0 → node 1 → edge 2). This forces a u-turn to get from 0 to 3. + f.graph.getTurnCostStorage().set(f.turnCostEnc, 0, 1, 2, Double.POSITIVE_INFINITY); + f.freeze(); + + // Dijkstra on base graph + Path dijkstra = new Dijkstra(f.graph, f.weighting, f.traversalMode).calcPath(0, 3); + assertTrue(dijkstra.isFound()); + + // CH with a node ordering that makes CH pick the other detour branch + PrepareContractionHierarchies pch = PrepareContractionHierarchies.fromGraph(f.graph, f.chConfig); + pch.useFixedNodeOrdering(NodeOrderingProvider.fromArray(2, 4, 0, 3, 1)); + PrepareContractionHierarchies.Result res = pch.doWork(); + RoutingCHGraph chGraph = RoutingCHGraphImpl.fromGraph(f.graph, res.getCHStorage(), res.getCHConfig()); + Path ch = new CHRoutingAlgorithmFactory(chGraph).createAlgo(new PMap()).calcPath(0, 3); + assertTrue(ch.isFound()); + + // same weight, but different distance. this is why for edge-based routing with finite u-turn costs paths are not unique in a tree + assertEquals(dijkstra.getWeight(), ch.getWeight()); + assertEquals(dijkstra.getDistance_mm(), 365340); + assertEquals(ch.getDistance_mm(), 254144); } - private void runRandomTest(Fixture f, Random rnd) { + private void runRandomTest(Fixture f, Random rnd, long seed, boolean strict) { LocationIndexTree locationIndex = new LocationIndexTree(f.graph, f.graph.getDirectory()); locationIndex.prepareIndex(); @@ -129,7 +190,6 @@ private void runRandomTest(Fixture f, Random rnd) { int numQueries = 100; int numPathsNotFound = 0; - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { int from = rnd.nextInt(queryGraph.getNodes()); int to = rnd.nextInt(queryGraph.getNodes()); @@ -151,26 +211,13 @@ private void runRandomTest(Fixture f, Random rnd) { continue; } - double weight = path.getWeight(); - if (Math.abs(refWeight - weight) > 1.e-2) { - LOGGER.warn("expected: " + refPath.calcNodes()); - LOGGER.warn("given: " + path.calcNodes()); - fail("wrong weight: " + from + "->" + to + ", dijkstra: " + refWeight + " vs. ch: " + path.getWeight()); - } - if (Math.abs(path.getDistance() - refPath.getDistance()) > 1.e-1) { - strictViolations.add("wrong distance " + from + "->" + to + ", expected: " + refPath.getDistance() + ", given: " + path.getDistance()); - } - if (Math.abs(path.getTime() - refPath.getTime()) > 50) { - strictViolations.add("wrong time " + from + "->" + to + ", expected: " + refPath.getTime() + ", given: " + path.getTime()); - } + List strictViolations = comparePaths(refPath, path, from, to, false, seed); + if (strict && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } if (numPathsNotFound > 0.9 * numQueries) { fail("Too many paths not found: " + numPathsNotFound + "/" + numQueries); } - if (strictViolations.size() > 0.05 * numQueries) { - fail("Too many strict violations: " + strictViolations.size() + "/" + numQueries + "\n" + - String.join("\n", strictViolations)); - } } } diff --git a/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java index 70ca4ea8322..3af0233caa5 100644 --- a/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/RandomizedRoutingTest.java @@ -18,12 +18,11 @@ package com.graphhopper.routing; +import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; +import com.graphhopper.routing.ch.NodeOrderingProvider; import com.graphhopper.routing.ch.PrepareContractionHierarchies; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.Subnetwork; -import com.graphhopper.routing.ev.TurnCost; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.lm.*; import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.querygraph.QueryRoutingCHGraph; @@ -35,8 +34,11 @@ import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; +import com.graphhopper.util.BaseGraphVisualizer; import com.graphhopper.util.GHUtility; import com.graphhopper.util.PMap; +import com.graphhopper.util.RandomGraph; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -56,11 +58,13 @@ import static com.graphhopper.util.GHUtility.createRandomSnaps; import static com.graphhopper.util.Parameters.Algorithms.*; import static com.graphhopper.util.Parameters.Routing.ALGORITHM; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Collections; /** * This test compares different routing algorithms with {@link DijkstraBidirectionRef}. Most prominently it uses - * randomly create graphs to create all sorts of different situations. + * randomly created graphs to create all sorts of different situations. * * @author easbar * @see RandomCHRoutingTest - similar but only tests CH algorithms @@ -97,7 +101,7 @@ private static class FixtureSupplier { private final String name; static FixtureSupplier create(Algo algo, boolean prepareCH, boolean prepareLM, TraversalMode traversalMode) { - return new FixtureSupplier(() -> new Fixture(algo, prepareCH, prepareLM, traversalMode), algo.toString()); + return new FixtureSupplier(() -> new Fixture(algo, prepareCH, prepareLM, traversalMode), algo.toString() + "_" + traversalMode.name()); } public FixtureSupplier(Supplier supplier, String name) { @@ -148,6 +152,10 @@ public String toString() { } private void preProcessGraph() { + preProcessGraph(null); + } + + private void preProcessGraph(NodeOrderingProvider nodeOrderingProvider) { graph.freeze(); weighting = traversalMode.isEdgeBased() ? new SpeedWeighting(speedEnc, turnCostEnc, graph.getTurnCostStorage(), Double.POSITIVE_INFINITY) @@ -155,6 +163,7 @@ private void preProcessGraph() { if (prepareCH) { CHConfig chConfig = traversalMode.isEdgeBased() ? CHConfig.edgeBased("p", weighting) : CHConfig.nodeBased("p", weighting); PrepareContractionHierarchies pch = PrepareContractionHierarchies.fromGraph(graph, chConfig); + pch.useFixedNodeOrdering(nodeOrderingProvider); PrepareContractionHierarchies.Result res = pch.doWork(); routingCHGraph = RoutingCHGraphImpl.fromGraph(graph, res.getCHStorage(), res.getCHConfig()); } @@ -193,12 +202,12 @@ private RoutingAlgorithm createAlgo(Graph graph) { return algoFactory.createAlgo(new PMap().putObject(ALGORITHM, ASTAR_BI)); } case LM_BIDIR: - return new LMRoutingAlgorithmFactory(lm).createAlgo(graph, weighting, new AlgorithmOptions().setAlgorithm(ASTAR_BI).setTraversalMode(traversalMode)); + return new LMRoutingAlgorithmFactory(lm).createAlgo(graph, graph.wrapWeighting(weighting), new AlgorithmOptions().setAlgorithm(ASTAR_BI).setTraversalMode(traversalMode)); case LM_UNIDIR: - return new LMRoutingAlgorithmFactory(lm).createAlgo(graph, weighting, new AlgorithmOptions().setAlgorithm(ASTAR).setTraversalMode(traversalMode)); + return new LMRoutingAlgorithmFactory(lm).createAlgo(graph, graph.wrapWeighting(weighting), new AlgorithmOptions().setAlgorithm(ASTAR).setTraversalMode(traversalMode)); case PERFECT_ASTAR: { - AStarBidirection perfectAStarBi = new AStarBidirection(graph, weighting, traversalMode); - perfectAStarBi.setApproximation(new PerfectApproximator(graph, weighting, traversalMode, false)); + AStarBidirection perfectAStarBi = new AStarBidirection(graph, graph.wrapWeighting(weighting), traversalMode); + perfectAStarBi.setApproximation(new PerfectApproximator(graph, graph.wrapWeighting(weighting), traversalMode, false)); return perfectAStarBi; } default: @@ -229,15 +238,24 @@ public Stream provideArguments(ExtensionContext context) { @ParameterizedTest @ArgumentsSource(RepeatedFixtureProvider.class) public void randomGraph(FixtureSupplier fixtureSupplier) { + run_randomGraph(fixtureSupplier, false); + } + + @ParameterizedTest + @ArgumentsSource(RepeatedFixtureProvider.class) + public void randomGraph_strict(FixtureSupplier fixtureSupplier) { + run_randomGraph(fixtureSupplier, true); + } + + private void run_randomGraph(FixtureSupplier fixtureSupplier, boolean tree) { Fixture f = fixtureSupplier.supplier.get(); final long seed = System.nanoTime(); final int numQueries = 50; Random rnd = new Random(seed); - GHUtility.buildRandomGraph(f.graph, rnd, 100, 2.2, true, f.speedEnc, null, 0.8, 0.8); + RandomGraph.start().seed(seed).nodes(100).curviness(0.1).speedZero(tree ? 0 : 0.1).tree(tree).fill(f.graph, f.speedEnc); GHUtility.addRandomTurnCosts(f.graph, seed, null, f.turnCostEnc, f.maxTurnCosts, f.turnCostStorage); // GHUtility.printGraphForUnitTest(f.graph, null, f.speedEnc); f.preProcessGraph(); - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { int source = rnd.nextInt(f.graph.getNodes()); int target = rnd.nextInt(f.graph.getNodes()); @@ -246,13 +264,9 @@ public void randomGraph(FixtureSupplier fixtureSupplier) { .calcPath(source, target); Path path = f.createAlgo() .calcPath(source, target); - strictViolations.addAll(GHUtility.comparePaths(refPath, path, source, target, seed)); - } - if (strictViolations.size() > 3) { - for (String strictViolation : strictViolations) { - LOGGER.info("strict violation: " + strictViolation); - } - fail("Too many strict violations: " + strictViolations.size() + " / " + numQueries + ", seed: " + seed); + List strictViolations = GHUtility.comparePaths(refPath, path, source, target, true, seed); + if (tree && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } } @@ -262,21 +276,28 @@ public void randomGraph(FixtureSupplier fixtureSupplier) { @ParameterizedTest @ArgumentsSource(RepeatedFixtureProvider.class) public void randomGraph_withQueryGraph(FixtureSupplier fixtureSupplier) { + run_randomGraph_withQueryGraph(fixtureSupplier, false); + } + + @ParameterizedTest + @ArgumentsSource(RepeatedFixtureProvider.class) + public void randomGraph_withQueryGraph_strict(FixtureSupplier fixtureSupplier) { + run_randomGraph_withQueryGraph(fixtureSupplier, true); + } + + private void run_randomGraph_withQueryGraph(FixtureSupplier fixtureSupplier, boolean tree) { Fixture f = fixtureSupplier.supplier.get(); final long seed = System.nanoTime(); final int numQueries = 50; - // we may not use an offset when query graph is involved, otherwise traveling via virtual edges will not be - // the same as taking the direct edge! - double pOffset = 0; Random rnd = new Random(seed); - GHUtility.buildRandomGraph(f.graph, rnd, 50, 2.2, true, f.speedEnc, null, 0.8, pOffset); + // todo: with curviness > 0 this sometimes fails for LM_UNIDIR (not sure why) + RandomGraph.start().seed(seed).nodes(50).curviness(0).speedZero(tree ? 0 : 0.1).tree(tree).fill(f.graph, f.speedEnc); GHUtility.addRandomTurnCosts(f.graph, seed, null, f.turnCostEnc, f.maxTurnCosts, f.turnCostStorage); // GHUtility.printGraphForUnitTest(f.graph, null, f.speedEnc); f.preProcessGraph(); LocationIndexTree index = new LocationIndexTree(f.graph, f.graph.getDirectory()); index.prepareIndex(); - List strictViolations = new ArrayList<>(); for (int i = 0; i < numQueries; i++) { List snaps = createRandomSnaps(f.graph.getBounds(), index, rnd, 2, true, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(f.graph, snaps); @@ -286,13 +307,45 @@ public void randomGraph_withQueryGraph(FixtureSupplier fixtureSupplier) { Path refPath = new DijkstraBidirectionRef(queryGraph, queryGraph.wrapWeighting(f.weighting), f.traversalMode).calcPath(source, target); Path path = f.createAlgo(queryGraph).calcPath(source, target); - strictViolations.addAll(GHUtility.comparePaths(refPath, path, source, target, seed)); - } - // we do not do a strict check because there can be ambiguity, for example when there are zero weight loops. - // however, when there are too many deviations we fail - if (strictViolations.size() > 3) { - LOGGER.warn(strictViolations.toString()); - fail("Too many strict violations: " + strictViolations.size() + " / " + numQueries + ", seed: " + seed); + List strictViolations = GHUtility.comparePaths(refPath, path, source, target, true, seed); + if (tree && !strictViolations.isEmpty()) + fail(strictViolations.toString()); } } + + @ParameterizedTest + @ArgumentsSource(FixtureProvider.class) + void distanceViaVirtualNodeIsTheSameAsViaOriginalEdge(FixtureSupplier s) { + Fixture f = s.supplier.get(); + // 2---x (3) + // / \ + // 0-------1 + f.graph.edge(1, 0).set(f.speedEnc, 20, 0); + f.graph.edge(2, 1).set(f.speedEnc, 5, 5); + f.graph.edge(2, 0).set(f.speedEnc, 10, 15); + GHUtility.updateDistancesFor(f.graph, 0, 49.999686, 9.999129); + GHUtility.updateDistancesFor(f.graph, 1, 49.999279, 10.000405); + GHUtility.updateDistancesFor(f.graph, 2, 50.000728, 9.999568); + f.preProcessGraph(NodeOrderingProvider.fromArray(2, 0, 1)); + LocationIndexTree index = new LocationIndexTree(f.graph, f.graph.getDirectory()); + index.prepareIndex(); + + Snap snap = index.findClosest(50.00024, 9.99985, e -> true); + QueryGraph queryGraph = QueryGraph.create(f.graph, Collections.singletonList(snap)); + Path dijkstra = new Dijkstra(queryGraph, queryGraph.wrapWeighting(f.weighting), f.traversalMode).calcPath(0, 1); + Path path = f.createAlgo(queryGraph).calcPath(0, 1); + + assertTrue(dijkstra.isFound() && path.isFound()); + // Usually we would travel from 2 to 1 via the virtual node 3, but CH takes the shortcut 0-1(via 2) and skips the virtual node + assertEquals(IntArrayList.from(0, 2, 3, 1), dijkstra.calcNodes()); + if (f.prepareCH) + assertEquals(IntArrayList.from(0, 2, 1), path.calcNodes()); + else + assertEquals(IntArrayList.from(0, 2, 3, 1), path.calcNodes()); + // ... but the times/distances/weights should still be the same! + assertEquals(dijkstra.getTime(), path.getTime()); + assertEquals(dijkstra.getDistance(), path.getDistance()); +// assertEquals(dijkstra.getWeight(), path.getWeight()); + } + } diff --git a/core/src/test/java/com/graphhopper/routing/RoundTripRoutingTest.java b/core/src/test/java/com/graphhopper/routing/RoundTripRoutingTest.java index 08e0134d7bd..396b4ec44b0 100644 --- a/core/src/test/java/com/graphhopper/routing/RoundTripRoutingTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoundTripRoutingTest.java @@ -28,7 +28,8 @@ import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.DAType; +import com.graphhopper.storage.GHDirectory; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; @@ -76,7 +77,7 @@ public void testLookupAndCalcPaths_simpleSquareGraph() { PMap hints = new PMap(); hints.putObject(Parameters.Algorithms.RoundTrip.POINTS, numPoints); hints.putObject(Parameters.Algorithms.RoundTrip.DISTANCE, roundTripDistance); - LocationIndex locationIndex = new LocationIndexTree(g, new RAMDirectory()).prepareIndex(); + LocationIndex locationIndex = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)).prepareIndex(); List stagePoints = RoundTripRouting.lookup(Collections.singletonList(start), new FiniteWeightFilter(weighting), locationIndex, new RoundTripRouting.Params(hints, heading, 3)); @@ -98,7 +99,7 @@ public void testLookupAndCalcPaths_simpleSquareGraph() { public void testCalcRoundTrip() { BaseGraph g = createTestGraph(); - LocationIndex locationIndex = new LocationIndexTree(g, new RAMDirectory()).prepareIndex(); + LocationIndex locationIndex = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)).prepareIndex(); Snap snap4 = locationIndex.findClosest(0.05, 0.25, EdgeFilter.ALL_EDGES); assertEquals(4, snap4.getClosestNode()); Snap snap5 = locationIndex.findClosest(0.00, 0.05, EdgeFilter.ALL_EDGES); @@ -126,7 +127,36 @@ public void testCalcRoundTrip() { private BaseGraph createTestGraph() { BaseGraph graph = new BaseGraph.Builder(em).withTurnCosts(true).create(); - AlternativeRouteTest.initTestGraph(graph, speedEnc); + /* 9 + _/\ + 1 2-3-4-10 + \ / \ + 5--6-7---8 + + */ + graph.edge(1, 9).setDistance(0).set(speedEnc, 60, 60); + graph.edge(9, 2).setDistance(0).set(speedEnc, 60, 60); + graph.edge(2, 3).setDistance(0).set(speedEnc, 60, 60); + graph.edge(3, 4).setDistance(0).set(speedEnc, 60, 60); + graph.edge(4, 10).setDistance(0).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(0).set(speedEnc, 60, 60); + graph.edge(6, 7).setDistance(0).set(speedEnc, 60, 60); + graph.edge(7, 8).setDistance(0).set(speedEnc, 60, 60); + graph.edge(1, 5).setDistance(0).set(speedEnc, 60, 60); + graph.edge(6, 3).setDistance(0).set(speedEnc, 60, 60); + graph.edge(4, 8).setDistance(0).set(speedEnc, 60, 60); + + updateDistancesFor(graph, 5, 0.00, 0.05); + updateDistancesFor(graph, 6, 0.00, 0.10); + updateDistancesFor(graph, 7, 0.00, 0.15); + updateDistancesFor(graph, 8, 0.00, 0.25); + + updateDistancesFor(graph, 1, 0.05, 0.00); + updateDistancesFor(graph, 9, 0.10, 0.05); + updateDistancesFor(graph, 2, 0.05, 0.10); + updateDistancesFor(graph, 3, 0.05, 0.15); + updateDistancesFor(graph, 4, 0.05, 0.25); + updateDistancesFor(graph, 10, 0.05, 0.30); return graph; } diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java index f9a0ef429bf..3ff4050b169 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmTest.java @@ -19,6 +19,7 @@ import com.carrotsearch.hppc.IntArrayList; import com.carrotsearch.hppc.IntIndexedContainer; +import com.graphhopper.json.Statement; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; import com.graphhopper.routing.ch.PrepareContractionHierarchies; import com.graphhopper.routing.ev.DecimalEncodedValue; @@ -29,7 +30,11 @@ import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.*; +import com.graphhopper.routing.weighting.SpeedWeighting; +import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.routing.weighting.custom.CustomModelParser; +import com.graphhopper.routing.weighting.custom.CustomWeighting; import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; @@ -42,17 +47,17 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.ArgumentsProvider; import org.junit.jupiter.params.provider.ArgumentsSource; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.Random; +import java.util.*; import java.util.stream.Stream; import static com.graphhopper.routing.util.TraversalMode.EDGE_BASED; import static com.graphhopper.routing.util.TraversalMode.NODE_BASED; +import static com.graphhopper.routing.weighting.Weighting.roundWeight; import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; import static com.graphhopper.util.GHUtility.updateDistancesFor; import static com.graphhopper.util.Parameters.Algorithms.ASTAR_BI; @@ -181,13 +186,15 @@ private Snap createSnapBetweenNodes(Graph graph, int node1, int node2) { return res; } - private void compareWithRef(Weighting weighting, BaseGraph graph, PathCalculator refCalculator, GHPoint from, GHPoint to, long seed) { + private void compareWithRef(Weighting weighting, BaseGraph graph, PathCalculator refCalculator, GHPoint from, GHPoint to, long seed, boolean strict) { Path path = calcPath(graph, weighting, from, to); Path refPath = refCalculator.calcPath(graph, weighting, traversalMode, defaultMaxVisitedNodes, from, to); - assertEquals(refPath.getWeight(), path.getWeight(), 1.e-1, "wrong weight, " + weighting + ", seed: " + seed); - assertEquals(refPath.calcNodes(), path.calcNodes(), "wrong nodes, " + weighting + ", seed: " + seed); - assertEquals(refPath.getDistance(), path.getDistance(), 1.e-1, "wrong distance, " + weighting + ", seed: " + seed); - assertEquals(refPath.getTime(), path.getTime(), 100, "wrong time, " + weighting + ", seed: " + seed); + IntArrayList refPathNodes = (IntArrayList) refPath.calcNodes(); + int source = refPathNodes.isEmpty() ? -1 : refPathNodes.get(0); + int target = refPathNodes.isEmpty() ? -1 : ArrayUtil.getLast(refPathNodes); + List strictViolations = GHUtility.comparePaths(refPath, path, source, target, true, seed); + if (strict) + assertTrue(strictViolations.isEmpty()); } public void resetCH() { @@ -236,8 +243,8 @@ public void testCalcShortestPath(Fixture f) { public void testCalcShortestPath_sourceEqualsTarget(Fixture f) { // 0-1-2 BaseGraph graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(1, 2).setDistance(2).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(1, 2).setDistance(200).set(f.carSpeedEnc, 60, 60); Path p = f.calcPath(graph, 0, 0); assertPathFromEqualsTo(p, 0); @@ -250,13 +257,13 @@ public void testSimpleAlternative(Fixture f) { // | | // 3--4 BaseGraph graph = f.createGHStorage(); - graph.edge(0, 2).setDistance(9).set(f.carSpeedEnc, 60, 60); - graph.edge(2, 1).setDistance(2).set(f.carSpeedEnc, 60, 60); - graph.edge(2, 3).setDistance(11).set(f.carSpeedEnc, 60, 60); - graph.edge(3, 4).setDistance(6).set(f.carSpeedEnc, 60, 60); - graph.edge(4, 1).setDistance(9).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 2).setDistance(90).set(f.carSpeedEnc, 10, 10); + graph.edge(2, 1).setDistance(20).set(f.carSpeedEnc, 10, 10); + graph.edge(2, 3).setDistance(110).set(f.carSpeedEnc, 10, 10); + graph.edge(3, 4).setDistance(60).set(f.carSpeedEnc, 10, 10); + graph.edge(4, 1).setDistance(90).set(f.carSpeedEnc, 10, 10); Path p = f.calcPath(graph, 0, 4); - assertEquals(20, p.getDistance(), 1e-4, p.toString()); + assertEquals(200, p.getDistance(), p.toString()); assertEquals(nodes(0, 2, 1, 4), p.calcNodes()); } @@ -395,26 +402,26 @@ static void initFootVsCar(DecimalEncodedValue carSpeedEnc, DecimalEncodedValue f // see test-graph.svg ! static void initTestStorage(Graph graph, DecimalEncodedValue speedEnc) { - graph.edge(0, 1).setDistance(7).set(speedEnc, 60, 60); - graph.edge(0, 4).setDistance(6).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(0).set(speedEnc, 10, 10); + graph.edge(0, 4).setDistance(0).set(speedEnc, 10, 10); - graph.edge(1, 4).setDistance(2).set(speedEnc, 60, 60); - graph.edge(1, 5).setDistance(8).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(2).set(speedEnc, 60, 60); + graph.edge(1, 4).setDistance(0).set(speedEnc, 10, 10); + graph.edge(1, 5).setDistance(0).set(speedEnc, 10, 10); + graph.edge(1, 2).setDistance(0).set(speedEnc, 10, 10); - graph.edge(2, 5).setDistance(5).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(2).set(speedEnc, 60, 60); + graph.edge(2, 5).setDistance(0).set(speedEnc, 10, 10); + graph.edge(2, 3).setDistance(0).set(speedEnc, 10, 10); - graph.edge(3, 5).setDistance(2).set(speedEnc, 60, 60); - graph.edge(3, 7).setDistance(10).set(speedEnc, 60, 60); + graph.edge(3, 5).setDistance(0).set(speedEnc, 10, 10); + graph.edge(3, 7).setDistance(0).set(speedEnc, 10, 10); - graph.edge(4, 6).setDistance(4).set(speedEnc, 60, 60); - graph.edge(4, 5).setDistance(7).set(speedEnc, 60, 60); + graph.edge(4, 6).setDistance(0).set(speedEnc, 10, 10); + graph.edge(4, 5).setDistance(0).set(speedEnc, 10, 10); - graph.edge(5, 6).setDistance(2).set(speedEnc, 60, 60); - graph.edge(5, 7).setDistance(1).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(0).set(speedEnc, 10, 10); + graph.edge(5, 7).setDistance(0).set(speedEnc, 10, 10); - EdgeIteratorState edge6_7 = graph.edge(6, 7).setDistance(5).set(speedEnc, 60, 60); + EdgeIteratorState edge6_7 = graph.edge(6, 7).setDistance(0).set(speedEnc, 10, 10); updateDistancesFor(graph, 0, 0.0010, 0.00001); updateDistancesFor(graph, 1, 0.0008, 0.0000); @@ -444,18 +451,18 @@ public void testNoPathFound(Fixture f) { // 7-5-6 // \| // 8 - graph.edge(0, 1).setDistance(7).set(f.carSpeedEnc, 60, 60); - graph.edge(5, 6).setDistance(2).set(f.carSpeedEnc, 60, 60); - graph.edge(5, 7).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(5, 8).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(7, 8).setDistance(1).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 1).setDistance(700).set(f.carSpeedEnc, 60, 60); + graph.edge(5, 6).setDistance(200).set(f.carSpeedEnc, 60, 60); + graph.edge(5, 7).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(5, 8).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(7, 8).setDistance(100).set(f.carSpeedEnc, 60, 60); assertFalse(f.calcPath(graph, 0, 5).isFound()); // disconnected as directed graph // 2-0->1 graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(1).set(f.carSpeedEnc, 60, 0); - graph.edge(0, 2).setDistance(1).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(f.carSpeedEnc, 60, 0); + graph.edge(0, 2).setDistance(100).set(f.carSpeedEnc, 60, 60); f.resetCH(); assertFalse(f.calcPath(graph, 1, 2).isFound()); assertTrue(f.calcPath(graph, 2, 1).isFound()); @@ -468,7 +475,7 @@ public void testWikipediaShortestPath(Fixture f) { initWikipediaTestGraph(graph, f.carSpeedEnc); Path p = f.calcPath(graph, 0, 4); assertEquals(nodes(0, 2, 5, 4), p.calcNodes(), p.toString()); - assertEquals(20, p.getDistance(), 1e-4, p.toString()); + assertEquals(200, p.getDistance(), p.toString()); } @ParameterizedTest @@ -483,15 +490,15 @@ public void testCalcIf1EdgeAway(Fixture f) { // see wikipedia-graph.svg ! private void initWikipediaTestGraph(Graph graph, DecimalEncodedValue speedEnc) { - graph.edge(0, 1).setDistance(7).set(speedEnc, 60, 60); - graph.edge(0, 2).setDistance(9).set(speedEnc, 60, 60); - graph.edge(0, 5).setDistance(14).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(10).set(speedEnc, 60, 60); - graph.edge(1, 3).setDistance(15).set(speedEnc, 60, 60); - graph.edge(2, 5).setDistance(2).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(11).set(speedEnc, 60, 60); - graph.edge(3, 4).setDistance(6).set(speedEnc, 60, 60); - graph.edge(4, 5).setDistance(9).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(70).set(speedEnc, 20, 20); + graph.edge(0, 2).setDistance(90).set(speedEnc, 20, 20); + graph.edge(0, 5).setDistance(140).set(speedEnc, 20, 20); + graph.edge(1, 2).setDistance(100).set(speedEnc, 20, 20); + graph.edge(1, 3).setDistance(150).set(speedEnc, 20, 20); + graph.edge(2, 5).setDistance(20).set(speedEnc, 20, 20); + graph.edge(2, 3).setDistance(110).set(speedEnc, 20, 20); + graph.edge(3, 4).setDistance(60).set(speedEnc, 20, 20); + graph.edge(4, 5).setDistance(90).set(speedEnc, 20, 20); } @ParameterizedTest @@ -517,16 +524,16 @@ public void testBidirectional(Fixture f) { // 7-6----5 public static void initBiGraph(Graph graph, DecimalEncodedValue speedEnc) { // distance will be overwritten in second step as we need to calculate it from lat,lon - graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - graph.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 5).setDistance(1).set(speedEnc, 60, 60); - graph.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - graph.edge(6, 7).setDistance(1).set(speedEnc, 60, 60); - graph.edge(7, 0).setDistance(1).set(speedEnc, 60, 60); - graph.edge(3, 8).setDistance(1).set(speedEnc, 60, 60); - graph.edge(8, 6).setDistance(1).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(0).set(speedEnc, 60, 60); + graph.edge(1, 2).setDistance(0).set(speedEnc, 60, 60); + graph.edge(2, 3).setDistance(0).set(speedEnc, 60, 60); + graph.edge(3, 4).setDistance(0).set(speedEnc, 60, 60); + graph.edge(4, 5).setDistance(0).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(0).set(speedEnc, 60, 60); + graph.edge(6, 7).setDistance(0).set(speedEnc, 60, 60); + graph.edge(7, 0).setDistance(0).set(speedEnc, 60, 60); + graph.edge(3, 8).setDistance(0).set(speedEnc, 60, 60); + graph.edge(8, 6).setDistance(0).set(speedEnc, 60, 60); // we need lat,lon for edge precise queries because the distances of snapped point // to adjacent nodes is calculated from lat,lon of the necessary points @@ -552,16 +559,16 @@ public void testCreateAlgoTwice(Fixture f) { // \ / / // 7-6-5-/ BaseGraph graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(1, 2).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(2, 3).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(3, 4).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(4, 5).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(5, 6).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(6, 7).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(7, 0).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(3, 8).setDistance(1).set(f.carSpeedEnc, 60, 60); - graph.edge(8, 6).setDistance(1).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(1, 2).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(2, 3).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(3, 4).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(4, 5).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(5, 6).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(6, 7).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(7, 0).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(3, 8).setDistance(100).set(f.carSpeedEnc, 60, 60); + graph.edge(8, 6).setDistance(100).set(f.carSpeedEnc, 60, 60); // run the same query twice, this can be interesting because in the second call algorithms that pre-process // the graph might depend on the state of the graph after the first call @@ -590,7 +597,7 @@ public void testBidirectional2(Fixture f) { BaseGraph graph = f.createGHStorage(); initBidirGraphManualDistances(graph, f.carSpeedEnc); Path p = f.calcPath(graph, 0, 4); - assertEquals(40, p.getDistance(), 1e-4, p.toString()); + assertEquals(4000, p.getDistance(), 1e-4, p.toString()); assertEquals(5, p.calcNodes().size(), p.toString()); assertEquals(nodes(0, 7, 6, 5, 4), p.calcNodes()); } @@ -601,16 +608,16 @@ private void initBidirGraphManualDistances(BaseGraph graph, DecimalEncodedValue // | 8 | // \ / / // 7-6-5-/ - graph.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); - graph.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - graph.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - graph.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - graph.edge(4, 5).setDistance(20).set(speedEnc, 60, 60); - graph.edge(5, 6).setDistance(10).set(speedEnc, 60, 60); - graph.edge(6, 7).setDistance(5).set(speedEnc, 60, 60); - graph.edge(7, 0).setDistance(5).set(speedEnc, 60, 60); - graph.edge(3, 8).setDistance(20).set(speedEnc, 60, 60); - graph.edge(8, 6).setDistance(20).set(speedEnc, 60, 60); + graph.edge(0, 1).setDistance(10000).set(speedEnc, 60, 60); + graph.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + graph.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + graph.edge(3, 4).setDistance(100).set(speedEnc, 60, 60); + graph.edge(4, 5).setDistance(2000).set(speedEnc, 60, 60); + graph.edge(5, 6).setDistance(1000).set(speedEnc, 60, 60); + graph.edge(6, 7).setDistance(500).set(speedEnc, 60, 60); + graph.edge(7, 0).setDistance(500).set(speedEnc, 60, 60); + graph.edge(3, 8).setDistance(2000).set(speedEnc, 60, 60); + graph.edge(8, 6).setDistance(2000).set(speedEnc, 60, 60); } @ParameterizedTest @@ -654,7 +661,7 @@ private static void initMatrixALikeGraph(BaseGraph tmpGraph, DecimalEncodedValue float dist = 5 + Math.abs(rand.nextInt(5)); if (print) System.out.print(" " + (int) dist + "\t "); - tmpGraph.edge(matrix[w][h], matrix[w][h - 1]).setDistance(dist).set(speedEnc, 60, 60); + tmpGraph.edge(matrix[w][h], matrix[w][h - 1]).setDistance(dist).set(speedEnc, 10, 10); } } if (print) { @@ -672,7 +679,7 @@ private static void initMatrixALikeGraph(BaseGraph tmpGraph, DecimalEncodedValue float dist = 5 + Math.abs(rand.nextInt(5)); if (print) System.out.print("-- " + (int) dist + "\t-- "); - tmpGraph.edge(matrix[w][h], matrix[w - 1][h]).setDistance(dist).set(speedEnc, 60, 60); + tmpGraph.edge(matrix[w][h], matrix[w - 1][h]).setDistance(dist).set(speedEnc, 10, 10); } if (print) System.out.print("(" + matrix[w][h] + ")\t"); @@ -700,8 +707,8 @@ private void testCorrectWeight(Fixture f, BaseGraph g) { public void testCannotCalculateSP(Fixture f) { // 0->1->2 BaseGraph graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(1).set(f.carSpeedEnc, 60, 0); - graph.edge(1, 2).setDistance(1).set(f.carSpeedEnc, 60, 0); + graph.edge(0, 1).setDistance(100).set(f.carSpeedEnc, 60, 0); + graph.edge(1, 2).setDistance(100).set(f.carSpeedEnc, 60, 0); Path p = f.calcPath(graph, 0, 2); assertEquals(3, p.calcNodes().size(), p.toString()); } @@ -710,16 +717,16 @@ public void testCannotCalculateSP(Fixture f) { @ArgumentsSource(FixtureProvider.class) public void testDirectedGraphBug1(Fixture f) { BaseGraph graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(3).set(f.carSpeedEnc, 60, 0); - graph.edge(1, 2).setDistance(2.99).set(f.carSpeedEnc, 60, 0); + graph.edge(0, 1).setDistance(300).set(f.carSpeedEnc, 10, 0); + graph.edge(1, 2).setDistance(299).set(f.carSpeedEnc, 10, 0); - graph.edge(0, 3).setDistance(2).set(f.carSpeedEnc, 60, 0); - graph.edge(3, 4).setDistance(3).set(f.carSpeedEnc, 60, 0); - graph.edge(4, 2).setDistance(1).set(f.carSpeedEnc, 60, 0); + graph.edge(0, 3).setDistance(200).set(f.carSpeedEnc, 10, 0); + graph.edge(3, 4).setDistance(300).set(f.carSpeedEnc, 10, 0); + graph.edge(4, 2).setDistance(100).set(f.carSpeedEnc, 10, 0); Path p = f.calcPath(graph, 0, 2); assertEquals(nodes(0, 1, 2), p.calcNodes(), p.toString()); - assertEquals(5.99, p.getDistance(), 1e-4, p.toString()); + assertEquals(599, p.getDistance(), 1, p.toString()); } @ParameterizedTest @@ -729,10 +736,10 @@ public void testDirectedGraphBug2(Fixture f) { // | / // 3< BaseGraph graph = f.createGHStorage(); - graph.edge(0, 1).setDistance(1).set(f.carSpeedEnc, 60, 0); - graph.edge(1, 2).setDistance(1).set(f.carSpeedEnc, 60, 0); - graph.edge(2, 3).setDistance(1).set(f.carSpeedEnc, 60, 0); - graph.edge(3, 1).setDistance(4).set(f.carSpeedEnc, 60, 60); + graph.edge(0, 1).setDistance(100).set(f.carSpeedEnc, 60, 0); + graph.edge(1, 2).setDistance(100).set(f.carSpeedEnc, 60, 0); + graph.edge(2, 3).setDistance(100).set(f.carSpeedEnc, 60, 0); + graph.edge(3, 1).setDistance(400).set(f.carSpeedEnc, 60, 60); Path p = f.calcPath(graph, 0, 3); assertEquals(nodes(0, 1, 2, 3), p.calcNodes()); @@ -747,21 +754,16 @@ public void testDirectedGraphBug2(Fixture f) { public void testWithCoordinates(Fixture f) { Weighting weighting = new SpeedWeighting(f.carSpeedEnc); BaseGraph graph = f.createGHStorage(false); - graph.edge(0, 1).setDistance(2).set(f.carSpeedEnc, 60, 60). + graph.edge(0, 1).setDistance(0).set(f.carSpeedEnc, 60, 60). setWayGeometry(Helper.createPointList(1.5, 1)); - graph.edge(2, 3).setDistance(2).set(f.carSpeedEnc, 60, 60). + graph.edge(2, 3).setDistance(0).set(f.carSpeedEnc, 60, 60). setWayGeometry(Helper.createPointList(0, 1.5)); - graph.edge(3, 4).setDistance(2).set(f.carSpeedEnc, 60, 60). + graph.edge(3, 4).setDistance(0).set(f.carSpeedEnc, 60, 60). setWayGeometry(Helper.createPointList(0, 2)); - - // duplicate but the second edge is longer - graph.edge(0, 2).setDistance(1.2).set(f.carSpeedEnc, 60, 60); - graph.edge(0, 2).setDistance(1.5).set(f.carSpeedEnc, 60, 60). - setWayGeometry(Helper.createPointList(0.5, 0)); - - graph.edge(1, 3).setDistance(1.3).set(f.carSpeedEnc, 60, 60). + graph.edge(0, 2).setDistance(0).set(f.carSpeedEnc, 60, 60); + graph.edge(1, 3).setDistance(0).set(f.carSpeedEnc, 60, 60). setWayGeometry(Helper.createPointList(0.5, 1.5)); - graph.edge(1, 4).setDistance(1).set(f.carSpeedEnc, 60, 60); + graph.edge(1, 4).setDistance(0).set(f.carSpeedEnc, 60, 60); updateDistancesFor(graph, 0, 1, 0.6); updateDistancesFor(graph, 1, 1, 1.5); @@ -870,6 +872,52 @@ public void testViaEdges_SpecialCases(Fixture f) { assertEquals(12.57, p.getDistance(), .1, p.toString()); } + @ParameterizedTest + @ArgumentsSource(FixtureProvider.class) + public void testBlockArea(Fixture f) { + // 1 -y- 2 + // | 5 | + // x 4 | the area around node 1 is expensive (node 4 is outside this area) + // | | + // 0 --- 3 + BaseGraph graph = f.createGHStorage(); + graph.edge(0, 1).set(f.carSpeedEnc, 10, 10); + graph.edge(1, 2).set(f.carSpeedEnc, 10, 10); + graph.edge(2, 3).set(f.carSpeedEnc, 10, 10); + graph.edge(3, 0).set(f.carSpeedEnc, 10, 10); + updateDistancesFor(graph, 0, 40.000, 10.000); + updateDistancesFor(graph, 1, 40.001, 10.000); + updateDistancesFor(graph, 2, 40.001, 10.001); + updateDistancesFor(graph, 3, 40.000, 10.001); + + JsonFeatureCollection areas = new JsonFeatureCollection(); + Coordinate[] blockArea = new Coordinate[]{ + new Coordinate(9.9997, 40.0007), + new Coordinate(9.9997, 40.0013), + new Coordinate(10.0003, 40.0013), + new Coordinate(10.0003, 40.0007), + new Coordinate(9.9997, 40.0007) + }; + areas.getFeatures().add(new JsonFeature("expensive", + "Feature", + null, + new GeometryFactory().createPolygon(blockArea), + new HashMap<>())); + CustomModel customModel = new CustomModel() + .addToSpeed(Statement.If("true", Statement.Op.LIMIT, "10")) + .addToPriority(Statement.If("in_expensive", Statement.Op.MULTIPLY, "0.1")); + customModel.addAreas(areas); + + CustomWeighting weighting = CustomModelParser.createWeighting(f.encodingManager, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); + + // We route from x(4) to y(5). The virtual edges adjacent to node 1 are in the area and thus expensive, + // so we need to take the detour via 3. But x-0 and y-2 are not. If QueryOverlay#adjustWeights distributed + // the weight difference between the original edges 0-1 and 1-2 (penalized all the way) equally to the + // virtual edges also x-0 and y-2 would carry the penalty and the route would go through the expensive area. + Path path = f.calcPath(graph, weighting, new GHPoint(40.0006, 10.000), new GHPoint(40.001, 10.0004)); + assertEquals(nodes(4, 0, 3, 2, 5), path.calcNodes()); + } + @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void testQueryGraphAndFastest(Fixture f) { @@ -894,8 +942,8 @@ public void testTwoWeightsPerEdge(Fixture f) { // of the speed and read 0 => infinity weight => overflow of millis => negative millis! Path p = f.calcPath(graph, weighting, 0, 10); assertEquals(23645657, p.getTime()); - assertEquals(425622, p.getDistance(), 1); - assertEquals(23646, p.getWeight(), 1); + assertEquals(425_621_860, p.getDistance_mm()); + assertEquals(236457, p.getWeight()); } @ParameterizedTest @@ -907,7 +955,7 @@ public void testTwoWeightsPerEdge2(Fixture f) { @Override public double calcMinWeightPerDistance() { - return 0.8; + return 8.0; } @Override @@ -924,13 +972,13 @@ public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { // a 'hill' at node 6 if (adj == 6) - return 3 * edgeState.getDistance(); + return roundWeight(10 * 3 * edgeState.getDistance()); else if (base == 6) - return edgeState.getDistance() * 0.9; + return roundWeight(10 * edgeState.getDistance() * 0.9); else if (adj == 4) - return 2 * edgeState.getDistance(); + return roundWeight(10 * 2 * edgeState.getDistance()); - return edgeState.getDistance() * 0.8; + return roundWeight(10 * edgeState.getDistance() * 0.8); } @Override @@ -940,7 +988,7 @@ public final long calcEdgeMillis(EdgeIteratorState edgeState, boolean reverse) { @Override public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { - return tmpW.calcTurnWeight(inEdge, viaNode, outEdge); + return roundWeight(10 * tmpW.calcTurnWeight(inEdge, viaNode, outEdge)); } @Override @@ -973,32 +1021,45 @@ public String toString() { initEleGraph(graph, 60, f.carSpeedEnc); p = f.calcPath(graph, fakeWeighting, 3, 0, 10, 9); assertEquals(nodes(12, 0, 1, 2, 11, 7, 10, 13), p.calcNodes()); - assertEquals(10280445, p.getTime()); - assertEquals(616827, p.getDistance(), 1); - assertEquals(493462, p.getWeight(), 1); + assertEquals(10280446, p.getTime()); + assertEquals(616827059, p.getDistance_mm()); + assertEquals(4934615, p.getWeight()); } @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void testRandomGraph(Fixture f) { + doTestRandomGraph(f, false); + } + + @ParameterizedTest + @ArgumentsSource(FixtureProvider.class) + public void testRandomTree(Fixture f) { + doTestRandomGraph(f, true); + } + + private void doTestRandomGraph(Fixture f, boolean tree) { Weighting weighting = new SpeedWeighting(f.carSpeedEnc); BaseGraph graph = f.createGHStorage(false); final long seed = System.nanoTime(); LOGGER.info("testRandomGraph - using seed: " + seed); Random rnd = new Random(seed); - GHUtility.buildRandomGraph(graph, rnd, 10, 2.0, true, f.carSpeedEnc, null, 0.7, 0.7); + RandomGraph.start().seed(seed).nodes(20).curviness(0.1).speedZero(tree ? 0 : 0.1).tree(tree).fill(graph, f.carSpeedEnc); final PathCalculator refCalculator = new DijkstraCalculator(); int numRuns = 100; for (int i = 0; i < numRuns; i++) { - BBox bounds = graph.getBounds(); - double latFrom = bounds.minLat + rnd.nextDouble() * (bounds.maxLat - bounds.minLat); - double lonFrom = bounds.minLon + rnd.nextDouble() * (bounds.maxLon - bounds.minLon); - double latTo = bounds.minLat + rnd.nextDouble() * (bounds.maxLat - bounds.minLat); - double lonTo = bounds.minLon + rnd.nextDouble() * (bounds.maxLon - bounds.minLon); - f.compareWithRef(weighting, graph, refCalculator, new GHPoint(latFrom, lonFrom), new GHPoint(latTo, lonTo), seed); + GHPoint from = createRandomPoint(rnd, graph.getBounds()); + GHPoint to = createRandomPoint(rnd, graph.getBounds()); + f.compareWithRef(weighting, graph, refCalculator, from, to, seed, tree); } } + private GHPoint createRandomPoint(Random rnd, BBox bounds) { + double lat = GHUtility.randomDoubleInRange(rnd, bounds.minLat, bounds.maxLat); + double lon = GHUtility.randomDoubleInRange(rnd, bounds.minLon, bounds.maxLon); + return new GHPoint(lat, lon); + } + @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void testMultipleVehicles_issue548(Fixture f) { @@ -1061,18 +1122,18 @@ private void initEleGraph(Graph graph, double s, DecimalEncodedValue speedEnc) { graph.edge(8, 9).setDistance(10).set(speedEnc, s, 0); graph.edge(9, 8).setDistance(9).set(speedEnc, s, 0); graph.edge(10, 9).setDistance(10).set(speedEnc, s, 0); - updateDistancesFor(graph, 0, 3, 0); - updateDistancesFor(graph, 3, 2.5, 0); - updateDistancesFor(graph, 5, 1, 0); - updateDistancesFor(graph, 8, 0, 0); - updateDistancesFor(graph, 1, 3, 1); - updateDistancesFor(graph, 4, 2, 1); - updateDistancesFor(graph, 6, 1, 1); - updateDistancesFor(graph, 9, 0, 1); - updateDistancesFor(graph, 2, 3, 2); - updateDistancesFor(graph, 11, 2, 2); - updateDistancesFor(graph, 7, 1, 2); - updateDistancesFor(graph, 10, 0, 2); + updateDistancesFor(graph, 0, 3, 0, 0); + updateDistancesFor(graph, 3, 2.5, 0, 0); + updateDistancesFor(graph, 5, 1, 0, 0); + updateDistancesFor(graph, 8, 0, 0, 0); + updateDistancesFor(graph, 1, 3, 1, 0); + updateDistancesFor(graph, 4, 2, 1, 0); + updateDistancesFor(graph, 6, 1, 1, 0); + updateDistancesFor(graph, 9, 0, 1, 0); + updateDistancesFor(graph, 2, 3, 2, 0); + updateDistancesFor(graph, 11, 2, 2, 0); + updateDistancesFor(graph, 7, 1, 2, 0); + updateDistancesFor(graph, 10, 0, 2, 0); } private static String getCHGraphName(Weighting weighting) { @@ -1114,7 +1175,7 @@ public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode travers @Override public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode traversalMode, int maxVisitedNodes, GHPoint from, GHPoint to) { - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); Snap fromSnap = index.findClosest(from.getLat(), from.getLon(), EdgeFilter.ALL_EDGES); Snap toSnap = index.findClosest(to.getLat(), to.getLon(), EdgeFilter.ALL_EDGES); @@ -1124,7 +1185,7 @@ public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode travers @Override public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode traversalMode, int maxVisitedNodes, Snap from, Snap to) { QueryGraph queryGraph = QueryGraph.create(graph, from, to); - RoutingAlgorithm algo = createAlgo(queryGraph, weighting, traversalMode); + RoutingAlgorithm algo = createAlgo(queryGraph, queryGraph.wrapWeighting(weighting), traversalMode); algo.setMaxVisitedNodes(maxVisitedNodes); return algo.calcPath(from.getClosestNode(), to.getClosestNode()); } @@ -1215,7 +1276,7 @@ public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode travers @Override public Path calcPath(BaseGraph graph, Weighting weighting, TraversalMode traversalMode, int maxVisitedNodes, GHPoint from, GHPoint to) { - LocationIndexTree locationIndex = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree locationIndex = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); LocationIndex index = locationIndex.prepareIndex(); Snap fromSnap = index.findClosest(from.getLat(), from.getLon(), EdgeFilter.ALL_EDGES); Snap toSnap = index.findClosest(to.getLat(), to.getLon(), EdgeFilter.ALL_EDGES); diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java index becf35d93f7..7f293722f25 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java @@ -24,7 +24,6 @@ import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; -import com.graphhopper.jackson.Jackson; import com.graphhopper.reader.dem.SRTMProvider; import com.graphhopper.storage.Graph; import com.graphhopper.util.*; @@ -34,14 +33,14 @@ import org.junit.jupiter.api.Test; import java.io.File; -import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; -import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.*; +import static com.graphhopper.json.Statement.Op.LIMIT; import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.util.Parameters.Algorithms.*; import static org.junit.jupiter.api.Assertions.*; @@ -73,18 +72,20 @@ public void setup() { @Test public void testMonaco() { - GraphHopper hopper = createHopper(MONACO, new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("car")); + Profile profile = TestProfiles.accessAndSpeed("car"); + profile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.importOrLoad(); checkQueries(hopper, createMonacoCarQueries()); Graph g = hopper.getBaseGraph(); // When OSM file stays unchanged make static edge and node IDs a requirement - assertEquals(GHUtility.asSet(924, 576, 2), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(10))); - assertEquals(GHUtility.asSet(291, 369, 19), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(20))); - assertEquals(GHUtility.asSet(45, 497, 488), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(480))); + assertEquals(GHUtility.asSet(9, 11), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(10))); + assertEquals(GHUtility.asSet(25, 12, 16), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(20))); + assertEquals(GHUtility.asSet(536, 481, 479), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(480))); - assertEquals(43.738776, g.getNodeAccess().getLat(10), 1e-6); - assertEquals(7.4170402, g.getNodeAccess().getLon(201), 1e-6); + assertEquals(43.7253762, g.getNodeAccess().getLat(10), 1e-6); + assertEquals(7.4281012, g.getNodeAccess().getLon(201), 1e-6); } private List createMonacoCarQueries() { @@ -110,15 +111,14 @@ private List createMonacoCarQueries() { public void testMonacoMotorcycleCurvature() { List queries = new ArrayList<>(); queries.add(new Query(43.730729, 7.42135, 43.727697, 7.419199, 2675, 117)); - queries.add(new Query(43.727687, 7.418737, 43.74958, 7.436566, 3727, 170)); + queries.add(new Query(43.727687, 7.418737, 43.74958, 7.436566, 3730, 170)); queries.add(new Query(43.728677, 7.41016, 43.739213, 7.4277, 2769, 167)); queries.add(new Query(43.733802, 7.413433, 43.739662, 7.424355, 2373, 137)); queries.add(new Query(43.730949, 7.412338, 43.739643, 7.424542, 2203, 116)); queries.add(new Query(43.727592, 7.419333, 43.727712, 7.419333, 0, 1)); GraphHopper hopper = createHopper(MONACO, new Profile("car").setCustomModel( - CustomModel.merge(getCustomModel("motorcycle.json"), getCustomModel("curvature.json"))).setVehicle("roads")); - hopper.setVehiclesString("car,roads"); - hopper.setEncodedValuesString("curvature,track_type,surface"); + CustomModel.merge(getCustomModel("motorcycle.json"), getCustomModel("curvature.json")))); + hopper.setEncodedValuesString("curvature,track_type,surface,road_access, road_class, road_environment, car_average_speed, car_access, max_speed, ferry_speed"); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -131,7 +131,7 @@ public void testBike2_issue432() { // reverse route avoids the location // list.add(new OneRun(52.349713, 8.013293, 52.349969, 8.013813, 293, 21)); GraphHopper hopper = createHopper(DIR + "/map-bug432.osm.gz", - new Profile("bike2").setVehicle("bike")); + TestProfiles.accessSpeedAndPriority("bike")); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -147,7 +147,7 @@ public void testOneWayCircleBug() { queries.add(new Query(51.376509, -0.530863, 51.376197, -0.531576, 75, 15)); GraphHopper hopper = createHopper(DIR + "/circle-bug.osm.gz", - new Profile("car").setVehicle("car")); + TestProfiles.accessAndSpeed("car")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -165,7 +165,7 @@ public void testMoscow() { // respect one way! // http://localhost:8989/?point=55.819066%2C37.596374&point=55.818898%2C37.59661 queries.add(new Query(55.818702, 37.595564, 55.818536, 37.595848, 1114, 23)); - GraphHopper hopper = createHopper(MOSCOW, new Profile("car").setVehicle("car")); + GraphHopper hopper = createHopper(MOSCOW, TestProfiles.accessAndSpeed("car")); hopper.setMinNetworkSize(200); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -176,8 +176,7 @@ public void testMoscowTurnCosts() { List queries = new ArrayList<>(); queries.add(new Query(55.813357, 37.5958585, 55.811042, 37.594689, 1043.99, 12)); queries.add(new Query(55.813159, 37.593884, 55.811278, 37.594217, 1048, 13)); - GraphHopper hopper = createHopper(MOSCOW, - new Profile("car").setVehicle("car").setTurnCosts(true)); + GraphHopper hopper = createHopper(MOSCOW, TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); hopper.setMinNetworkSize(200); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -187,8 +186,7 @@ public void testMoscowTurnCosts() { public void testSimpleTurnCosts() { List list = new ArrayList<>(); list.add(new Query(-0.49, 0.0, 0.0, -0.49, 298792.107, 6)); - GraphHopper hopper = createHopper(DIR + "/test_simple_turncosts.osm.xml", - new Profile("car").setVehicle("car").setTurnCosts(true)); + GraphHopper hopper = createHopper(DIR + "/test_simple_turncosts.osm.xml", TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); hopper.importOrLoad(); checkQueries(hopper, list); } @@ -197,19 +195,13 @@ public void testSimpleTurnCosts() { public void testSimplePTurn() { List list = new ArrayList<>(); list.add(new Query(0, 0.00099, -0.00099, 0, 664, 6)); - GraphHopper hopper = createHopper(DIR + "/test_simple_pturn.osm.xml", - new Profile("car").setVehicle("car").setTurnCosts(true)); + GraphHopper hopper = createHopper(DIR + "/test_simple_pturn.osm.xml", TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car())); hopper.importOrLoad(); checkQueries(hopper, list); } static CustomModel getCustomModel(String file) { - try { - String string = Helper.readJSONFileWithoutComments(new InputStreamReader(GHUtility.class.getResourceAsStream("/com/graphhopper/custom_models/" + file))); - return Jackson.newObjectMapper().readValue(string, CustomModel.class); - } catch (Exception ex) { - throw new RuntimeException(ex); - } + return GHUtility.loadCustomModelFromJar(file); } @Test @@ -220,7 +212,7 @@ public void testSidewalkNo() { // longer path should go through tertiary, see discussion in #476 queries.add(new Query(57.154888, -2.101822, 57.147299, -2.096286, 1118, 68)); - Profile profile = new Profile("hike").setVehicle("foot"); + Profile profile = TestProfiles.accessSpeedAndPriority("foot"); GraphHopper hopper = createHopper(DIR + "/map-sidewalk-no.osm.gz", profile); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -235,7 +227,7 @@ public void testMonacoFastest() { queries.get(3).getPoints().get(1).expectedPoints = 141; queries.get(4).getPoints().get(1).expectedDistance = 2149; queries.get(4).getPoints().get(1).expectedPoints = 120; - GraphHopper hopper = createHopper(MONACO, new Profile("car").setVehicle("car")); + GraphHopper hopper = createHopper(MONACO, TestProfiles.accessAndSpeed("car")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -251,42 +243,39 @@ public void testMonacoMixed() { queries.get(3).getPoints().get(1).expectedPoints = 137; queries.get(4).getPoints().get(1).expectedPoints = 116; - GraphHopper hopper = createHopper(MONACO, - new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("car"), - new Profile("foot").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("foot")); + Profile carProfile = TestProfiles.accessAndSpeed("car"); + carProfile.getCustomModel().setDistanceInfluence(10_000d); + Profile footProfile = TestProfiles.accessSpeedAndPriority("foot"); + footProfile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, carProfile, footProfile); hopper.importOrLoad(); checkQueries(hopper, queries); } @Test - public void testMonacoFoot() { - GraphHopper hopper = createHopper(MONACO, new Profile("foot").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("foot")); + public void testRealFootCustomModelInMonaco() { + Profile profile = new Profile("foot").setCustomModel(getCustomModel("foot.json")); + profile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.importOrLoad(); checkQueries(hopper, createMonacoFoot()); - Graph g = hopper.getBaseGraph(); - - // see testMonaco for a similar ID test - assertEquals(GHUtility.asSet(924, 576, 2), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(10))); - assertEquals(GHUtility.asSet(440, 442), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(441))); - assertEquals(GHUtility.asSet(913, 914, 911), GHUtility.getNeighbors(g.createEdgeExplorer().setBaseNode(912))); - - assertEquals(43.7467818, g.getNodeAccess().getLat(100), 1e-6); - assertEquals(7.4312824, g.getNodeAccess().getLon(702), 1e-6); } @Test public void testMonacoFoot3D() { // most routes have same number of points as testMonaceFoot results but longer distance due to elevation difference List queries = createMonacoFoot(); - queries.get(0).getPoints().get(1).expectedDistance = 1627; + queries.get(0).getPoints().get(1).expectedDistance = 1624; queries.get(2).getPoints().get(1).expectedDistance = 2250; queries.get(3).getPoints().get(1).expectedDistance = 1482; // or slightly longer tour with less nodes: list.get(1).setDistance(1, 3610); - queries.get(1).getPoints().get(1).expectedDistance = 3573; + queries.get(1).getPoints().get(1).expectedDistance = 3576; queries.get(1).getPoints().get(1).expectedPoints = 149; - GraphHopper hopper = createHopper(MONACO, new Profile("foot").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("foot")); + Profile profile = TestProfiles.accessSpeedAndPriority("foot"); + profile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -304,25 +293,24 @@ private List createMonacoFoot() { @Test public void testNorthBayreuthHikeFastestAnd3D() { List queries = new ArrayList<>(); - // prefer hiking route 'Teufelsloch Unterwaiz' and 'Rotmain-Wanderweg' + // prefer hiking route 'Teufelsloch Unterwaiz' and 'Rotmain-Wanderweg' queries.add(new Query(49.974972, 11.515657, 49.991022, 11.512299, 2365, 67)); // prefer hiking route 'Markgrafenweg Bayreuth Kulmbach' but avoid tertiary highway from Pechgraben - queries.add(new Query(49.990967, 11.545258, 50.023182, 11.555386, 5636, 97)); - GraphHopper hopper = createHopper(BAYREUTH, new Profile("hike").setCustomModel(getCustomModel("hike.json")).setVehicle("roads")); - hopper.setVehiclesString("roads,foot"); + queries.add(new Query(49.990967, 11.545258, 50.023182, 11.555386, 5690, 118)); + GraphHopper hopper = createHopper(BAYREUTH, new Profile("hike").setCustomModel(getCustomModel("hike.json"))); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); + checkQueries(hopper, queries); } @Test public void testHikeCanUseExtremeSacScales() { - GraphHopper hopper = createHopper(HOHEWARTE, new Profile("hike").setCustomModel(getCustomModel("hike.json")).setVehicle("roads")); - hopper.setVehiclesString("foot,roads"); + GraphHopper hopper = createHopper(HOHEWARTE, new Profile("hike").setCustomModel(getCustomModel("hike.json"))); // do not pull elevation data: hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); GHResponse res = hopper.route(new GHRequest(47.290322, 11.333889, 47.301593, 11.333489).setProfile("hike")); - assertEquals(3604, res.getBest().getTime() / 1000.0, 60); // 6100sec with srtm data + assertEquals(4806, res.getBest().getTime() / 1000.0, 60); // 6100sec with srtm data assertEquals(2000, res.getBest().getDistance(), 10); // 2536m with srtm data } @@ -330,9 +318,9 @@ public void testHikeCanUseExtremeSacScales() { public void testMonacoBike3D() { List queries = new ArrayList<>(); // 1. alternative: go over steps 'Rampe Major' => 1.7km vs. around 2.7km - queries.add(new Query(43.730864, 7.420771, 43.727687, 7.418737, 2702, 111)); + queries.add(new Query(43.730864, 7.420771, 43.727687, 7.418737, 2670, 118)); // 2. - queries.add(new Query(43.728499, 7.417907, 43.74958, 7.436566, 4208, 228)); + queries.add(new Query(43.728499, 7.417907, 43.74958, 7.436566, 4223, 233)); // 3. queries.add(new Query(43.728677, 7.41016, 43.739213, 7.427806, 2776, 167)); // 4. @@ -341,16 +329,15 @@ public void testMonacoBike3D() { // try reverse direction // 1. queries.add(new Query(43.727687, 7.418737, 43.730864, 7.420771, 2598, 115)); - queries.add(new Query(43.74958, 7.436566, 43.728499, 7.417907, 4250, 165)); + queries.add(new Query(43.74958, 7.436566, 43.728499, 7.417907, 3982, 181)); queries.add(new Query(43.739213, 7.427806, 43.728677, 7.41016, 2806, 145)); // 4. avoid tunnel(s)! queries.add(new Query(43.739662, 7.424355, 43.733802, 7.413433, 1901, 116)); - // atm the custom model is intended to be used with 'roads' vehicle when allowing reverse direction for oneways - // but tests here still assert that reverse oneways are excluded + // tests here still assert that reverse oneways are excluded GraphHopper hopper = createHopper(MONACO, new Profile("bike").setCustomModel(CustomModel.merge(getCustomModel("bike.json"), getCustomModel("bike_elevation.json")). - addToPriority(If("!bike_access", MULTIPLY, "0"))).setVehicle("roads")); - hopper.setVehiclesString("roads,bike"); + addToPriority(If("!bike_access", MULTIPLY, "0")))); + hopper.setEncodedValuesString("average_slope, max_slope, " + hopper.getEncodedValuesString()); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -365,7 +352,7 @@ public void testLandmarkBug() { run.add(50.023623, 11.56929, 7069, 178); queries.add(run); - GraphHopper hopper = createHopper(BAYREUTH, new Profile("bike").setVehicle("bike")); + GraphHopper hopper = createHopper(BAYREUTH, TestProfiles.accessSpeedAndPriority("bike")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -379,7 +366,7 @@ public void testBug1014() { query.add(50.023623, 11.56929, 6777, 175); queries.add(query); - GraphHopper hopper = createHopper(BAYREUTH, new Profile("bike").setVehicle("bike")); + GraphHopper hopper = createHopper(BAYREUTH, TestProfiles.accessSpeedAndPriority("bike")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -391,8 +378,9 @@ public void testMonacoBike() { queries.add(new Query(43.727687, 7.418737, 43.74958, 7.436566, 3580, 168)); queries.add(new Query(43.728677, 7.41016, 43.739213, 7.427806, 2323, 121)); queries.add(new Query(43.733802, 7.413433, 43.739662, 7.424355, 1446, 91)); - GraphHopper hopper = createHopper(MONACO, new Profile("bike"). - setCustomModel(new CustomModel().setDistanceInfluence(7000d)).setVehicle("bike")); + Profile profile = TestProfiles.accessSpeedAndPriority("bike"); + profile.getCustomModel().setDistanceInfluence(7000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -407,15 +395,15 @@ public void testMonacoMountainBike() { // hard to select between secondary and primary (both are AVOID for mtb) queries.add(new Query(43.733802, 7.413433, 43.739662, 7.424355, 1867, 107)); - GraphHopper hopper = createHopper(MONACO, new Profile("mtb").setVehicle("mtb")); + GraphHopper hopper = createHopper(MONACO, TestProfiles.accessSpeedAndPriority("mtb")); hopper.importOrLoad(); checkQueries(hopper, queries); Helper.removeDir(new File(GH_LOCATION)); hopper = createHopper(MONACO, - new Profile("mtb").setVehicle("mtb"), - new Profile("racingbike").setVehicle("racingbike")); + TestProfiles.accessSpeedAndPriority("mtb"), + TestProfiles.accessSpeedAndPriority("racingbike")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -423,20 +411,20 @@ public void testMonacoMountainBike() { @Test public void testMonacoRacingBike() { List queries = new ArrayList<>(); - queries.add(new Query(43.730864, 7.420771, 43.727687, 7.418737, 2594, 111)); + queries.add(new Query(43.730864, 7.420771, 43.727687, 7.418737, 2594, 111)); // watch out, this route has an alternative that looks very different but has almost identical weight queries.add(new Query(43.727687, 7.418737, 43.74958, 7.436566, 3615, 184)); queries.add(new Query(43.728677, 7.41016, 43.739213, 7.427806, 2651, 167)); queries.add(new Query(43.733802, 7.413433, 43.739662, 7.424355, 1516, 86)); - GraphHopper hopper = createHopper(MONACO, new Profile("racingbike").setVehicle("racingbike")); + GraphHopper hopper = createHopper(MONACO, TestProfiles.accessSpeedAndPriority("racingbike")); hopper.importOrLoad(); checkQueries(hopper, queries); Helper.removeDir(new File(GH_LOCATION)); hopper = createHopper(MONACO, - new Profile("racingbike").setVehicle("racingbike"), - new Profile("bike").setVehicle("bike") + TestProfiles.accessSpeedAndPriority("racingbike"), + TestProfiles.accessSpeedAndPriority("bike") ); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -445,23 +433,24 @@ public void testMonacoRacingBike() { @Test public void testKremsBikeRelation() { List queries = new ArrayList<>(); - queries.add(new Query(48.409523, 15.602394, 48.375466, 15.72916, 12491, 159)); - queries.add(new Query(48.410061, 15.63951, 48.411386, 15.604899, 3077, 79)); - queries.add(new Query(48.412294, 15.62007, 48.398306, 15.609667, 3965, 94)); + queries.add(new Query(48.409523, 15.602394, 48.375466, 15.72916, 12493, 159)); + queries.add(new Query(48.410061, 15.63951, 48.411386, 15.604899, 3091, 92)); + queries.add(new Query(48.412294, 15.62007, 48.398306, 15.609667, 3965, 95)); - GraphHopper hopper = createHopper(KREMS, - new Profile("bike"). - setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("bike")); + Profile bikeProfile = new Profile("bike").setCustomModel(new CustomModel(). + addToPriority(If("bike_access", MULTIPLY, "bike_priority")). + addToPriority(ElseIf("bike_network != MISSING", MULTIPLY, "1.8")). + addToPriority(Else(MULTIPLY, "0")). + addToSpeed(If("true", LIMIT, "bike_average_speed"))); + + GraphHopper hopper = createHopper(KREMS, bikeProfile); hopper.importOrLoad(); checkQueries(hopper, queries); hopper.getBaseGraph(); Helper.removeDir(new File(GH_LOCATION)); - hopper = createHopper(KREMS, - new Profile("bike"). - setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("bike"), - new Profile("car").setVehicle("car")); + hopper = createHopper(KREMS, bikeProfile, TestProfiles.accessAndSpeed("car")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -469,21 +458,23 @@ public void testKremsBikeRelation() { @Test public void testKremsMountainBikeRelation() { List queries = new ArrayList<>(); - queries.add(new Query(48.409523, 15.602394, 48.375466, 15.72916, 12574, 169)); - queries.add(new Query(48.410061, 15.63951, 48.411386, 15.604899, 3101, 94)); + queries.add(new Query(48.409523, 15.602394, 48.375466, 15.72916, 12493, 159)); + queries.add(new Query(48.410061, 15.63951, 48.411386, 15.604899, 3091, 92)); queries.add(new Query(48.412294, 15.62007, 48.398306, 15.609667, 3965, 95)); - GraphHopper hopper = createHopper(KREMS, new Profile("mtb"). - setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("mtb")); + Profile mtbProfile = new Profile("mtb").setCustomModel(new CustomModel(). + addToPriority(If("bike_access", MULTIPLY, "bike_priority")). + addToPriority(ElseIf("bike_network != MISSING", MULTIPLY, "1.8")). + addToPriority(Else(MULTIPLY, "0")). + addToSpeed(If("true", LIMIT, "bike_average_speed"))); + + GraphHopper hopper = createHopper(KREMS, mtbProfile); hopper.importOrLoad(); checkQueries(hopper, queries); Helper.removeDir(new File(GH_LOCATION)); - hopper = createHopper(KREMS, - new Profile("mtb"). - setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("mtb"), - new Profile("bike").setVehicle("bike")); + hopper = createHopper(KREMS, mtbProfile, TestProfiles.accessSpeedAndPriority("bike")); hopper.importOrLoad(); checkQueries(hopper, queries); @@ -498,7 +489,7 @@ private List createAndorraQueries() { @Test public void testAndorra() { - Profile profile = new Profile("car").setVehicle("car"); + Profile profile = TestProfiles.accessAndSpeed("car"); GraphHopper hopper = createHopper(ANDORRA, profile); hopper.importOrLoad(); checkQueries(hopper, createAndorraQueries()); @@ -506,7 +497,7 @@ public void testAndorra() { @Test public void testAndorraPbf() { - Profile profile = new Profile("car").setVehicle("car"); + Profile profile = TestProfiles.accessAndSpeed("car"); GraphHopper hopper = createHopper(ANDORRA_PBF, profile); hopper.importOrLoad(); checkQueries(hopper, createAndorraQueries()); @@ -517,12 +508,12 @@ public void testAndorraFoot() { List queries = createAndorraQueries(); queries.get(0).getPoints().get(1).expectedDistance = 16460; queries.get(0).getPoints().get(1).expectedPoints = 653; - queries.get(1).getPoints().get(1).expectedDistance = 12839; + queries.get(1).getPoints().get(1).expectedDistance = 12840; queries.get(1).getPoints().get(1).expectedPoints = 435; queries.add(new Query(42.521269, 1.52298, 42.50418, 1.520662, 3223, 107)); - GraphHopper hopper = createHopper(ANDORRA, new Profile("foot").setVehicle("foot")); + GraphHopper hopper = createHopper(ANDORRA, TestProfiles.accessSpeedAndPriority("foot")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -531,14 +522,15 @@ public void testAndorraFoot() { public void testCampoGrande() { // test not only NE quadrant of earth! - // bzcat campo-grande.osm.bz2 - // | ./bin/osmosis --read-xml enableDateParsing=no file=- --bounding-box top=-20.4 left=-54.6 bottom=-20.6 right=-54.5 --write-xml file=- + // bzcat campo-grande.osm.bz2 + // | ./bin/osmosis --read-xml enableDateParsing=no file=- --bounding-box top=-20.4 left=-54.6 bottom=-20.6 right=-54.5 --write-xml file=- // | bzip2 > campo-grande.extracted.osm.bz2 List queries = new ArrayList<>(); queries.add(new Query(-20.4001, -54.5999, -20.598, -54.54, 25323, 271)); queries.add(new Query(-20.43, -54.54, -20.537, -54.5999, 16233, 226)); - GraphHopper hopper = createHopper(DIR + "/campo-grande.osm.gz", - new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(1_000d)).setVehicle("car")); + Profile profile = TestProfiles.accessAndSpeed("car"); + profile.getCustomModel().setDistanceInfluence(1_000d); + GraphHopper hopper = createHopper(DIR + "/campo-grande.osm.gz", profile); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -553,7 +545,9 @@ public void testMonacoVia() { List queries = new ArrayList<>(); queries.add(query); - GraphHopper hopper = createHopper(MONACO, new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("car")); + Profile profile = TestProfiles.accessAndSpeed("car"); + profile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -566,7 +560,7 @@ public void testHarsdorf() { // choose Unterloher Weg and the following residential + cycleway // list.add(new OneRun(50.004333, 11.600254, 50.044449, 11.543434, 6931, 184)); - GraphHopper hopper = createHopper(BAYREUTH, new Profile("bike").setVehicle("bike")); + GraphHopper hopper = createHopper(BAYREUTH, TestProfiles.accessSpeedAndPriority("bike")); hopper.importOrLoad(); checkQueries(hopper, queries); } @@ -577,14 +571,14 @@ public void testNeudrossenfeld() { // choose cycleway (Dreschenauer Straße) list.add(new Query(49.987132, 11.510496, 50.018839, 11.505024, 3985, 106)); - GraphHopper hopper = createHopper(BAYREUTH, new Profile("bike").setVehicle("bike")); + GraphHopper hopper = createHopper(BAYREUTH, TestProfiles.accessSpeedAndPriority("bike")); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, list); Helper.removeDir(new File(GH_LOCATION)); - hopper = createHopper(BAYREUTH, new Profile("bike2").setVehicle("bike")); + hopper = createHopper(BAYREUTH, TestProfiles.accessSpeedAndPriority("bike")); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, list); @@ -596,8 +590,8 @@ public void testBikeBayreuth_UseBikeNetwork() { list.add(new Query(49.979667, 11.521019, 49.987415, 11.510577, 1288, 45)); GraphHopper hopper = createHopper(BAYREUTH, new Profile("bike").setCustomModel( - CustomModel.merge(getCustomModel("bike.json"), getCustomModel("bike_elevation.json"))).setVehicle("roads")); - hopper.setVehiclesString("bike,roads"); + CustomModel.merge(getCustomModel("bike.json"), getCustomModel("bike_elevation.json")))); + hopper.setEncodedValuesString("average_slope, max_slope, " + hopper.getEncodedValuesString()); hopper.setElevationProvider(new SRTMProvider(DIR)); hopper.importOrLoad(); checkQueries(hopper, list); @@ -611,7 +605,7 @@ public void testDisconnectedAreaAndMultiplePoints() { query.add(53.751299, 9.3869, 10, 10); GraphHopper hopper = createHopper(DIR + "/krautsand.osm.gz", - new Profile("car").setVehicle("car")); + TestProfiles.accessAndSpeed("car")); hopper.importOrLoad(); for (Function requestFactory : createRequestFactories()) { @@ -625,7 +619,9 @@ public void testDisconnectedAreaAndMultiplePoints() { @Test public void testMonacoParallel() throws InterruptedException { - GraphHopper hopper = createHopper(MONACO, new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(10_000d)).setVehicle("car")); + Profile profile = TestProfiles.accessAndSpeed("car"); + profile.getCustomModel().setDistanceInfluence(10_000d); + GraphHopper hopper = createHopper(MONACO, profile); hopper.getReaderConfig().setMaxWayPointDistance(0); hopper.getRouterConfig().setSimplifyResponse(false); hopper.importOrLoad(); @@ -711,7 +707,12 @@ private GraphHopper createHopper(String osmFile, Profile... profiles) { setStoreOnFlush(false). setOSMFile(osmFile). setProfiles(profiles). - setEncodedValuesString("average_slope,max_slope,hike_rating"). + setEncodedValuesString("hike_rating, car_access, car_average_speed, " + + "foot_access, foot_priority, foot_average_speed, foot_network, " + + "bike_access, bike_priority, bike_average_speed, bike_network, roundabout, " + + "mtb_access, mtb_priority, mtb_average_speed, mtb_rating, " + + "racingbike_access, racingbike_priority, racingbike_average_speed, " + + "foot_road_access, bike_road_access, country, road_class, road_environment, ferry_speed"). setGraphHopperLocation(GH_LOCATION); hopper.getRouterConfig().setSimplifyResponse(false); hopper.setMinNetworkSize(0); @@ -735,7 +736,7 @@ private void checkQueries(GraphHopper hopper, List queries) { String expectedAlgo = request.getHints().getString("expected_algo", "no_expected_algo"); checkResponse(expectedAlgo, res, query); // for edge-based routing we expect a slightly different algo name for CH - if (profile.isTurnCosts()) + if (profile.hasTurnCosts()) expectedAlgo = expectedAlgo.replaceAll("\\|ch-routing", "|ch|edge_based|no_sod-routing"); assertTrue(res.getBest().getDebugInfo().contains(expectedAlgo), "Response does not contain expected algo string. Expected: '" + expectedAlgo + diff --git a/core/src/test/java/com/graphhopper/routing/RoutingCHGraphImplTest.java b/core/src/test/java/com/graphhopper/routing/RoutingCHGraphImplTest.java index 2309303d6ec..f38aefaf727 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingCHGraphImplTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingCHGraphImplTest.java @@ -22,11 +22,9 @@ import com.carrotsearch.hppc.IntSet; import com.graphhopper.routing.ch.PrepareEncoder; import com.graphhopper.routing.ev.*; -import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.storage.*; import com.graphhopper.util.EdgeExplorer; import com.graphhopper.util.EdgeIteratorState; @@ -39,15 +37,14 @@ public class RoutingCHGraphImplTest { @Test public void testBaseAndCHEdges() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); graph.edge(1, 0); graph.edge(8, 9); graph.freeze(); - CHConfig chConfig = CHConfig.nodeBased("p", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); + CHConfig chConfig = CHConfig.nodeBased("p", new SpeedWeighting(speedEnc)); CHStorage store = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(store); chBuilder.setIdentityLevels(); @@ -78,15 +75,13 @@ void testShortcutConnection() { // 4 ------ 1 > 0 // ^ \ // 3 2 - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - EdgeExplorer baseCarOutExplorer = graph.createEdgeExplorer(AccessFilter.outEdges(accessEnc)); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(4, 1).setDistance(30)); + graph.edge(4, 1).setDistance(30).set(speedEnc, 60); graph.freeze(); - CHConfig chConfig = CHConfig.nodeBased("ch", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); + CHConfig chConfig = CHConfig.nodeBased("ch", new SpeedWeighting(speedEnc)); CHStorage store = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(store); chBuilder.setIdentityLevels(); @@ -94,6 +89,7 @@ void testShortcutConnection() { chBuilder.addShortcutNodeBased(1, 2, PrepareEncoder.getScDirMask(), 10, 10, 11); chBuilder.addShortcutNodeBased(1, 3, PrepareEncoder.getScBwdDir(), 10, 14, 15); + EdgeExplorer baseExplorer = graph.createEdgeExplorer(); RoutingCHGraph lg = RoutingCHGraphImpl.fromGraph(graph, store, chConfig); RoutingCHEdgeExplorer chOutExplorer = lg.createOutEdgeExplorer(); RoutingCHEdgeExplorer chInExplorer = lg.createInEdgeExplorer(); @@ -105,7 +101,7 @@ void testShortcutConnection() { assertEquals(2, GHUtility.count(chOutExplorer.setBaseNode(1))); assertEquals(3, GHUtility.count(chInExplorer.setBaseNode(1))); assertEquals(GHUtility.asSet(2, 4), GHUtility.getNeighbors(chOutExplorer.setBaseNode(1))); - assertEquals(GHUtility.asSet(4), GHUtility.getNeighbors(baseCarOutExplorer.setBaseNode(1))); + assertEquals(GHUtility.asSet(4), GHUtility.getNeighbors(baseExplorer.setBaseNode(1))); assertEquals(0, GHUtility.count(chOutExplorer.setBaseNode(3))); assertEquals(0, GHUtility.count(chInExplorer.setBaseNode(3))); @@ -116,15 +112,14 @@ void testShortcutConnection() { @Test public void testGetWeight() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); EdgeIteratorState edge1 = graph.edge(0, 1); EdgeIteratorState edge2 = graph.edge(1, 2); graph.freeze(); - CHConfig chConfig = CHConfig.nodeBased("ch", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); + CHConfig chConfig = CHConfig.nodeBased("ch", new SpeedWeighting(speedEnc)); CHStorage store = CHStorage.fromGraph(graph, chConfig); RoutingCHGraph g = RoutingCHGraphImpl.fromGraph(graph, store, chConfig); assertFalse(g.getEdgeIteratorState(edge1.getEdge(), Integer.MIN_VALUE).isShortcut()); @@ -144,69 +139,65 @@ public void testGetWeight() { @Test public void testGetWeightIfAdvancedEncoder() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph ghStorage = new BaseGraph.Builder(em).create(); ghStorage.edge(0, 3); ghStorage.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(ghStorage, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); chBuilder.setIdentityLevels(); - int sc1 = ghStorage.getEdges() + chBuilder.addShortcutNodeBased(0, 1, PrepareEncoder.getScFwdDir(), 100.123, NO_EDGE, NO_EDGE); + int sc1 = ghStorage.getEdges() + chBuilder.addShortcutNodeBased(0, 1, PrepareEncoder.getScFwdDir(), 100, NO_EDGE, NO_EDGE); RoutingCHGraph lg = RoutingCHGraphImpl.fromGraph(ghStorage, chStore, chConfig); assertEquals(1, lg.getEdgeIteratorState(sc1, 1).getAdjNode()); assertEquals(0, lg.getEdgeIteratorState(sc1, 1).getBaseNode()); - assertEquals(100.123, lg.getEdgeIteratorState(sc1, 1).getWeight(false), 1e-3); - assertEquals(100.123, lg.getEdgeIteratorState(sc1, 0).getWeight(false), 1e-3); + assertEquals(100, lg.getEdgeIteratorState(sc1, 1).getWeight(false)); + assertEquals(100, lg.getEdgeIteratorState(sc1, 0).getWeight(false)); - int sc2 = ghStorage.getEdges() + chBuilder.addShortcutNodeBased(2, 3, PrepareEncoder.getScDirMask(), 1.011011, NO_EDGE, NO_EDGE); + int sc2 = ghStorage.getEdges() + chBuilder.addShortcutNodeBased(2, 3, PrepareEncoder.getScDirMask(), 1, NO_EDGE, NO_EDGE); assertEquals(3, lg.getEdgeIteratorState(sc2, 3).getAdjNode()); assertEquals(2, lg.getEdgeIteratorState(sc2, 3).getBaseNode()); - assertEquals(1.011011, lg.getEdgeIteratorState(sc2, 2).getWeight(false), 1e-3); - assertEquals(1.011011, lg.getEdgeIteratorState(sc2, 3).getWeight(false), 1e-3); + assertEquals(1.0, lg.getEdgeIteratorState(sc2, 2).getWeight(false)); + assertEquals(1.0, lg.getEdgeIteratorState(sc2, 3).getWeight(false)); } @Test public void testWeightExact() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(1)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(1, 2).setDistance(1)); + graph.edge(0, 1).setDistance(1).set(speedEnc, 60); + graph.edge(1, 2).setDistance(1).set(speedEnc, 60); graph.freeze(); - CHConfig chConfig = CHConfig.nodeBased("ch", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); + CHConfig chConfig = CHConfig.nodeBased("ch", new SpeedWeighting(speedEnc)); CHStorage store = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(store); chBuilder.setIdentityLevels(); - // we just make up some weights, they do not really have to be related to our previous edges. - // 1.004+1.006 = 2.09999999999. we make sure this does not become 2.09 instead of 2.10 (due to truncation) - double x1 = 1.004; - double x2 = 1.006; + // this test used to be a lot more interesting. now that we are calculating only with whole number doubles it became trivial + double x1 = 1; + double x2 = 2; RoutingCHGraph rg = RoutingCHGraphImpl.fromGraph(graph, store, chConfig); chBuilder.addShortcutNodeBased(0, 2, PrepareEncoder.getScFwdDir(), x1 + x2, 0, 1); RoutingCHEdgeIteratorState sc = rg.getEdgeIteratorState(2, 2); - assertEquals(2.01, sc.getWeight(false), 1.e-6); + assertEquals(3, sc.getWeight(false)); } @Test public void testSimpleShortcutCreationAndTraversal() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 3).setDistance(10)); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(3, 4).setDistance(10)); + graph.edge(1, 3).setDistance(10).set(speedEnc, 60); + graph.edge(3, 4).setDistance(10).set(speedEnc, 60); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); @@ -222,15 +213,14 @@ public void testSimpleShortcutCreationAndTraversal() { @Test public void testAddShortcutSkippedEdgesWriteRead() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - final EdgeIteratorState edge1 = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 3).setDistance(10)); - final EdgeIteratorState edge2 = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(3, 4).setDistance(10)); + final EdgeIteratorState edge1 = graph.edge(1, 3).setDistance(10).set(speedEnc, 60); + final EdgeIteratorState edge2 = graph.edge(3, 4).setDistance(10).set(speedEnc, 60); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); @@ -244,15 +234,14 @@ public void testAddShortcutSkippedEdgesWriteRead() { @Test public void testSkippedEdges() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - final EdgeIteratorState edge1 = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 3).setDistance(10)); - final EdgeIteratorState edge2 = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(3, 4).setDistance(10)); + final EdgeIteratorState edge1 = graph.edge(1, 3).setDistance(10).set(speedEnc, 60); + final EdgeIteratorState edge2 = graph.edge(3, 4).setDistance(10).set(speedEnc, 60); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); @@ -264,16 +253,16 @@ public void testSkippedEdges() { @Test public void testAddShortcut_edgeBased_throwsIfNotConfiguredForEdgeBased() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(1)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(1, 2).setDistance(1)); + graph.edge(0, 1).setDistance(100).set(speedEnc, 60); + graph.edge(1, 2).setDistance(100).set(speedEnc, 60); + graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(chStore); @@ -283,15 +272,14 @@ public void testAddShortcut_edgeBased_throwsIfNotConfiguredForEdgeBased() { @Test public void testAddShortcut_edgeBased() { // 0 -> 1 -> 2 - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(1)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(1, 2).setDistance(3)); + graph.edge(0, 1).setDistance(100).set(speedEnc, 60); + graph.edge(1, 2).setDistance(300).set(speedEnc, 60); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.edgeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); @@ -304,12 +292,11 @@ public void testAddShortcut_edgeBased() { @Test public void outOfBounds() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.nodeBased("p1", weighting); CHStorage chStore = CHStorage.fromGraph(graph, chConfig); RoutingCHGraph lg = RoutingCHGraphImpl.fromGraph(graph, chStore, chConfig); @@ -318,15 +305,14 @@ public void outOfBounds() { @Test public void testGetEdgeIterator() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(1)); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(1, 2).setDistance(1)); + graph.edge(0, 1).setDistance(100).set(speedEnc, 60); + graph.edge(1, 2).setDistance(100).set(speedEnc, 60); graph.freeze(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em); + Weighting weighting = new SpeedWeighting(speedEnc); CHConfig chConfig = CHConfig.edgeBased("p1", weighting); CHStorage store = CHStorage.fromGraph(graph, chConfig); CHStorageBuilder chBuilder = new CHStorageBuilder(store); diff --git a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java b/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java deleted file mode 100644 index 7269e88fe5e..00000000000 --- a/core/src/test/java/com/graphhopper/routing/TrafficChangeWithNodeOrderingReusingTest.java +++ /dev/null @@ -1,237 +0,0 @@ -package com.graphhopper.routing; - -import com.graphhopper.reader.osm.OSMReader; -import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; -import com.graphhopper.routing.ch.PrepareContractionHierarchies; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.VehicleAccess; -import com.graphhopper.routing.ev.VehicleSpeed; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.OSMParsers; -import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.util.parsers.CarAverageSpeedParser; -import com.graphhopper.routing.weighting.AbstractWeighting; -import com.graphhopper.routing.weighting.TurnCostProvider; -import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; -import com.graphhopper.storage.*; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.MiniPerfTest; -import com.graphhopper.util.PMap; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.ArgumentsProvider; -import org.junit.jupiter.params.provider.ArgumentsSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.util.Locale; -import java.util.Random; -import java.util.stream.Stream; - -import static java.lang.System.nanoTime; -import static org.junit.jupiter.api.Assertions.assertEquals; - -/** - * Tests CH contraction and query performance when re-using the node ordering after random changes - * have been applied to the edge weights (like when considering traffic). - */ -@Disabled("for performance testing only") -public class TrafficChangeWithNodeOrderingReusingTest { - private static final Logger LOGGER = LoggerFactory.getLogger(TrafficChangeWithNodeOrderingReusingTest.class); - // make sure to increase xmx/xms for the JVM created by the surefire plugin in parent pom.xml when using bigger maps - private static final String OSM_FILE = "../core/files/monaco.osm.gz"; - - private static class Fixture { - private final int maxDeviationPercentage; - private final BaseGraph graph; - private final EncodingManager em; - private final OSMParsers osmParsers; - private final CHConfig baseCHConfig; - private final CHConfig trafficCHConfig; - - public Fixture(int maxDeviationPercentage) { - this.maxDeviationPercentage = maxDeviationPercentage; - BooleanEncodedValue accessEnc = VehicleAccess.create("car"); - DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, false); - em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); - CarAverageSpeedParser carParser = new CarAverageSpeedParser(em); - osmParsers = new OSMParsers() - .addWayTagParser(carParser); - baseCHConfig = CHConfig.nodeBased("base", CustomModelParser.createFastestWeighting(accessEnc, speedEnc, em)); - trafficCHConfig = CHConfig.nodeBased("traffic", new RandomDeviationWeighting(baseCHConfig.getWeighting(), accessEnc, speedEnc, maxDeviationPercentage)); - graph = new BaseGraph.Builder(em).create(); - } - - @Override - public String toString() { - return "maxDeviationPercentage=" + maxDeviationPercentage; - } - } - - private static class FixtureProvider implements ArgumentsProvider { - @Override - public Stream provideArguments(ExtensionContext context) { - return Stream.of( - new Fixture(0), - new Fixture(1), - new Fixture(5), - new Fixture(10), - new Fixture(50) - ).map(Arguments::of); - } - } - - @ParameterizedTest - @ArgumentsSource(FixtureProvider.class) - public void testPerformanceForRandomTrafficChange(Fixture f) throws IOException { - final long seed = 2139960664L; - final int numQueries = 50_000; - - LOGGER.info("Running performance test, max deviation percentage: " + f.maxDeviationPercentage); - // read osm - OSMReader reader = new OSMReader(f.graph, f.osmParsers, new OSMReaderConfig()); - reader.setFile(new File(OSM_FILE)); - reader.readGraph(); - f.graph.freeze(); - - // create CH - PrepareContractionHierarchies basePch = PrepareContractionHierarchies.fromGraph(f.graph, f.baseCHConfig); - PrepareContractionHierarchies.Result res = basePch.doWork(); - - // check correctness & performance - checkCorrectness(f.graph, res.getCHStorage(), f.baseCHConfig, seed, 100); - runPerformanceTest(f.graph, res.getCHStorage(), f.baseCHConfig, seed, numQueries); - - // now we re-use the contraction order from the previous contraction and re-run it with the traffic weighting - PrepareContractionHierarchies trafficPch = PrepareContractionHierarchies.fromGraph(f.graph, f.trafficCHConfig) - .useFixedNodeOrdering(res.getCHStorage().getNodeOrderingProvider()); - res = trafficPch.doWork(); - - // check correctness & performance - checkCorrectness(f.graph, res.getCHStorage(), f.trafficCHConfig, seed, 100); - runPerformanceTest(f.graph, res.getCHStorage(), f.trafficCHConfig, seed, numQueries); - } - - private static void checkCorrectness(BaseGraph graph, CHStorage chStorage, CHConfig chConfig, long seed, long numQueries) { - LOGGER.info("checking correctness"); - RoutingCHGraph chGraph = RoutingCHGraphImpl.fromGraph(graph, chStorage, chConfig); - Random rnd = new Random(seed); - int numFails = 0; - for (int i = 0; i < numQueries; ++i) { - Dijkstra dijkstra = new Dijkstra(graph, chConfig.getWeighting(), TraversalMode.NODE_BASED); - RoutingAlgorithm chAlgo = new CHRoutingAlgorithmFactory(chGraph).createAlgo(new PMap()); - - int from = rnd.nextInt(graph.getNodes()); - int to = rnd.nextInt(graph.getNodes()); - double dijkstraWeight = dijkstra.calcPath(from, to).getWeight(); - double chWeight = chAlgo.calcPath(from, to).getWeight(); - double error = Math.abs(dijkstraWeight - chWeight); - if (error > 1) { - System.out.println("failure from " + from + " to " + to + " dijkstra: " + dijkstraWeight + " ch: " + chWeight); - numFails++; - } - } - LOGGER.info("number of failed queries: " + numFails); - assertEquals(0, numFails); - } - - private static void runPerformanceTest(final BaseGraph graph, CHStorage chStorage, CHConfig chConfig, long seed, final int iterations) { - final int numNodes = graph.getNodes(); - RoutingCHGraph chGraph = RoutingCHGraphImpl.fromGraph(graph, chStorage, chConfig); - final Random random = new Random(seed); - - LOGGER.info("Running performance test, seed = {}", seed); - final double[] distAndWeight = {0.0, 0.0}; - MiniPerfTest performanceTest = new MiniPerfTest(); - performanceTest.setIterations(iterations).start(new MiniPerfTest.Task() { - private long queryTime; - - @Override - public int doCalc(boolean warmup, int run) { - if (!warmup && run % 1000 == 0) { - LOGGER.debug("Finished {} of {} runs. {}", run, iterations, - run > 0 ? String.format(Locale.ROOT, " Time: %6.2fms", queryTime * 1.e-6 / run) : ""); - } - if (run == iterations - 1) { - String avg = fmt(queryTime * 1.e-6 / run); - LOGGER.debug("Finished all ({}) runs, avg time: {}ms", iterations, avg); - } - int from = random.nextInt(numNodes); - int to = random.nextInt(numNodes); - long start = nanoTime(); - RoutingAlgorithm algo = new CHRoutingAlgorithmFactory(chGraph).createAlgo(new PMap()); - Path path = algo.calcPath(from, to); - if (!warmup && !path.isFound()) - return 1; - - if (!warmup) { - queryTime += nanoTime() - start; - double distance = path.getDistance(); - double weight = path.getWeight(); - distAndWeight[0] += distance; - distAndWeight[1] += weight; - } - return 0; - } - }); - if (performanceTest.getDummySum() > 0.5 * iterations) { - throw new IllegalStateException("too many errors, probably something is wrong"); - } - LOGGER.info("Total distance: {}, total weight: {}", distAndWeight[0], distAndWeight[1]); - LOGGER.info("Average query time: {}ms", performanceTest.getMean()); - } - - private static String fmt(double number) { - return String.format(Locale.ROOT, "%.2f", number); - } - - /** - * Wraps another weighting and applies random weight deviations to it. - * Do not use with AStar/Landmarks! - */ - private static class RandomDeviationWeighting extends AbstractWeighting { - private final Weighting baseWeighting; - private final double maxDeviationPercentage; - - public RandomDeviationWeighting(Weighting baseWeighting, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc, double maxDeviationPercentage) { - super(accessEnc, speedEnc, TurnCostProvider.NO_TURN_COST_PROVIDER); - this.baseWeighting = baseWeighting; - this.maxDeviationPercentage = maxDeviationPercentage; - } - - @Override - public double calcMinWeightPerDistance() { - // left as is, ok for now, but do not use with astar, at least as long as deviations can be negative!! - return this.baseWeighting.calcMinWeightPerDistance(); - } - - @Override - public double calcEdgeWeight(EdgeIteratorState edgeState, boolean reverse) { - double baseWeight = baseWeighting.calcEdgeWeight(edgeState, reverse); - if (Double.isInfinite(baseWeight)) { - // we are not touching this, might happen when speed is 0 ? - return baseWeight; - } - // apply a random (but deterministic) weight deviation - deviation may not depend on reverse flag! - long seed = edgeState.getEdge(); - Random rnd = new Random(seed); - double deviation = 2 * (rnd.nextDouble() - 0.5) * baseWeight * maxDeviationPercentage / 100; - double result = baseWeight + deviation; - if (result < 0) { - throw new IllegalStateException("negative weights are not allowed: " + result); - } - return result; - } - - @Override - public String getName() { - return "random_deviation"; - } - } -} diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java index a23638ed5ee..0f538520a9d 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/CHPreparationGraphTest.java @@ -58,8 +58,8 @@ void basic() { @Test void useLargeEdgeId() { CHPreparationGraph.OrigGraph.Builder builder = new CHPreparationGraph.OrigGraph.Builder(); - int largeEdgeID = Integer.MAX_VALUE >> 2; - assertEquals(536_870_911, largeEdgeID); + int largeEdgeID = Integer.MAX_VALUE >> 1; + assertEquals(1_073_741_823, largeEdgeID); // 0->1 builder.addEdge(0, 1, largeEdgeID, true, false); CHPreparationGraph.OrigGraph g = builder.build(); @@ -72,6 +72,6 @@ void useLargeEdgeId() { IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> new CHPreparationGraph.OrigGraph.Builder().addEdge(0, 1, largeEdgeID + 1, true, false) ); - assertTrue(e.getMessage().contains("Maximum edge key exceeded: 1073741824, max: 1073741823"), e.getMessage()); + assertTrue(e.getMessage().contains("Maximum node or edge key exceeded: -2147483648, max: 2147483647"), e.getMessage()); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java index 38908d387da..fd90e8d65a4 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/CHTurnCostTest.java @@ -24,7 +24,6 @@ import com.graphhopper.routing.RoutingAlgorithm; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.EncodedValueLookup; import com.graphhopper.routing.ev.TurnCost; import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.querygraph.QueryRoutingCHGraph; @@ -265,7 +264,7 @@ public void testFindPath_chain() { // we contract the graph such that only a few shortcuts are created and that the fwd/bwd searches for the // 0-8 query meet at node 4 (make sure we include all three cases where turn cost times might come to play: // fwd/bwd search and meeting point) - checkPathUsingCH(ArrayUtil.iota(9), 8, 9, 0, 8, new int[]{1, 3, 5, 7, 0, 8, 2, 6, 4}); + checkPathUsingCH(ArrayUtil.iota(9), 80, 90, 0, 8, new int[]{1, 3, 5, 7, 0, 8, 2, 6, 4}); } @Test @@ -298,11 +297,11 @@ public void testFindPath_bidir_chain() { Path pathFwd = createAlgo().calcPath(0, 6); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5, 6), pathFwd.calcNodes()); - assertEquals(6 + 15, pathFwd.getWeight(), 1.e-6); + assertEquals(60 + 150, pathFwd.getWeight(), 1.e-6); Path pathBwd = createAlgo().calcPath(6, 0); assertEquals(IntArrayList.from(6, 5, 4, 3, 2, 1, 0), pathBwd.calcNodes()); - assertEquals(6 + 10, pathBwd.getWeight(), 1.e-6); + assertEquals(60 + 100, pathBwd.getWeight(), 1.e-6); } @@ -778,7 +777,7 @@ public void test_issue1593_full(String algo) { // cannot go 3-4-1 setRestriction(edge0, edge3, 4); graph.freeze(); - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); List points = Arrays.asList( // 8 (on edge4) @@ -840,7 +839,7 @@ public void test_issue_1593_simple(String algo) { // we have to pay attention when there are virtual nodes: turning from the shortcut 3-5 onto the // virtual edge 5-x should be forbidden. - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); Snap snap = index.findClosest(0.1, 0.15, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(graph, snap); @@ -863,7 +862,7 @@ public void testRouteViaVirtualNode(String algo) { updateDistancesFor(graph, 2, 0.03, 0.03); graph.freeze(); automaticPrepareCH(); - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); Snap snap = index.findClosest(0.01, 0.01, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(graph, snap); @@ -891,7 +890,7 @@ public void testRouteViaVirtualNode_withAlternative(String algo) { updateDistancesFor(graph, 2, 0.00, 0.02); graph.freeze(); automaticPrepareCH(); - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); Snap snap = index.findClosest(0.01, 0.01, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(graph, snap); @@ -911,12 +910,12 @@ public void testFiniteUTurnCost_virtualViaNode(String algo) { // 4->3->2->1-x-0 // | // 5->6 - graph.edge(4, 3).setDistance(00).set(speedEnc, 10, 0); - graph.edge(3, 2).setDistance(00).set(speedEnc, 10, 0); - graph.edge(2, 1).setDistance(00).set(speedEnc, 10, 0); - graph.edge(1, 0).setDistance(00).set(speedEnc, 10, 10); - graph.edge(1, 5).setDistance(00).set(speedEnc, 10, 0); - graph.edge(5, 6).setDistance(00).set(speedEnc, 10, 0); + graph.edge(4, 3).setDistance(0).set(speedEnc, 10, 0); + graph.edge(3, 2).setDistance(0).set(speedEnc, 10, 0); + graph.edge(2, 1).setDistance(0).set(speedEnc, 10, 0); + graph.edge(1, 0).setDistance(0).set(speedEnc, 10, 10); + graph.edge(1, 5).setDistance(0).set(speedEnc, 10, 0); + graph.edge(5, 6).setDistance(0).set(speedEnc, 10, 0); updateDistancesFor(graph, 4, 0.1, 0.0); updateDistancesFor(graph, 3, 0.1, 0.1); updateDistancesFor(graph, 2, 0.1, 0.2); @@ -929,7 +928,7 @@ public void testFiniteUTurnCost_virtualViaNode(String algo) { graph.freeze(); chConfig = chConfigs.get(2); prepareCH(0, 1, 2, 3, 4, 5, 6); - LocationIndexTree index = new LocationIndexTree(graph, new RAMDirectory()); + LocationIndexTree index = new LocationIndexTree(graph, new GHDirectory("", DAType.RAM)); index.prepareIndex(); GHPoint virtualPoint = new GHPoint(0.1, 0.35); Snap snap = index.findClosest(virtualPoint.lat, virtualPoint.lon, EdgeFilter.ALL_EDGES); @@ -1081,8 +1080,7 @@ public void testFindPath_random_compareWithDijkstra_zeroUTurnCost() { private void compareWithDijkstraOnRandomGraph(long seed) { final Random rnd = new Random(seed); - // for larger graphs preparation takes much longer the higher the degree is! - GHUtility.buildRandomGraph(graph, rnd, 20, 3.0, true, speedEnc, null, 0.9, 0.8); + RandomGraph.start().seed(seed).nodes(20).curviness(0.1).speedZero(0.1).fill(graph, speedEnc); GHUtility.addRandomTurnCosts(graph, seed, null, turnCostEnc, maxCost, turnCostStorage); graph.freeze(); checkStrict = false; @@ -1109,7 +1107,7 @@ public void testFindPath_heuristic_compareWithDijkstra_finiteUTurnCost() { } private void compareWithDijkstraOnRandomGraph_heuristic(long seed) { - GHUtility.buildRandomGraph(graph, new Random(seed), 20, 3.0, true, speedEnc, null, 0.9, 0.8); + RandomGraph.start().seed(seed).nodes(20).curviness(0.1).speedZero(0.1).fill(graph, speedEnc); GHUtility.addRandomTurnCosts(graph, seed, null, turnCostEnc, maxCost, turnCostStorage); graph.freeze(); checkStrict = false; @@ -1131,15 +1129,17 @@ private void checkPathUsingRandomContractionOrder(IntArrayList expectedPath, int } private void checkPath(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to, int[] contractionOrder) { - checkPathUsingDijkstra(expectedPath, expectedEdgeWeight, expectedTurnCosts, from, to); - checkPathUsingCH(expectedPath, expectedEdgeWeight, expectedTurnCosts, from, to, contractionOrder); + // todo: move out x10 + checkPathUsingDijkstra(expectedPath, expectedEdgeWeight * 10, expectedTurnCosts * 10, from, to); + checkPathUsingCH(expectedPath, expectedEdgeWeight * 10, expectedTurnCosts * 10, from, to, contractionOrder); } private void checkPathUsingDijkstra(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to) { Path dijkstraPath = findPathUsingDijkstra(from, to); int expectedWeight = expectedEdgeWeight + expectedTurnCosts; - int expectedDistance = expectedEdgeWeight * 10; - int expectedTime = (expectedEdgeWeight + expectedTurnCosts) * 1000; + int expectedDistance = expectedEdgeWeight; + // todo: move out x10 + int expectedTime = (expectedEdgeWeight / 10 + expectedTurnCosts / 10) * 1000; assertEquals(expectedPath, dijkstraPath.calcNodes(), "Normal Dijkstra did not find expected path."); assertEquals(expectedWeight, dijkstraPath.getWeight(), 1.e-6, "Normal Dijkstra did not calculate expected weight."); assertEquals(expectedDistance, dijkstraPath.getDistance(), 1.e-6, "Normal Dijkstra did not calculate expected distance."); @@ -1149,8 +1149,9 @@ private void checkPathUsingDijkstra(IntArrayList expectedPath, int expectedEdgeW private void checkPathUsingCH(IntArrayList expectedPath, int expectedEdgeWeight, int expectedTurnCosts, int from, int to, int[] contractionOrder) { Path chPath = findPathUsingCH(from, to, contractionOrder); int expectedWeight = expectedEdgeWeight + expectedTurnCosts; - int expectedDistance = expectedEdgeWeight * 10; - int expectedTime = (expectedEdgeWeight + expectedTurnCosts) * 1000; + int expectedDistance = expectedEdgeWeight; + // todo: move out x10 + int expectedTime = (expectedEdgeWeight / 10 + expectedTurnCosts / 10) * 1000; assertEquals(expectedPath, chPath.calcNodes(), "Contraction Hierarchies did not find expected path. contraction order=" + Arrays.toString(contractionOrder)); assertEquals(expectedWeight, chPath.getWeight(), 1.e-6, "Contraction Hierarchies did not calculate expected weight."); assertEquals(expectedDistance, chPath.getDistance(), 1.e-6, "Contraction Hierarchies did not calculate expected distance."); diff --git a/core/src/test/java/com/graphhopper/routing/ch/EdgeBasedNodeContractorTest.java b/core/src/test/java/com/graphhopper/routing/ch/EdgeBasedNodeContractorTest.java index 112dffccb71..266afdca4c3 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/EdgeBasedNodeContractorTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/EdgeBasedNodeContractorTest.java @@ -100,9 +100,9 @@ public void testContractNodes_simpleLoop() { contractNodes(5, 6, 3, 2, 9, 1, 8, 4, 7, 0); checkShortcuts( - createShortcut(2, 8, edge8to3, edge3to2, 5, false, true), - createShortcut(8, 7, edge8to3.getEdgeKey(), edge2to7.getEdgeKey(), 6, edge2to7.getEdge(), 6, true, false), - createShortcut(7, 7, edge7to8.getEdgeKey(), edge2to7.getEdgeKey(), edge7to8.getEdge(), 7, 8, true, false) + createShortcut(2, 8, edge8to3, edge3to2, 50, false, true), + createShortcut(8, 7, edge8to3.getEdgeKey(), edge2to7.getEdgeKey(), 6, edge2to7.getEdge(), 60, true, false), + createShortcut(7, 7, edge7to8.getEdgeKey(), edge2to7.getEdgeKey(), edge7to8.getEdge(), 7, 80, true, false) ); } @@ -127,12 +127,12 @@ public void testContractNodes_necessaryAlternative() { contractAllNodesInOrder(); checkShortcuts( // from contracting node 0: need a shortcut because of turn restriction - createShortcut(3, 6, e6to0, e0to3, 9, false, true), + createShortcut(3, 6, e6to0, e0to3, 90, false, true), // from contracting node 3: two shortcuts: // 1) in case we come from 1->6 (cant turn left) // 2) in case we come from 2->6 (going via node 0 would be more expensive) - createShortcut(5, 6, e6to0.getEdgeKey(), e3to5.getEdgeKey(), 7, e3to5.getEdge(), 11, false, true), - createShortcut(5, 6, e6to3, e3to5, 3, false, true) + createShortcut(5, 6, e6to0.getEdgeKey(), e3to5.getEdgeKey(), 7, e3to5.getEdge(), 110, false, true), + createShortcut(5, 6, e6to3, e3to5, 30, false, true) ); } @@ -152,12 +152,12 @@ public void testContractNodes_alternativeNecessary_noUTurn() { contractAllNodesInOrder(); checkShortcuts( // from contraction of node 0 - createShortcut(2, 4, e0to4, e0to2, 8, false, true), + createShortcut(2, 4, e0to4, e0to2, 80, false, true), // from contraction of node 2 // It might look like it is always better to go directly from 4 to 2, but when we come from edge (2->4) // we may not do a u-turn at 4. - createShortcut(3, 4, e0to4.getEdgeKey(), e2to3.getEdgeKey(), 5, e2to3.getEdge(), 10, false, true), - createShortcut(3, 4, e2to4, e2to3, 4, false, true) + createShortcut(3, 4, e0to4.getEdgeKey(), e2to3.getEdgeKey(), 5, e2to3.getEdge(), 100, false, true), + createShortcut(3, 4, e2to4, e2to3, 40, false, true) ); } @@ -186,12 +186,12 @@ public void testContractNodes_bidirectionalLoop() { contractAllNodesInOrder(); checkShortcuts( // from contraction of node 3 - createShortcut(4, 6, e3to4.detach(true), e6to3.detach(true), 6, true, false), - createShortcut(4, 6, e6to3, e3to4, 4, false, true), + createShortcut(4, 6, e3to4.detach(true), e6to3.detach(true), 60, true, false), + createShortcut(4, 6, e6to3, e3to4, 40, false, true), // from contraction of node 4 // two 'parallel' shortcuts to preserve shortest paths to 5 when coming from 4->6 and 3->6 !! - createShortcut(5, 6, e6to3.getEdgeKey(), e4to5.getEdgeKey(), 8, e4to5.getEdge(), 5, false, true), - createShortcut(5, 6, e4to6.detach(true), e4to5, 3, false, true) + createShortcut(5, 6, e6to3.getEdgeKey(), e4to5.getEdgeKey(), 8, e4to5.getEdge(), 50, false, true), + createShortcut(5, 6, e4to6.detach(true), e4to5, 30, false, true) ); } @@ -261,7 +261,7 @@ public void testContractNode_twoNormalEdges_noTurncosts() { contractNode(nodeContractor, 3, 3); contractNode(nodeContractor, 4, 4); nodeContractor.finishContraction(); - checkShortcuts(createShortcut(3, 4, e3to2, e2to4, 8)); + checkShortcuts(createShortcut(3, 4, e3to2, e2to4, 80)); } @Test @@ -321,7 +321,7 @@ public void testContractNode_duplicateOutgoingEdges_differentWeight() { contractNodes(2, 0, 4, 1, 3); // there should be only one shortcut checkShortcuts( - createShortcut(1, 3, 2, 6, 1, 3, 2) + createShortcut(1, 3, 2, 6, 1, 3, 20) ); } @@ -338,7 +338,7 @@ public void testContractNode_duplicateIncomingEdges_differentWeight() { setMaxLevelOnAllNodes(); contractNodes(2, 0, 4, 1, 3); checkShortcuts( - createShortcut(1, 3, 4, 6, 2, 3, 2) + createShortcut(1, 3, 4, 6, 2, 3, 20) ); } @@ -388,7 +388,7 @@ public void testContractNode_twoNormalEdges_withTurnCost() { freeze(); setMaxLevelOnAllNodes(); contractNodes(2, 0, 1, 3, 4); - checkShortcuts(createShortcut(3, 4, e3to2, e2to4, 12)); + checkShortcuts(createShortcut(3, 4, e3to2, e2to4, 120)); } @Test @@ -421,8 +421,8 @@ public void testContractNode_twoNormalEdges_bidirectional() { // note that for now we add a shortcut for each direction. using fwd/bwd flags would be more efficient, // but requires a more sophisticated way to determine the 'first' and 'last' original edges at various // places - createShortcut(3, 4, 2, 4, 1, 2, 12, true, false), - createShortcut(3, 4, 5, 3, 2, 1, 12, false, true) + createShortcut(3, 4, 2, 4, 1, 2, 120, true, false), + createShortcut(3, 4, 5, 3, 2, 1, 120, false, true) ); } @@ -439,8 +439,8 @@ public void testContractNode_twoNormalEdges_bidirectional_differentCosts() { setMaxLevelOnAllNodes(); contractNodes(2, 0, 1, 3, 4); checkShortcuts( - createShortcut(3, 4, e3to2, e2to4, 12, true, false), - createShortcut(3, 4, e2to4.detach(true), e3to2.detach(true), 15, false, true) + createShortcut(3, 4, e3to2, e2to4, 120, true, false), + createShortcut(3, 4, e2to4.detach(true), e3to2.detach(true), 150, false, true) ); } @@ -475,8 +475,8 @@ public void testContractNode_shortcutDoesNotSpanUTurn() { contractNodes(3, 4, 2, 6, 7, 5, 1); checkShortcuts( // from contracting node 3 - createShortcut(4, 7, e7to3, e3to4, 3, false, true), - createShortcut(4, 5, e3to4.detach(true), e3to5, 3, true, false) + createShortcut(4, 7, e7to3, e3to4, 30, false, true), + createShortcut(4, 5, e3to4.detach(true), e3to5, 30, true, false) // important! no shortcut from 7 to 5 when contracting node 4, because it includes a u-turn ); } @@ -486,7 +486,7 @@ public void testContractNode_multiple_loops_directTurnIsBest() { // turning on any of the loops is restricted so we take the direct turn -> one extra shortcuts GraphWithTwoLoops g = new GraphWithTwoLoops(maxCost, maxCost, 1, 2, 3, 4); g.contractAndCheckShortcuts( - createShortcut(7, 8, g.e7to6, g.e6to8, 11, true, false)); + createShortcut(7, 8, g.e7to6, g.e6to8, 110, true, false)); } @Test @@ -494,8 +494,8 @@ public void testContractNode_multiple_loops_leftLoopIsBest() { // direct turn is restricted, so we take the left loop -> two extra shortcuts GraphWithTwoLoops g = new GraphWithTwoLoops(2, maxCost, 1, 2, 3, maxCost); g.contractAndCheckShortcuts( - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(3), 12, false, true), - createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 20, true, false) + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(3), 120, false, true), + createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 200, true, false) ); } @@ -504,8 +504,8 @@ public void testContractNode_multiple_loops_rightLoopIsBest() { // direct turn is restricted, going on left loop is expensive, so we take the right loop -> two extra shortcuts GraphWithTwoLoops g = new GraphWithTwoLoops(8, 1, 1, 2, 3, maxCost); g.contractAndCheckShortcuts( - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(2), 12, false, true), - createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 21, true, false) + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(2), 120, false, true), + createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 210, true, false) ); } @@ -514,9 +514,9 @@ public void testContractNode_multiple_loops_leftRightLoopIsBest() { // multiple turns are restricted, it is best to take the left and the right loop -> three extra shortcuts GraphWithTwoLoops g = new GraphWithTwoLoops(3, maxCost, 1, maxCost, 3, maxCost); g.contractAndCheckShortcuts( - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(3), 13, false, true), - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.getScEdge(5), g.getScEdge(2), 24, false, true), - createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 33, true, false) + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(3), 130, false, true), + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.getScEdge(5), g.getScEdge(2), 240, false, true), + createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 330, true, false) ); } @@ -525,9 +525,9 @@ public void testContractNode_multiple_loops_rightLeftLoopIsBest() { // multiple turns are restricted, it is best to take the right and the left loop -> three extra shortcuts GraphWithTwoLoops g = new GraphWithTwoLoops(maxCost, 5, 4, 2, maxCost, maxCost); g.contractAndCheckShortcuts( - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(2), 16, false, true), - createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.getScEdge(5), g.getScEdge(3), 25, false, true), - createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 33, true, false) + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e3to6.getEdgeKey(), g.e7to6.getEdge(), g.getScEdge(2), 160, false, true), + createShortcut(6, 7, g.e7to6.getEdgeKey(), g.e1to6.getEdgeKey(), g.getScEdge(5), g.getScEdge(3), 250, false, true), + createShortcut(7, 8, g.e7to6.getEdgeKey(), g.e6to8.getEdgeKey(), g.getScEdge(4), g.e6to8.getEdge(), 330, true, false) ); } @@ -573,10 +573,10 @@ private void contractAndCheckShortcuts(Shortcut... shortcuts) { contractNodes(0, 1, 2, 3, 4, 5, 6, 9, 10, 7, 8); HashSet expectedShortcuts = new HashSet<>(); expectedShortcuts.addAll(Arrays.asList( - createShortcut(1, 6, e6to0, e0to1, 7, false, true), - createShortcut(6, 6, e6to0.getEdgeKey(), e1to6.getEdgeKey(), getScEdge(0), e1to6.getEdge(), 9, true, false), - createShortcut(3, 6, e6to2, e2to3, 3, false, true), - createShortcut(6, 6, e6to2.getEdgeKey(), e3to6.getEdgeKey(), getScEdge(1), e3to6.getEdge(), 10, true, false) + createShortcut(1, 6, e6to0, e0to1, 70, false, true), + createShortcut(6, 6, e6to0.getEdgeKey(), e1to6.getEdgeKey(), getScEdge(0), e1to6.getEdge(), 90, true, false), + createShortcut(3, 6, e6to2, e2to3, 30, false, true), + createShortcut(6, 6, e6to2.getEdgeKey(), e3to6.getEdgeKey(), getScEdge(1), e3to6.getEdge(), 100, true, false) )); expectedShortcuts.addAll(Arrays.asList(shortcuts)); checkShortcuts(expectedShortcuts); @@ -594,7 +594,7 @@ public void testContractNode_detour_detourIsBetter() { GraphWithDetour g = new GraphWithDetour(2, 9, 5, 1); contractNodes(0, 4, 3, 1, 2); checkShortcuts( - createShortcut(1, 2, g.e1to0, g.e0to2, 7) + createShortcut(1, 2, g.e1to0, g.e0to2, 70) ); } @@ -631,7 +631,7 @@ private class GraphWithDetour { public void testContractNode_detour_multipleInOut_needsShortcut() { GraphWithDetourMultipleInOutEdges g = new GraphWithDetourMultipleInOutEdges(0, 0, 0, 1, 3); contractNodes(0, 2, 5, 6, 7, 1, 3, 4); - checkShortcuts(createShortcut(1, 4, g.e1to0, g.e0to4, 7)); + checkShortcuts(createShortcut(1, 4, g.e1to0, g.e0to4, 70)); } @Test @@ -680,8 +680,8 @@ public void testContractNode_loopAvoidance_loopNecessary() { contractNodes(0, 1, 3, 4, 5, 2); final int numEdges = 6; checkShortcuts( - createShortcut(1, 2, g.e2to0, g.e0to1, 3, false, true), - createShortcut(2, 2, g.e2to0.getEdgeKey(), g.e1to2.getEdgeKey(), numEdges, g.e1to2.getEdge(), 4, true, false) + createShortcut(1, 2, g.e2to0, g.e0to1, 30, false, true), + createShortcut(2, 2, g.e2to0.getEdgeKey(), g.e1to2.getEdgeKey(), numEdges, g.e1to2.getEdge(), 40, true, false) ); } @@ -691,7 +691,7 @@ public void testContractNode_loopAvoidance_loopAvoidable() { GraphWithLoop g = new GraphWithLoop(3); contractNodes(0, 1, 3, 4, 5, 2); checkShortcuts( - createShortcut(1, 2, g.e2to0, g.e0to1, 3, false, true) + createShortcut(1, 2, g.e2to0, g.e0to1, 30, false, true) ); } @@ -761,8 +761,8 @@ public void testContractNode_noUnnecessaryShortcut_witnessPathOfEqualWeight() { // path, or (depending on the implementation-specific edge traversal order) the original path does *not* // update/overwrite the already found witness path. checkShortcuts( - createShortcut(2, 4, e2to3, e3to4, 2), - createShortcut(5, 4, e5to3, e3to4, 2) + createShortcut(2, 4, e2to3, e3to4, 20), + createShortcut(5, 4, e5to3, e3to4, 20) ); } @@ -846,7 +846,7 @@ public void testContractNode_bidirectional_edge_at_fromNode(boolean edge1to2bidi setMaxLevelOnAllNodes(); contractNodes(2, 0, 1, 5, 4, 3); // we might come from (5->1) so we still need a way back to (3->4) -> we need a shortcut - Shortcut expectedShortcuts = createShortcut(1, 3, 2, 4, 1, 2, 2); + Shortcut expectedShortcuts = createShortcut(1, 3, 2, 4, 1, 2, 20); checkShortcuts(expectedShortcuts); } @@ -890,12 +890,12 @@ public void testNodeContraction_directWitness() { contractNodes(2, 6, 3, 5, 4, 0, 8, 10, 11, 1, 7, 9); // note that the shortcut edge ids depend on the insertion order which might change when changing the implementation checkShortcuts( - createShortcut(3, 1, 2, 4, 1, 2, 2, false, true), - createShortcut(1, 9, 2, 16, 1, 8, 2, true, false), - createShortcut(5, 7, 10, 12, 5, 6, 2, true, false), - createShortcut(7, 9, 18, 12, 9, 6, 2, false, true), - createShortcut(4, 1, 2, 6, 12, 3, 3, false, true), - createShortcut(4, 7, 8, 12, 4, 13, 3, true, false) + createShortcut(3, 1, 2, 4, 1, 2, 20, false, true), + createShortcut(1, 9, 2, 16, 1, 8, 20, true, false), + createShortcut(5, 7, 10, 12, 5, 6, 20, true, false), + createShortcut(7, 9, 18, 12, 9, 6, 20, false, true), + createShortcut(4, 1, 2, 6, 12, 3, 30, false, true), + createShortcut(4, 7, 8, 12, 4, 13, 30, true, false) ); } @@ -941,7 +941,7 @@ public void testNodeContraction_letShortcutsWitnessEachOther_twoIn() { setMaxLevelOnAllNodes(); contractNodes(3, 0, 5, 1, 4, 2); checkShortcuts( - createShortcut(4, 2, 4, 6, 2, 3, 2, false, true) + createShortcut(4, 2, 4, 6, 2, 3, 20, false, true) ); } @@ -965,7 +965,7 @@ public void testNodeContraction_letShortcutsWitnessEachOther_twoOut() { setMaxLevelOnAllNodes(); contractNodes(2, 0, 5, 1, 4, 3); checkShortcuts( - createShortcut(1, 3, 2, 4, 1, 2, 2) + createShortcut(1, 3, 2, 4, 1, 2, 20) ); } @@ -983,7 +983,7 @@ public void testNodeContraction_parallelEdges_onlyOneLoopShortcutNeeded() { contractNodes(0, 2, 1); // it is sufficient to be able to travel the 1-0-1 loop in one (the cheaper) direction checkShortcuts( - createShortcut(1, 1, 1, 3, 0, 1, 7) + createShortcut(1, 1, 1, 3, 0, 1, 70) ); } @@ -1013,18 +1013,18 @@ public void testNodeContraction_duplicateEdge_severalLoops() { checkNumShortcuts(11); checkShortcuts( // from node 4 contraction - createShortcut(5, 3, 11, 9, 5, 4, 66, true, false), - createShortcut(5, 3, 8, 10, 4, 5, 66, false, true), - createShortcut(3, 2, 2, 9, 1, 4, 29, false, true), - createShortcut(3, 2, 8, 3, 4, 1, 29, true, false), - createShortcut(5, 2, 2, 10, 1, 5, 75, false, true), - createShortcut(5, 2, 11, 3, 5, 1, 75, true, false), + createShortcut(5, 3, 11, 9, 5, 4, 660, true, false), + createShortcut(5, 3, 8, 10, 4, 5, 660, false, true), + createShortcut(3, 2, 2, 9, 1, 4, 290, false, true), + createShortcut(3, 2, 8, 3, 4, 1, 290, true, false), + createShortcut(5, 2, 2, 10, 1, 5, 750, false, true), + createShortcut(5, 2, 11, 3, 5, 1, 750, true, false), // from node 5 contraction - createShortcut(2, 2, 6, 5, 3, 2, 99, true, false), - createShortcut(2, 2, 6, 3, 3, 6, 134, true, false), - createShortcut(2, 2, 2, 5, 8, 2, 114, true, false), - createShortcut(3, 2, 4, 9, 2, 7, 106, false, true), - createShortcut(3, 2, 8, 5, 9, 2, 105, true, false) + createShortcut(2, 2, 6, 5, 3, 2, 990, true, false), + createShortcut(2, 2, 6, 3, 3, 6, 1340, true, false), + createShortcut(2, 2, 2, 5, 8, 2, 1140, true, false), + createShortcut(3, 2, 4, 9, 2, 7, 1060, false, true), + createShortcut(3, 2, 8, 5, 9, 2, 1050, true, false) ); } @@ -1037,9 +1037,9 @@ public void testNodeContraction_tripleConnection() { setMaxLevelOnAllNodes(); contractNodes(1, 0); checkShortcuts( - createShortcut(0, 0, 2, 5, 1, 2, 5.5), - createShortcut(0, 0, 0, 5, 0, 2, 4.5), - createShortcut(0, 0, 0, 3, 0, 1, 3.0) + createShortcut(0, 0, 2, 5, 1, 2, 55), + createShortcut(0, 0, 0, 5, 0, 2, 45), + createShortcut(0, 0, 0, 3, 0, 1, 30) ); } @@ -1081,8 +1081,8 @@ public void testNodeContraction_node_in_loop() { setTurnCost(3, 2, 4, 2); contractNodes(2, 0, 1, 4, 3); checkShortcuts( - createShortcut(4, 3, 7, 5, 3, 2, 6, true, false), - createShortcut(4, 3, 4, 6, 2, 3, 4, false, true) + createShortcut(4, 3, 7, 5, 3, 2, 60, true, false), + createShortcut(4, 3, 4, 6, 2, 3, 40, false, true) ); } @@ -1108,8 +1108,8 @@ public void testFindPath_finiteUTurnCost() { setRestriction(0, 3, 1); contractNodes(4, 0, 1, 2, 3); checkShortcuts( - createShortcut(2, 3, 2, 4, 1, 2, 600, false, true), - createShortcut(3, 3, 2, 3, 1, 1, 260, true, false) + createShortcut(2, 3, 2, 4, 1, 2, 6000, false, true), + createShortcut(3, 3, 2, 3, 1, 1, 2600, true, false) ); } @@ -1141,7 +1141,7 @@ public void testNodeContraction_minorWeightDeviation() { setMaxLevelOnAllNodes(); contractNodes(2, 0, 1, 3, 4); checkShortcuts( - createShortcut(1, 3, 2, 4, 1, 2, 145.847) + createShortcut(1, 3, 2, 4, 1, 2, 1458) ); } @@ -1182,8 +1182,8 @@ void issue_2564() { setMaxLevelOnAllNodes(); contractNodes(0, 5, 2, 1, 3, 4); checkShortcuts( - createShortcut(1, 3, 2, 4, 1, 2, 17.497, true, false), - createShortcut(1, 3, 5, 3, 2, 1, 17.497, false, true) + createShortcut(1, 3, 2, 4, 1, 2, 175, true, false), + createShortcut(1, 3, 5, 3, 2, 1, 175, false, true) ); } diff --git a/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java b/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java index 4995926a985..9d61ce210a0 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/NodeBasedNodeContractorTest.java @@ -167,8 +167,8 @@ public void testShortcutMergeBug(boolean reverse) { @Test public void testContractNode_directed_shortcutRequired() { // 0 --> 1 --> 2 - final EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 0); - final EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(2).set(speedEnc, 60, 0); + final EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(speedEnc, 60, 0); + final EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(200).set(speedEnc, 60, 0); freeze(); setMaxLevelOnAllNodes(); contractInOrder(1, 0, 2); @@ -178,8 +178,8 @@ public void testContractNode_directed_shortcutRequired() { @Test public void testContractNode_directed_shortcutRequired_reverse() { // 0 <-- 1 <-- 2 - final EdgeIteratorState edge1 = graph.edge(2, 1).setDistance(1).set(speedEnc, 60, 0); - final EdgeIteratorState edge2 = graph.edge(1, 0).setDistance(2).set(speedEnc, 60, 0); + final EdgeIteratorState edge1 = graph.edge(2, 1).setDistance(100).set(speedEnc, 60, 0); + final EdgeIteratorState edge2 = graph.edge(1, 0).setDistance(200).set(speedEnc, 60, 0); freeze(); setMaxLevelOnAllNodes(); contractInOrder(1, 2, 0); @@ -189,8 +189,8 @@ public void testContractNode_directed_shortcutRequired_reverse() { @Test public void testContractNode_bidirected_shortcutsRequired() { // 0 -- 1 -- 2 - final EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - final EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(2).set(speedEnc, 60, 60); + final EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + final EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(200).set(speedEnc, 60, 60); freeze(); contractInOrder(1, 2, 0); checkShortcuts(expectedShortcut(2, 0, edge2, edge1, true, true)); @@ -200,9 +200,9 @@ public void testContractNode_bidirected_shortcutsRequired() { public void testContractNode_directed_withWitness() { // 0 --> 1 --> 2 // \_________/ - graph.edge(0, 1).setDistance(1).set(speedEnc, 60, 0); - graph.edge(1, 2).setDistance(2).set(speedEnc, 60, 0); - graph.edge(0, 2).setDistance(1).set(speedEnc, 60, 0); + graph.edge(0, 1).setDistance(10).set(speedEnc, 60, 0); + graph.edge(1, 2).setDistance(200).set(speedEnc, 60, 0); + graph.edge(0, 2).setDistance(100).set(speedEnc, 60, 0); freeze(); setMaxLevelOnAllNodes(); createNodeContractor().contractNode(1); diff --git a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java index bb58e42b386..fff9429e632 100644 --- a/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java +++ b/core/src/test/java/com/graphhopper/routing/ch/PrepareContractionHierarchiesTest.java @@ -27,7 +27,6 @@ import com.graphhopper.routing.util.*; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.storage.*; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; @@ -54,51 +53,51 @@ public class PrepareContractionHierarchiesTest { // | | | // 17-16-...-11<-/ private static void initDirected2(Graph g, DecimalEncodedValue speedEnc) { - g.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - g.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - g.edge(4, 5).setDistance(1).set(speedEnc, 60, 60); - g.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - g.edge(6, 7).setDistance(1).set(speedEnc, 60, 60); - g.edge(7, 8).setDistance(1).set(speedEnc, 60, 60); - g.edge(8, 9).setDistance(1).set(speedEnc, 60, 60); - g.edge(9, 10).setDistance(1).set(speedEnc, 60, 60); - g.edge(10, 11).setDistance(1).set(speedEnc, 60, 0); - g.edge(11, 12).setDistance(1).set(speedEnc, 60, 60); - g.edge(11, 9).setDistance(3).set(speedEnc, 60, 0); - g.edge(12, 13).setDistance(1).set(speedEnc, 60, 60); - g.edge(13, 14).setDistance(1).set(speedEnc, 60, 60); - g.edge(14, 15).setDistance(1).set(speedEnc, 60, 60); - g.edge(15, 16).setDistance(1).set(speedEnc, 60, 60); - g.edge(16, 17).setDistance(1).set(speedEnc, 60, 60); - g.edge(17, 0).setDistance(1).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + g.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60); + g.edge(5, 6).setDistance(100).set(speedEnc, 60, 60); + g.edge(6, 7).setDistance(100).set(speedEnc, 60, 60); + g.edge(7, 8).setDistance(100).set(speedEnc, 60, 60); + g.edge(8, 9).setDistance(100).set(speedEnc, 60, 60); + g.edge(9, 10).setDistance(100).set(speedEnc, 60, 60); + g.edge(10, 11).setDistance(100).set(speedEnc, 60, 0); + g.edge(11, 12).setDistance(100).set(speedEnc, 60, 60); + g.edge(11, 9).setDistance(300).set(speedEnc, 60, 0); + g.edge(12, 13).setDistance(100).set(speedEnc, 60, 60); + g.edge(13, 14).setDistance(100).set(speedEnc, 60, 60); + g.edge(14, 15).setDistance(100).set(speedEnc, 60, 60); + g.edge(15, 16).setDistance(100).set(speedEnc, 60, 60); + g.edge(16, 17).setDistance(100).set(speedEnc, 60, 60); + g.edge(17, 0).setDistance(100).set(speedEnc, 60, 60); } // prepare-routing.svg private static void initShortcutsGraph(Graph g, DecimalEncodedValue speedEnc) { - g.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 2).setDistance(1).set(speedEnc, 60, 60); - g.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - g.edge(2, 3).setDistance(1.5).set(speedEnc, 60, 60); - g.edge(1, 4).setDistance(1).set(speedEnc, 60, 60); - g.edge(2, 9).setDistance(1).set(speedEnc, 60, 60); - g.edge(9, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(10, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(4, 5).setDistance(1).set(speedEnc, 60, 60); - g.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - g.edge(6, 7).setDistance(1).set(speedEnc, 60, 60); - g.edge(7, 8).setDistance(1).set(speedEnc, 60, 60); - g.edge(8, 9).setDistance(1).set(speedEnc, 60, 60); - g.edge(4, 11).setDistance(1).set(speedEnc, 60, 60); - g.edge(9, 14).setDistance(1).set(speedEnc, 60, 60); - g.edge(10, 14).setDistance(1).set(speedEnc, 60, 60); - g.edge(11, 12).setDistance(1).set(speedEnc, 60, 60); - g.edge(12, 15).setDistance(1).set(speedEnc, 60, 60); - g.edge(12, 13).setDistance(1).set(speedEnc, 60, 60); - g.edge(13, 16).setDistance(1).set(speedEnc, 60, 60); - g.edge(15, 16).setDistance(2).set(speedEnc, 60, 60); - g.edge(14, 16).setDistance(1).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 2).setDistance(100).set(speedEnc, 60, 60); + g.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + g.edge(2, 3).setDistance(150).set(speedEnc, 60, 60); + g.edge(1, 4).setDistance(100).set(speedEnc, 60, 60); + g.edge(2, 9).setDistance(100).set(speedEnc, 60, 60); + g.edge(9, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(10, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60); + g.edge(5, 6).setDistance(100).set(speedEnc, 60, 60); + g.edge(6, 7).setDistance(100).set(speedEnc, 60, 60); + g.edge(7, 8).setDistance(100).set(speedEnc, 60, 60); + g.edge(8, 9).setDistance(100).set(speedEnc, 60, 60); + g.edge(4, 11).setDistance(100).set(speedEnc, 60, 60); + g.edge(9, 14).setDistance(100).set(speedEnc, 60, 60); + g.edge(10, 14).setDistance(100).set(speedEnc, 60, 60); + g.edge(11, 12).setDistance(100).set(speedEnc, 60, 60); + g.edge(12, 15).setDistance(100).set(speedEnc, 60, 60); + g.edge(12, 13).setDistance(100).set(speedEnc, 60, 60); + g.edge(13, 16).setDistance(100).set(speedEnc, 60, 60); + g.edge(15, 16).setDistance(200).set(speedEnc, 60, 60); + g.edge(14, 16).setDistance(100).set(speedEnc, 60, 60); } private static void initExampleGraph(Graph g, DecimalEncodedValue speedEnc) { @@ -108,13 +107,13 @@ private static void initExampleGraph(Graph g, DecimalEncodedValue speedEnc) { // / | // 4-----3 // - g.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 2).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 4).setDistance(3).set(speedEnc, 60, 60); - g.edge(1, 2).setDistance(3).set(speedEnc, 60, 60); - g.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(4, 3).setDistance(2).set(speedEnc, 60, 60); - g.edge(5, 1).setDistance(2).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 2).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 4).setDistance(300).set(speedEnc, 60, 60); + g.edge(1, 2).setDistance(300).set(speedEnc, 60, 60); + g.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(4, 3).setDistance(200).set(speedEnc, 60, 60); + g.edge(5, 1).setDistance(200).set(speedEnc, 60, 60); } @BeforeEach @@ -153,12 +152,12 @@ public void testMoreComplexGraph() { @Test public void testDirectedGraph() { - g.edge(5, 4).setDistance(3).set(speedEnc, 60, 0); - g.edge(4, 5).setDistance(10).set(speedEnc, 60, 0); - g.edge(2, 4).setDistance(1).set(speedEnc, 60, 0); - g.edge(5, 2).setDistance(1).set(speedEnc, 60, 0); - g.edge(3, 5).setDistance(1).set(speedEnc, 60, 0); - g.edge(4, 3).setDistance(1).set(speedEnc, 60, 0); + g.edge(5, 4).setDistance(300).set(speedEnc, 60, 0); + g.edge(4, 5).setDistance(1000).set(speedEnc, 60, 0); + g.edge(2, 4).setDistance(100).set(speedEnc, 60, 0); + g.edge(5, 2).setDistance(100).set(speedEnc, 60, 0); + g.edge(3, 5).setDistance(100).set(speedEnc, 60, 0); + g.edge(4, 3).setDistance(100).set(speedEnc, 60, 0); g.freeze(); assertEquals(6, g.getEdges()); PrepareContractionHierarchies prepare = createPrepareContractionHierarchies(g); @@ -168,7 +167,7 @@ public void testDirectedGraph() { assertEquals(6 + 2, routingCHGraph.getEdges()); RoutingAlgorithm algo = new CHRoutingAlgorithmFactory(routingCHGraph).createAlgo(new PMap()); Path p = algo.calcPath(4, 2); - assertEquals(3, p.getDistance(), 1e-6); + assertEquals(300, p.getDistance(), 1e-6); assertEquals(IntArrayList.from(4, 3, 5, 2), p.calcNodes()); } @@ -190,7 +189,7 @@ public void testDirectedGraph2() { assertEquals(oldCount + numShortcuts, routingCHGraph.getEdges()); RoutingAlgorithm algo = new CHRoutingAlgorithmFactory(routingCHGraph).createAlgo(new PMap()); Path p = algo.calcPath(0, 10); - assertEquals(10, p.getDistance(), 1e-6); + assertEquals(1000, p.getDistance(), 1e-6); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10), p.calcNodes()); } @@ -202,46 +201,46 @@ private static void initRoundaboutGraph(Graph g, DecimalEncodedValue speedEnc) { // -15-1--2--3--4 / / // / \-5->6/ / // -14 \________/ - g.edge(16, 0).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 9).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 17).setDistance(1).set(speedEnc, 60, 60); - g.edge(9, 10).setDistance(1).set(speedEnc, 60, 60); - g.edge(10, 11).setDistance(1).set(speedEnc, 60, 60); - g.edge(11, 28).setDistance(1).set(speedEnc, 60, 60); - g.edge(28, 29).setDistance(1).set(speedEnc, 60, 60); - g.edge(29, 30).setDistance(1).set(speedEnc, 60, 60); - g.edge(30, 31).setDistance(1).set(speedEnc, 60, 60); - g.edge(31, 4).setDistance(1).set(speedEnc, 60, 60); - - g.edge(17, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(15, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(14, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(14, 18).setDistance(1).set(speedEnc, 60, 60); - g.edge(18, 19).setDistance(1).set(speedEnc, 60, 60); - g.edge(19, 20).setDistance(1).set(speedEnc, 60, 60); - g.edge(20, 15).setDistance(1).set(speedEnc, 60, 60); - g.edge(19, 21).setDistance(1).set(speedEnc, 60, 60); - g.edge(21, 16).setDistance(1).set(speedEnc, 60, 60); - g.edge(1, 2).setDistance(1).set(speedEnc, 60, 60); - g.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - - g.edge(4, 5).setDistance(1).set(speedEnc, 60, 0); - g.edge(5, 6).setDistance(1).set(speedEnc, 60, 0); - g.edge(6, 7).setDistance(1).set(speedEnc, 60, 0); - g.edge(7, 13).setDistance(1).set(speedEnc, 60, 0); - g.edge(13, 12).setDistance(1).set(speedEnc, 60, 0); - g.edge(12, 4).setDistance(1).set(speedEnc, 60, 0); - - g.edge(7, 8).setDistance(1).set(speedEnc, 60, 60); - g.edge(8, 22).setDistance(1).set(speedEnc, 60, 60); - g.edge(22, 23).setDistance(1).set(speedEnc, 60, 60); - g.edge(23, 24).setDistance(1).set(speedEnc, 60, 60); - g.edge(24, 25).setDistance(1).set(speedEnc, 60, 60); - g.edge(25, 27).setDistance(1).set(speedEnc, 60, 60); - g.edge(27, 5).setDistance(1).set(speedEnc, 60, 60); - g.edge(25, 26).setDistance(1).set(speedEnc, 60, 0); - g.edge(26, 25).setDistance(1).set(speedEnc, 60, 0); + g.edge(16, 0).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 9).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 17).setDistance(100).set(speedEnc, 60, 60); + g.edge(9, 10).setDistance(100).set(speedEnc, 60, 60); + g.edge(10, 11).setDistance(100).set(speedEnc, 60, 60); + g.edge(11, 28).setDistance(100).set(speedEnc, 60, 60); + g.edge(28, 29).setDistance(100).set(speedEnc, 60, 60); + g.edge(29, 30).setDistance(100).set(speedEnc, 60, 60); + g.edge(30, 31).setDistance(100).set(speedEnc, 60, 60); + g.edge(31, 4).setDistance(100).set(speedEnc, 60, 60); + + g.edge(17, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(15, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(14, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(14, 18).setDistance(100).set(speedEnc, 60, 60); + g.edge(18, 19).setDistance(100).set(speedEnc, 60, 60); + g.edge(19, 20).setDistance(100).set(speedEnc, 60, 60); + g.edge(20, 15).setDistance(100).set(speedEnc, 60, 60); + g.edge(19, 21).setDistance(100).set(speedEnc, 60, 60); + g.edge(21, 16).setDistance(100).set(speedEnc, 60, 60); + g.edge(1, 2).setDistance(100).set(speedEnc, 60, 60); + g.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60); + + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 0); + g.edge(5, 6).setDistance(100).set(speedEnc, 60, 0); + g.edge(6, 7).setDistance(100).set(speedEnc, 60, 0); + g.edge(7, 13).setDistance(100).set(speedEnc, 60, 0); + g.edge(13, 12).setDistance(100).set(speedEnc, 60, 0); + g.edge(12, 4).setDistance(100).set(speedEnc, 60, 0); + + g.edge(7, 8).setDistance(100).set(speedEnc, 60, 60); + g.edge(8, 22).setDistance(100).set(speedEnc, 60, 60); + g.edge(22, 23).setDistance(100).set(speedEnc, 60, 60); + g.edge(23, 24).setDistance(100).set(speedEnc, 60, 60); + g.edge(24, 25).setDistance(100).set(speedEnc, 60, 60); + g.edge(25, 27).setDistance(100).set(speedEnc, 60, 60); + g.edge(27, 5).setDistance(100).set(speedEnc, 60, 60); + g.edge(25, 26).setDistance(100).set(speedEnc, 60, 0); + g.edge(26, 25).setDistance(100).set(speedEnc, 60, 0); } @Test @@ -271,14 +270,14 @@ public void testDisconnects() { // 2 // v // 7 - g.edge(8, 3).setDistance(1).set(speedEnc, 60, 0); - g.edge(3, 6).setDistance(1).set(speedEnc, 60, 0); - g.edge(6, 1).setDistance(1).set(speedEnc, 60, 0); - g.edge(1, 5).setDistance(1).set(speedEnc, 60, 0); - g.edge(4, 0).setDistance(1).set(speedEnc, 60, 0); - g.edge(0, 6).setDistance(1).set(speedEnc, 60, 0); - g.edge(6, 2).setDistance(1).set(speedEnc, 60, 0); - g.edge(2, 7).setDistance(1).set(speedEnc, 60, 0); + g.edge(8, 3).setDistance(100).set(speedEnc, 60, 0); + g.edge(3, 6).setDistance(100).set(speedEnc, 60, 0); + g.edge(6, 1).setDistance(100).set(speedEnc, 60, 0); + g.edge(1, 5).setDistance(100).set(speedEnc, 60, 0); + g.edge(4, 0).setDistance(100).set(speedEnc, 60, 0); + g.edge(0, 6).setDistance(100).set(speedEnc, 60, 0); + g.edge(6, 2).setDistance(100).set(speedEnc, 60, 0); + g.edge(2, 7).setDistance(100).set(speedEnc, 60, 0); g.freeze(); PrepareContractionHierarchies prepare = createPrepareContractionHierarchies(g) @@ -311,19 +310,19 @@ public void testStallOnDemandViaVirtuaNode_issue1574() { // the problem is very intricate and a combination of all these things: // * contraction hierarchies // * stall-on-demand (without sod there is no problem, at least in this test) - // * shortcuts weight rounding + // * shortcuts weight rounding (now gone) // * via nodes/virtual edges and the associated weight precision (without virtual nodes between source and target - // there is no problem, but this can happen for via routes + // there is no problem, but this can happen for via routes) // * the fact that the CHLevelEdgeFilter always accepts virtual nodes // here we will construct a special case where a connection is not found without the fix in #1574. - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); - EncodingManager encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager encodingManager = EncodingManager.start().add(speedEnc).build(); BaseGraph g = new BaseGraph.Builder(encodingManager).create(); - // use fastest weighting in this test to be able to fine-tune some weights via the speed (see below) - Weighting fastestWeighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); - CHConfig chConfig = CHConfig.nodeBased("c", fastestWeighting); + // note: we changed the weighting during some refactorings, so if this test is no longer testing what + // it is supposed to test maybe that's why. + Weighting weighting = new SpeedWeighting(speedEnc); + CHConfig chConfig = CHConfig.nodeBased("c", weighting); // the following graph reproduces the issue. note that we will use the node ids as ch levels, so there will // be a shortcut 3->2 visible at node 2 and another one 3->4 visible at node 3. // we will fine-tune the edge-speeds such that without the fix node 4 will be stalled and node 5 will not get @@ -334,13 +333,13 @@ public void testStallOnDemandViaVirtuaNode_issue1574() { // start 0 - 3 - x - 1 - 2 // \ | // sc ---- 4 - 5 - 6 - 7 finish - g.edge(0, 3).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - EdgeIteratorState edge31 = g.edge(3, 1).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - g.edge(1, 2).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - EdgeIteratorState edge24 = g.edge(2, 4).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - g.edge(4, 5).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - g.edge(5, 6).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); - g.edge(6, 7).setDistance(1).set(speedEnc, 60, 60).set(accessEnc, true, true); + g.edge(0, 3).setDistance(0).set(speedEnc, 60, 60); + EdgeIteratorState edge31 = g.edge(3, 1).setDistance(0).set(speedEnc, 60, 60); + g.edge(1, 2).setDistance(0).set(speedEnc, 60, 60); + EdgeIteratorState edge24 = g.edge(2, 4).setDistance(0).set(speedEnc, 60, 60); + g.edge(4, 5).setDistance(0).set(speedEnc, 60, 60); + g.edge(5, 6).setDistance(0).set(speedEnc, 60, 60); + g.edge(6, 7).setDistance(0).set(speedEnc, 60, 60); updateDistancesFor(g, 0, 0.001, 0.0000); updateDistancesFor(g, 3, 0.001, 0.0001); updateDistancesFor(g, 1, 0.001, 0.0002); @@ -354,7 +353,7 @@ public void testStallOnDemandViaVirtuaNode_issue1574() { // the weight of edge 3-1 is chosen such that node 2 gets stalled in the forward search via the incoming shortcut // at node 2 coming from 3. this happens because due to the virtual node x between 3 and 1, the weight of the // spt entry at 2 is different to the sum of the weights of the spt entry at node 3 and the shortcut edge. this - // is due to different floating point rounding arithmetic of shortcuts and virtual edges on the query graph. + // is (no: was!) due to different floating point rounding arithmetic of shortcuts and virtual edges on the query graph. edge31.set(speedEnc, 12, 12); // just stalling node 2 alone would not lead to connection not found, because the shortcut 3-4 still finds node @@ -380,26 +379,17 @@ public void testStallOnDemandViaVirtuaNode_issue1574() { QueryGraph queryGraph = QueryGraph.create(g, snap); - // we make sure our weight fine tunings do what they are supposed to - double weight03 = getWeight(queryGraph, fastestWeighting, accessEnc, 0, 3, false); - double scWeight23 = weight03 + getEdge(routingCHGraph, 2, 3, true).getWeight(false); - double scWeight34 = weight03 + getEdge(routingCHGraph, 3, 4, false).getWeight(false); - double sptWeight2 = weight03 + getWeight(queryGraph, fastestWeighting, accessEnc, 3, 8, false) + getWeight(queryGraph, fastestWeighting, accessEnc, 8, 1, false) + getWeight(queryGraph, fastestWeighting, accessEnc, 1, 2, false); - double sptWeight4 = sptWeight2 + getWeight(queryGraph, fastestWeighting, accessEnc, 2, 4, false); - assertTrue(scWeight23 < sptWeight2, "incoming shortcut weight 3->2 should be smaller than sptWeight at node 2 to make sure 2 gets stalled"); - assertTrue(sptWeight4 < scWeight34, "sptWeight at node 4 should be smaller than shortcut weight 3->4 to make sure node 4 gets stalled"); - + // we used to make sure our weight fine tunings do what they were supposed to do, but since we got rid of shortcut weight rounding altogether this no longer makes sense Path path = new CHRoutingAlgorithmFactory(routingCHGraph, queryGraph).createAlgo(new PMap()).calcPath(0, 7); - assertEquals(IntArrayList.from(0, 3, 8, 1, 2, 4, 5, 6, 7), path.calcNodes(), "wrong or no path found"); + assertEquals(IntArrayList.from(0, 3, 1, 2, 4, 5, 6, 7), path.calcNodes(), "wrong or no path found"); } - private double getWeight(Graph graph, Weighting w, BooleanEncodedValue accessEnc, int from, int to, boolean incoming) { - return w.calcEdgeWeight(getEdge(graph, accessEnc, from, to, false), incoming); + private double getWeight(Graph graph, Weighting w, int from, int to) { + return w.calcEdgeWeight(getEdge(graph, from, to), false); } - private EdgeIteratorState getEdge(Graph graph, BooleanEncodedValue accessEnc, int from, int to, boolean incoming) { - EdgeFilter filter = incoming ? AccessFilter.inEdges(accessEnc) : AccessFilter.outEdges(accessEnc); - EdgeIterator iter = graph.createEdgeExplorer(filter).setBaseNode(from); + private EdgeIteratorState getEdge(Graph graph, int from, int to) { + EdgeIterator iter = graph.createEdgeExplorer().setBaseNode(from); while (iter.next()) { if (iter.getAdjNode() == to) { return iter; @@ -423,10 +413,10 @@ public void testCircleBug() { // /--1 // -0--/ // | - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60); - g.edge(0, 1).setDistance(4).set(speedEnc, 60, 60); - g.edge(0, 2).setDistance(10).set(speedEnc, 60, 60); - g.edge(0, 3).setDistance(10).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(1000).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(400).set(speedEnc, 60, 60); + g.edge(0, 2).setDistance(1000).set(speedEnc, 60, 60); + g.edge(0, 3).setDistance(1000).set(speedEnc, 60, 60); PrepareContractionHierarchies prepare = createPrepareContractionHierarchies(g); PrepareContractionHierarchies.Result result = prepare.doWork(); assertEquals(0, result.getShortcuts()); @@ -439,15 +429,15 @@ public void testBug178() { // 0-1->-2--3--4 // \-<-/ // - g.edge(1, 2).setDistance(1).set(speedEnc, 60, 0); - g.edge(2, 1).setDistance(1).set(speedEnc, 60, 0); + g.edge(1, 2).setDistance(100).set(speedEnc, 60, 0); + g.edge(2, 1).setDistance(100).set(speedEnc, 60, 0); - g.edge(5, 0).setDistance(1).set(speedEnc, 60, 60); - g.edge(5, 6).setDistance(1).set(speedEnc, 60, 60); - g.edge(0, 1).setDistance(1).set(speedEnc, 60, 60); - g.edge(2, 3).setDistance(1).set(speedEnc, 60, 60); - g.edge(3, 4).setDistance(1).set(speedEnc, 60, 60); - g.edge(6, 3).setDistance(1).set(speedEnc, 60, 60); + g.edge(5, 0).setDistance(100).set(speedEnc, 60, 60); + g.edge(5, 6).setDistance(100).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(100).set(speedEnc, 60, 60); + g.edge(2, 3).setDistance(100).set(speedEnc, 60, 60); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60); + g.edge(6, 3).setDistance(100).set(speedEnc, 60, 60); PrepareContractionHierarchies prepare = createPrepareContractionHierarchies(g); useNodeOrdering(prepare, new int[]{4, 1, 2, 0, 5, 6, 3}); @@ -484,8 +474,8 @@ public void testMultiplePreparationsIdenticalView() { iter.set(bikeSpeedEnc, 18, 18); graph.freeze(); - checkPath(graph, carProfile, 7, 5, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); - checkPath(graph, bikeProfile, 7, 5, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); + checkPath(graph, carProfile, 7, 500, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); + checkPath(graph, bikeProfile, 7, 500, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); } @Test @@ -510,9 +500,9 @@ public void testMultiplePreparationsDifferentView() { graph.freeze(); - checkPath(graph, carConfig, 7, 5, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); + checkPath(graph, carConfig, 7, 500, IntArrayList.from(3, 9, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 15, 1, 3, 9, 14, 16, 12, 4, 2}); // detour around blocked 9,14 - checkPath(graph, bikeConfig, 9, 5, IntArrayList.from(3, 10, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 14, 15, 9, 1, 4, 3, 2, 12, 16}); + checkPath(graph, bikeConfig, 9, 500, IntArrayList.from(3, 10, 14, 16, 13, 12), new int[]{0, 5, 6, 7, 8, 10, 11, 13, 14, 15, 9, 1, 4, 3, 2, 12, 16}); } @Test @@ -533,7 +523,7 @@ public void testReusingNodeOrdering() { int numQueries = 100; long seed = System.nanoTime(); Random rnd = new Random(seed); - GHUtility.buildRandomGraph(graph, rnd, numNodes, 1.3, true, null, null, 0.9, 0.8); + RandomGraph.start().seed(seed).nodes(numNodes).fill(graph, null); AllEdgesIterator iter = graph.getAllEdges(); while (iter.next()) { double car1Fwd = rnd.nextDouble() * 100; diff --git a/core/src/test/java/com/graphhopper/routing/ev/BooleanEncodedValueTest.java b/core/src/test/java/com/graphhopper/routing/ev/BooleanEncodedValueTest.java index 54247797256..72498c405f5 100644 --- a/core/src/test/java/com/graphhopper/routing/ev/BooleanEncodedValueTest.java +++ b/core/src/test/java/com/graphhopper/routing/ev/BooleanEncodedValueTest.java @@ -32,8 +32,12 @@ public void testBitDirected() { int edgeId = 0; bool.setBool(false, edgeId, edgeIntAccess, false); bool.setBool(true, edgeId, edgeIntAccess, true); - assertFalse(bool.getBool(false, edgeId, edgeIntAccess)); assertTrue(bool.getBool(true, edgeId, edgeIntAccess)); + + bool.setBool(false, edgeId, edgeIntAccess, true); + bool.setBool(true, edgeId, edgeIntAccess, false); + assertTrue(bool.getBool(false, edgeId, edgeIntAccess)); + assertFalse(bool.getBool(true, edgeId, edgeIntAccess)); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/ev/ImportUnitSorterTest.java b/core/src/test/java/com/graphhopper/routing/ev/ImportUnitSorterTest.java new file mode 100644 index 00000000000..61669da09e3 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/ev/ImportUnitSorterTest.java @@ -0,0 +1,67 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.routing.ev; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.*; + +public class ImportUnitSorterTest { + + @Test + public void simple() { + ImportUnit a = create("a"); + ImportUnit b = create("b", "a"); + ImportUnit c = create("c", "b"); + + Map importUnits = new HashMap<>(); + importUnits.put("a", a); + importUnits.put("b", b); + importUnits.put("c", c); + + ImportUnitSorter sorter = new ImportUnitSorter(importUnits); + List sorted = sorter.sort(); + + assertEquals(importUnits.size(), sorted.size()); + assertEquals(List.of("a", "b", "c"), sorted); + } + + @Test + public void cycle() { + ImportUnit a = create("a", "b"); + ImportUnit b = create("b", "a"); + + Map importUnits = new HashMap<>(); + importUnits.put("a", a); + importUnits.put("b", b); + + ImportUnitSorter sorter = new ImportUnitSorter(importUnits); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, sorter::sort); + assertTrue(e.getMessage().contains("import units with cyclic dependencies are not allowed")); + } + + private ImportUnit create(String name, String... required) { + return ImportUnit.create(name, null, null, required); + } + +} diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java index c6a12420d8d..6e07d706193 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LMApproximatorTest.java @@ -27,11 +27,9 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.*; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Directory; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.*; import com.graphhopper.util.EdgeIterator; -import com.graphhopper.util.GHUtility; +import com.graphhopper.util.RandomGraph; import org.junit.jupiter.api.RepeatedTest; import java.util.Random; @@ -47,14 +45,13 @@ public void randomGraph() { } private void run(long seed) { - Directory dir = new RAMDirectory(); + Directory dir = new GHDirectory("", DAType.RAM); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); DecimalEncodedValue turnCostEnc = TurnCost.create("car", 1); EncodingManager encodingManager = new EncodingManager.Builder().add(speedEnc).addTurnCostEncodedValue(turnCostEnc).add(Subnetwork.create("car")).build(); BaseGraph graph = new BaseGraph.Builder(encodingManager).setDir(dir).withTurnCosts(true).create(); - Random rnd = new Random(seed); - GHUtility.buildRandomGraph(graph, rnd, 100, 2.2, true, speedEnc, null, 0.8, 0.8); + RandomGraph.start().seed(seed).nodes(50).curviness(0.1).speedZero(0.1).fill(graph, speedEnc); Weighting weighting = new SpeedWeighting(speedEnc); @@ -87,7 +84,7 @@ private void run(long seed) { if (path.isFound()) { // Give the beelineApproximator some slack, because the map distance of an edge // can be _smaller_ than its Euklidean distance, due to rounding. - double slack = path.getEdgeCount() * (1 / 1000.0); + double slack = path.getEdgeCount() * (10 / 1000.0); double realRemainingWeight = path.getWeight(); double approximatedRemainingWeight = lmApproximator.approximate(v); if (approximatedRemainingWeight - slack > realRemainingWeight) { @@ -136,7 +133,7 @@ private void run(long seed) { if (reversePath.isFound()) { // Give the beelineApproximator some slack, because the map distance of an edge // can be _smaller_ than its Euklidean distance, due to rounding. - double slack = reversePath.getEdgeCount() * (1 / 1000.0); + double slack = reversePath.getEdgeCount() * (10 / 1000.0); double realRemainingWeight = reversePath.getWeight(); double approximatedRemainingWeight = reverseLmApproximator.approximate(v); if (approximatedRemainingWeight - slack > realRemainingWeight) { diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java index ff735e586ce..e02b9db50c9 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LMIssueTest.java @@ -21,13 +21,9 @@ import com.graphhopper.routing.*; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Directory; -import com.graphhopper.storage.NodeAccess; -import com.graphhopper.storage.RAMDirectory; -import com.graphhopper.util.GHUtility; +import com.graphhopper.storage.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; @@ -40,7 +36,6 @@ public class LMIssueTest { private Directory dir; private BaseGraph graph; - private BooleanEncodedValue accessEnc; private DecimalEncodedValue speedEnc; private Weighting weighting; private LandmarkStorage lm; @@ -57,16 +52,15 @@ private enum Algo { @BeforeEach public void init() { - dir = new RAMDirectory(); - accessEnc = new SimpleBooleanEncodedValue("access", true); - speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); + dir = new GHDirectory("", DAType.RAM); + speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); DecimalEncodedValue turnCostEnc = TurnCost.create("car", 1); - encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).addTurnCostEncodedValue(turnCostEnc).add(Subnetwork.create("car")).build(); + encodingManager = new EncodingManager.Builder().add(speedEnc).addTurnCostEncodedValue(turnCostEnc).add(Subnetwork.create("car")).build(); graph = new BaseGraph.Builder(encodingManager) .withTurnCosts(true) .setDir(dir) .create(); - weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); + weighting = new SpeedWeighting(speedEnc); } private void preProcessGraph() { @@ -122,13 +116,13 @@ public void lm_problem_to_node_of_fallback_approximator(Algo algo) { na.setNode(3, 49.403009, 9.708364); na.setNode(4, 49.409021, 9.703622); // 30s - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(4, 3).setDistance(1000)).set(speedEnc, 120); - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 2).setDistance(1000)).set(speedEnc, 120); + graph.edge(4, 3).setDistance(1000).set(speedEnc, 120, 120); + graph.edge(0, 2).setDistance(1000).set(speedEnc, 120, 0); // 360s - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 3).setDistance(1000)).set(speedEnc, 10); + graph.edge(1, 3).setDistance(1000).set(speedEnc, 10, 60); // 80s - GHUtility.setSpeed(60, true, false, accessEnc, speedEnc, graph.edge(0, 1).setDistance(1000)).set(speedEnc, 45); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 4).setDistance(1000)).set(speedEnc, 45); + graph.edge(0, 1).setDistance(1000).set(speedEnc, 45, 0); + graph.edge(1, 4).setDistance(1000).set(speedEnc, 45, 60); preProcessGraph(); int source = 0; @@ -163,13 +157,13 @@ public void lm_issue2(Algo algo) { na.setNode(7, 49.406965, 9.702660); na.setNode(8, 49.405227, 9.702863); na.setNode(9, 49.409411, 9.709085); - GHUtility.setSpeed(112, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(623.197000)); - GHUtility.setSpeed(13, true, true, accessEnc, speedEnc, graph.edge(5, 1).setDistance(741.414000)); - GHUtility.setSpeed(35, true, true, accessEnc, speedEnc, graph.edge(9, 4).setDistance(1140.835000)); - GHUtility.setSpeed(18, true, true, accessEnc, speedEnc, graph.edge(5, 6).setDistance(670.689000)); - GHUtility.setSpeed(88, true, false, accessEnc, speedEnc, graph.edge(5, 9).setDistance(80.731000)); - GHUtility.setSpeed(82, true, true, accessEnc, speedEnc, graph.edge(0, 9).setDistance(273.948000)); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(4, 0).setDistance(956.552000)); + graph.edge(0, 1).setDistance(623.197000).set(speedEnc, 112, 112); + graph.edge(5, 1).setDistance(741.414000).set(speedEnc, 13, 13); + graph.edge(9, 4).setDistance(1140.835000).set(speedEnc, 35, 35); + graph.edge(5, 6).setDistance(670.689000).set(speedEnc, 18, 18); + graph.edge(5, 9).setDistance(80.731000).set(speedEnc, 88, 0); + graph.edge(0, 9).setDistance(273.948000).set(speedEnc, 82, 82); + graph.edge(4, 0).setDistance(956.552000).set(speedEnc, 60, 60); preProcessGraph(); int source = 5; diff --git a/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java b/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java index 20ba47f0f7e..b8579cb8ed5 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LMPreparationHandlerTest.java @@ -3,13 +3,10 @@ import com.graphhopper.GraphHopperConfig; import com.graphhopper.config.LMProfile; import com.graphhopper.config.Profile; -import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.weighting.FastestWeighting; -import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.storage.BaseGraph; import org.junit.jupiter.api.Test; @@ -34,19 +31,18 @@ public void testEnabled() { public void maximumLMWeight() { LMPreparationHandler handler = new LMPreparationHandler(); handler.setLMProfiles( - new LMProfile("conf1").setMaximumLMWeight(65_000), - new LMProfile("conf2").setMaximumLMWeight(20_000) + new LMProfile("conf1").setMaximumLMWeight(650_000), + new LMProfile("conf2").setMaximumLMWeight(200_000) ); - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", false); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); List lmConfigs = Arrays.asList( - new LMConfig("conf1", new FastestWeighting(accessEnc, speedEnc)), - new LMConfig("conf2", new ShortestWeighting(accessEnc, speedEnc)) + new LMConfig("conf1", new SpeedWeighting(speedEnc)), + new LMConfig("conf2", new SpeedWeighting(speedEnc)) ); List preparations = handler.createPreparations(lmConfigs, new BaseGraph.Builder(em).build(), em, null); - assertEquals(1, preparations.get(0).getLandmarkStorage().getFactor(), .1); - assertEquals(0.3, preparations.get(1).getLandmarkStorage().getFactor(), .1); + assertEquals(10, preparations.get(0).getLandmarkStorage().getFactor(), .1); + assertEquals(3, preparations.get(1).getLandmarkStorage().getFactor(), .1); } @Test diff --git a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java index ff941131372..e4d9e7124a6 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/LandmarkStorageTest.java @@ -27,9 +27,7 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Directory; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.*; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -66,7 +64,7 @@ public void tearDown() { @Test public void testInfiniteWeight() { - Directory dir = new RAMDirectory(); + Directory dir = new GHDirectory("", DAType.RAM); graph.edge(0, 1); LandmarkStorage lms = new LandmarkStorage(graph, encodingManager, dir, new LMConfig("car", new SpeedWeighting(speedEnc)), 8). setMaximumWeight(LandmarkStorage.PRECISION); @@ -93,7 +91,7 @@ public void testInfiniteWeight() { @Test public void testSetGetWeight() { graph.edge(0, 1).set(speedEnc, 60, 60).setDistance(40.1); - Directory dir = new RAMDirectory(); + Directory dir = new GHDirectory("", DAType.RAM); LandmarkStorage lms = new LandmarkStorage(graph, encodingManager, dir, new LMConfig("c1", new SpeedWeighting(speedEnc)), 4). setMaximumWeight(LandmarkStorage.PRECISION); @@ -122,7 +120,7 @@ public void testWithSubnetworks() { // 1 means => 2 allowed edge keys => excludes the node 6 subnetworkRemoval(weighting, 1); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), new LMConfig("car", weighting), 2); + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", weighting), 2); storage.setMinimumNodes(2); storage.createLandmarks(); assertEquals(3, storage.getSubnetworksWithLandmarks()); @@ -145,7 +143,7 @@ public void testWithStronglyConnectedComponent() { // 3 nodes => 6 allowed edge keys but still do not exclude 3 & 4 as strongly connected and not a too small subnetwork! subnetworkRemoval(weighting, 4); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), new LMConfig("car", weighting), 2); + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", weighting), 2); storage.setMinimumNodes(3); storage.createLandmarks(); assertEquals(2, storage.getSubnetworksWithLandmarks()); @@ -175,7 +173,7 @@ public void testWithOnewaySubnetworks() { // 1 allowed node => 2 allowed edge keys (exclude 2 and 3 because they are separate too small oneway subnetworks) subnetworkRemoval(weighting, 1); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), new LMConfig("car", weighting), 2); + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", weighting), 2); storage.setMinimumNodes(2); storage.createLandmarks(); @@ -192,7 +190,7 @@ public void testWeightingConsistence1() { graph.edge(1, 2).setDistance(10).set(speedEnc, 30, 30); graph.edge(2, 3).setDistance(10.1).set(speedEnc, 0, 0); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", new SpeedWeighting(speedEnc)), 2); storage.setMinimumNodes(2); storage.createLandmarks(); @@ -207,7 +205,7 @@ public void testWeightingConsistence2() { graph.edge(2, 3).setDistance(10.1).set(speedEnc, 0, 0); graph.edge(2, 3).setDistance(10).set(speedEnc, 30, 30); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", new SpeedWeighting(speedEnc)), 2); storage.setMinimumNodes(2); storage.createLandmarks(); @@ -221,7 +219,7 @@ public void testWeightingConsistence2() { public void testWithBorderBlocking() { RoutingAlgorithmTest.initBiGraph(graph, speedEnc); - LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new RAMDirectory(), + LandmarkStorage storage = new LandmarkStorage(graph, encodingManager, new GHDirectory("", DAType.RAM), new LMConfig("car", new SpeedWeighting(speedEnc)), 2); final SplitArea right = new SplitArea(emptyList()); final SplitArea left = new SplitArea(emptyList()); diff --git a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java index d10b96966fd..77dd226d340 100644 --- a/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java +++ b/core/src/test/java/com/graphhopper/routing/lm/PrepareLandmarksTest.java @@ -26,14 +26,11 @@ import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Directory; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.*; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; -import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; import com.graphhopper.util.Parameters; @@ -56,7 +53,6 @@ * @author Peter Karich */ public class PrepareLandmarksTest { - private BooleanEncodedValue accessEnc; private DecimalEncodedValue speedEnc; private EncodingManager encodingManager; private BaseGraph graph; @@ -64,9 +60,8 @@ public class PrepareLandmarksTest { @BeforeEach public void setUp() { - accessEnc = new SimpleBooleanEncodedValue("access", true); speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - encodingManager = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(Subnetwork.create("car")).build(); + encodingManager = new EncodingManager.Builder().add(speedEnc).add(Subnetwork.create("car")).build(); graph = new BaseGraph.Builder(encodingManager).create(); tm = TraversalMode.NODE_BASED; } @@ -86,21 +81,21 @@ public void testLandmarkStorageAndRouting() { // do not connect first with last column! double speed = 20 + rand.nextDouble() * 30; if (wIndex + 1 < width) - graph.edge(node, node + 1).set(accessEnc, true, true).set(speedEnc, speed); + graph.edge(node, node + 1).set(speedEnc, speed); // avoid dead ends if (hIndex + 1 < height) - graph.edge(node, node + width).set(accessEnc, true, true).set(speedEnc, speed); + graph.edge(node, node + width).set(speedEnc, speed); updateDistancesFor(graph, node, -hIndex / 50.0, wIndex / 50.0); } } - Directory dir = new RAMDirectory(); + Directory dir = new GHDirectory("", DAType.RAM); LocationIndexTree index = new LocationIndexTree(graph, dir); index.prepareIndex(); int lm = 5, activeLM = 2; - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); + Weighting weighting = new SpeedWeighting(speedEnc); LMConfig lmConfig = new LMConfig("car", weighting); LandmarkStorage store = new LandmarkStorage(graph, encodingManager, dir, lmConfig, lm); store.setMinimumNodes(2); @@ -115,13 +110,13 @@ public void testLandmarkStorageAndRouting() { assertEquals(0, store.getFromWeight(0, 224)); double factor = store.getFactor(); - assertEquals(4671, Math.round(store.getFromWeight(0, 47) * factor)); - assertEquals(3640, Math.round(store.getFromWeight(0, 52) * factor)); + assertEquals(12971, Math.round(store.getFromWeight(0, 47) * factor)); + assertEquals(10108, Math.round(store.getFromWeight(0, 52) * factor)); long weight1_224 = store.getFromWeight(1, 224); - assertEquals(5525, Math.round(weight1_224 * factor)); + assertEquals(15345, Math.round(weight1_224 * factor)); long weight1_47 = store.getFromWeight(1, 47); - assertEquals(921, Math.round(weight1_47 * factor)); + assertEquals(2558, Math.round(weight1_47 * factor)); // grid is symmetric assertEquals(weight1_224, store.getToWeight(1, 224)); @@ -138,7 +133,7 @@ public void testLandmarkStorageAndRouting() { // TODO should better select 0 and 224? assertEquals(Arrays.asList(224, 70), list); - PrepareLandmarks prepare = new PrepareLandmarks(new RAMDirectory(), graph, encodingManager, lmConfig, 4); + PrepareLandmarks prepare = new PrepareLandmarks(new GHDirectory("", DAType.RAM), graph, encodingManager, lmConfig, 4); prepare.setMinimumNodes(2); prepare.doWork(); LandmarkStorage lms = prepare.getLandmarkStorage(); @@ -156,7 +151,7 @@ public void testLandmarkStorageAndRouting() { assertEquals(expectedPath.getWeight(), path.getWeight(), .1); assertEquals(expectedPath.calcNodes(), path.calcNodes()); - assertEquals(expectedAlgo.getVisitedNodes() - 73, oneDirAlgoWithLandmarks.getVisitedNodes()); + assertEquals(expectedAlgo.getVisitedNodes() - 136, oneDirAlgoWithLandmarks.getVisitedNodes()); // landmarks with bidir A* RoutingAlgorithm biDirAlgoWithLandmarks = new LMRoutingAlgorithmFactory(lms).createAlgo(graph, weighting, @@ -164,7 +159,7 @@ public void testLandmarkStorageAndRouting() { path = biDirAlgoWithLandmarks.calcPath(41, 183); assertEquals(expectedPath.getWeight(), path.getWeight(), .1); assertEquals(expectedPath.calcNodes(), path.calcNodes()); - assertEquals(expectedAlgo.getVisitedNodes() - 95, biDirAlgoWithLandmarks.getVisitedNodes()); + assertEquals(expectedAlgo.getVisitedNodes() - 163, biDirAlgoWithLandmarks.getVisitedNodes()); // landmarks with A* and a QueryGraph. We expect slightly less optimal as two more cycles needs to be traversed // due to the two more virtual nodes but this should not harm in practise @@ -175,22 +170,23 @@ public void testLandmarkStorageAndRouting() { new AlgorithmOptions().setAlgorithm(ASTAR).setTraversalMode(tm).setHints(hints)); path = qGraphOneDirAlgo.calcPath(fromSnap.getClosestNode(), toSnap.getClosestNode()); - expectedAlgo = new AStar(qGraph, weighting, tm); + expectedAlgo = new AStar(qGraph, qGraph.wrapWeighting(weighting), tm); expectedPath = expectedAlgo.calcPath(fromSnap.getClosestNode(), toSnap.getClosestNode()); assertEquals(expectedPath.getWeight(), path.getWeight(), .1); assertEquals(expectedPath.calcNodes(), path.calcNodes()); - assertEquals(expectedAlgo.getVisitedNodes() - 73, qGraphOneDirAlgo.getVisitedNodes()); + // todonow: why did visited nodes change? + assertEquals(expectedAlgo.getVisitedNodes() - 136, qGraphOneDirAlgo.getVisitedNodes()); } @Test public void testStoreAndLoad() { - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(80_000)); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 2).setDistance(80_000)); + graph.edge(0, 1).setDistance(80_000).set(speedEnc, 60); + graph.edge(1, 2).setDistance(80_000).set(speedEnc, 60); String fileStr = "./target/tmp-lm"; Helper.removeDir(new File(fileStr)); - Directory dir = new RAMDirectory(fileStr, true).create(); - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); + Directory dir = new GHDirectory(fileStr, DAType.RAM_STORE).create(); + Weighting weighting = new SpeedWeighting(speedEnc); LMConfig lmConfig = new LMConfig("car", weighting); PrepareLandmarks plm = new PrepareLandmarks(dir, graph, encodingManager, lmConfig, 2); plm.setMinimumNodes(2); @@ -201,16 +197,16 @@ public void testStoreAndLoad() { assertEquals(Arrays.toString(new int[]{ 2, 0 }), Arrays.toString(plm.getLandmarkStorage().getLandmarks(1))); - assertEquals(4800, Math.round(plm.getLandmarkStorage().getFromWeight(0, 1) * expectedFactor)); + assertEquals(13333, Math.round(plm.getLandmarkStorage().getFromWeight(0, 1) * expectedFactor)); - dir = new RAMDirectory(fileStr, true); + dir = new GHDirectory(fileStr, DAType.RAM_STORE); plm = new PrepareLandmarks(dir, graph, encodingManager, lmConfig, 2); assertTrue(plm.loadExisting()); assertEquals(expectedFactor, plm.getLandmarkStorage().getFactor(), 1e-6); assertEquals(Arrays.toString(new int[]{ 2, 0 }), Arrays.toString(plm.getLandmarkStorage().getLandmarks(1))); - assertEquals(4800, Math.round(plm.getLandmarkStorage().getFromWeight(0, 1) * expectedFactor)); + assertEquals(13333, Math.round(plm.getLandmarkStorage().getFromWeight(0, 1) * expectedFactor)); Helper.removeDir(new File(fileStr)); } diff --git a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java index 5afbe55a5c0..424a0387294 100644 --- a/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java +++ b/core/src/test/java/com/graphhopper/routing/querygraph/QueryGraphTest.java @@ -25,7 +25,9 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.search.KVStorage; import com.graphhopper.storage.*; +import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.*; @@ -34,14 +36,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.IntStream; import static com.graphhopper.storage.index.Snap.Position.*; +import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; +import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; import static com.graphhopper.util.EdgeIteratorState.UNFAVORED_EDGE; import static com.graphhopper.util.GHUtility.updateDistancesFor; import static org.junit.jupiter.api.Assertions.*; @@ -72,13 +73,12 @@ void initGraph(Graph g) { // 0 1 // | // 2 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 1, 0); - na.setNode(1, 1, 2.5); - na.setNode(2, 0, 0); - g.edge(0, 2).setDistance(10).set(speedEnc, 60, 60); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60). + g.edge(0, 2).setDistance(0).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(1.5, 1, 1.5, 1.5)); + updateDistancesFor(g, 0, 1, 0); + updateDistancesFor(g, 1, 1, 2.5); + updateDistancesFor(g, 2, 0, 0); } @Test @@ -152,43 +152,38 @@ public void testFillVirtualEdges() { // 0 1 // | / // 2 3 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 1, 0); - na.setNode(1, 1, 2.5); - na.setNode(2, 0, 0); - na.setNode(3, 0, 1); - g.edge(0, 2).setDistance(10).set(speedEnc, 60, 60); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60) + g.edge(0, 2).setDistance(0).set(speedEnc, 60, 60); + EdgeIteratorState edgeWithGeo = g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60) .setWayGeometry(Helper.createPointList(1.5, 1, 1.5, 1.5)); g.edge(1, 3); + updateDistancesFor(g, 0, 1, 0); + updateDistancesFor(g, 1, 1, 2.5); + updateDistancesFor(g, 2, 0, 0); + updateDistancesFor(g, 3, 0, 1); + edgeWithGeo.setDistance(DIST_EARTH.calcDistance(edgeWithGeo.fetchWayGeometry(FetchMode.ALL))); - final int baseNode = 1; - EdgeIterator iter = g.createEdgeExplorer().setBaseNode(baseNode); - iter.next(); - // note that we do not really do a location index lookup, but rather create a snap artificially, also - // this snap is not very intuitive as we would expect snapping to the 1-0 edge, but this is how this - // test was written initially... - Snap snap = createLocationResult(2, 1.7, iter, 1, PILLAR); + Snap snap = createLocationResult(2, 1.7, edgeWithGeo, 1, PILLAR); QueryOverlay queryOverlay = QueryOverlayBuilder.build(g, Collections.singletonList(snap)); IntObjectMap realNodeModifications = queryOverlay.getEdgeChangesAtRealNodes(); assertEquals(2, realNodeModifications.size()); - // ignore nodes should include baseNode == 1 - assertEquals("[3->4]", realNodeModifications.get(3).getAdditionalEdges().toString()); - assertEquals("[2]", realNodeModifications.get(3).getRemovedEdges().toString()); assertEquals("[1->4]", realNodeModifications.get(1).getAdditionalEdges().toString()); - assertEquals("[2]", realNodeModifications.get(1).getRemovedEdges().toString()); + assertEquals("[1]", realNodeModifications.get(1).getRemovedEdges().toString()); + assertEquals("[0->4]", realNodeModifications.get(0).getAdditionalEdges().toString()); + assertEquals("[1]", realNodeModifications.get(0).getRemovedEdges().toString()); QueryGraph queryGraph = QueryGraph.create(g, snap); - EdgeIteratorState state = GHUtility.getEdge(queryGraph, 0, 1); - assertEquals(4, state.fetchWayGeometry(FetchMode.ALL).size()); + EdgeIteratorState state = GHUtility.getEdge(queryGraph, 1, 3); + assertEquals(2, state.fetchWayGeometry(FetchMode.ALL).size()); // fetch virtual edge and check way geometry - state = GHUtility.getEdge(queryGraph, 4, 3); + state = GHUtility.getEdge(queryGraph, 4, 1); + assertEquals(3, state.fetchWayGeometry(FetchMode.ALL).size()); + state = GHUtility.getEdge(queryGraph, 4, 0); assertEquals(2, state.fetchWayGeometry(FetchMode.ALL).size()); // now we actually test the edges at the real tower nodes (virtual ones should be added and some real ones removed) - assertEquals("[1->4, 1 1-0]", ((VirtualEdgeIterator) queryGraph.createEdgeExplorer().setBaseNode(1)).getEdges().toString()); - assertEquals("[3->4]", ((VirtualEdgeIterator) queryGraph.createEdgeExplorer().setBaseNode(3)).getEdges().toString()); + assertEquals("[1->4, 2 1-3]", ((VirtualEdgeIterator) queryGraph.createEdgeExplorer().setBaseNode(1)).getEdges().toString()); + assertEquals("[0->4, 0 0-2]", ((VirtualEdgeIterator) queryGraph.createEdgeExplorer().setBaseNode(0)).getEdges().toString()); } @Test @@ -236,10 +231,9 @@ public void testMultipleVirtualNodes() { @Test public void testOneWay() { - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0, 1); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 0); + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 0); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0, 1); EdgeIteratorState edge = GHUtility.getEdge(g, 0, 1); Snap res1 = createLocationResult(0.1, 0.1, edge, 0, EDGE); @@ -272,10 +266,9 @@ public void testVirtEdges() { public void testUseMeanElevation() { g.close(); g = new BaseGraph.Builder(encodingManager).set3D(true).create(); - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0, 0); - na.setNode(1, 0, 0.0001, 20); EdgeIteratorState edge = g.edge(0, 1); + updateDistancesFor(g, 0, 0, 0, 0); + updateDistancesFor(g, 1, 0, 0.0001, 20); EdgeIteratorState edgeReverse = edge.detach(true); DistanceCalcEuclidean distCalc = new DistanceCalcEuclidean(); @@ -302,9 +295,9 @@ public void testLoopStreet_Issue151() { // | | // x--- // - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60); - g.edge(1, 3).setDistance(10).set(speedEnc, 60, 60); - g.edge(3, 4).setDistance(10).set(speedEnc, 60, 60); + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60); + g.edge(1, 3).setDistance(0).set(speedEnc, 60, 60); + g.edge(3, 4).setDistance(0).set(speedEnc, 60, 60); EdgeIteratorState edge = g.edge(1, 3).setDistance(20).set(speedEnc, 60, 60).setWayGeometry(Helper.createPointList(-0.001, 0.001, -0.001, 0.002)); updateDistancesFor(g, 0, 0, 0); updateDistancesFor(g, 1, 0, 0.001); @@ -366,12 +359,12 @@ public void testAvoidDuplicateVirtualNodesIfIdentical() { @Test void towerSnapWhenCrossingPointIsOnEdgeButCloseToTower() { - g.getNodeAccess().setNode(0, 49.000000, 11.00100); - g.getNodeAccess().setNode(1, 49.000000, 11.00200); - g.getNodeAccess().setNode(2, 49.000300, 11.00200); g.edge(0, 1); g.edge(1, 2); - LocationIndexTree locationIndex = new LocationIndexTree(g, new RAMDirectory()); + updateDistancesFor(g, 0, 49.000000, 11.00100); + updateDistancesFor(g, 1, 49.000000, 11.00200); + updateDistancesFor(g, 2, 49.000300, 11.00200); + LocationIndexTree locationIndex = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); locationIndex.prepareIndex(); Snap snap = locationIndex.findClosest(49.0000010, 11.00800, EdgeFilter.ALL_EDGES); // Our query point is quite far away from the edge and further away from the tower node than from the crossing @@ -429,10 +422,10 @@ public void testIteration_Issue163() { * / \ * A B */ - g.getNodeAccess().setNode(nodeA, 1, 0); - g.getNodeAccess().setNode(nodeB, 1, 10); - g.edge(nodeA, nodeB).setDistance(10).set(speedEnc, 60, 0). + g.edge(nodeA, nodeB).setDistance(0).set(speedEnc, 60, 0). setWayGeometry(Helper.createPointList(1.5, 3, 1.5, 7)); + updateDistancesFor(g, nodeA, 1, 0); + updateDistancesFor(g, nodeB, 1, 10); // assert the behavior for classic edgeIterator assertEdgeIdsStayingEqual(inExplorer, outExplorer, nodeA, nodeB); @@ -480,21 +473,20 @@ public void testTurnCostsProperlyPropagated_Issue282() { EncodingManager em = EncodingManager.start().add(speedEnc).addTurnCostEncodedValue(turnCostEnc).build(); BaseGraph graphWithTurnCosts = new BaseGraph.Builder(em).withTurnCosts(true).create(); TurnCostStorage turnExt = graphWithTurnCosts.getTurnCostStorage(); - NodeAccess na = graphWithTurnCosts.getNodeAccess(); - na.setNode(0, .00, .00); - na.setNode(1, .00, .01); - na.setNode(2, .01, .01); - EdgeIteratorState edge0 = graphWithTurnCosts.edge(0, 1).setDistance(10).set(speedEnc, 60, 60); - EdgeIteratorState edge1 = graphWithTurnCosts.edge(2, 1).setDistance(10).set(speedEnc, 60, 60); + EdgeIteratorState edge0 = graphWithTurnCosts.edge(0, 1).setDistance(0).set(speedEnc, 60, 60); + EdgeIteratorState edge1 = graphWithTurnCosts.edge(2, 1).setDistance(0).set(speedEnc, 60, 60); + updateDistancesFor(graphWithTurnCosts, 0, .00, .00); + updateDistancesFor(graphWithTurnCosts, 1, .00, .01); + updateDistancesFor(graphWithTurnCosts, 2, .01, .01); Weighting weighting = new SpeedWeighting(speedEnc, turnCostEnc, graphWithTurnCosts.getTurnCostStorage(), Double.POSITIVE_INFINITY); // no turn costs initially - assertEquals(0, weighting.calcTurnWeight(edge0.getEdge(), 1, edge1.getEdge()), .1); + assertEquals(0, weighting.calcTurnWeight(edge0.getEdge(), 1, edge1.getEdge())); // now use turn costs turnExt.set(turnCostEnc, edge0.getEdge(), 1, edge1.getEdge(), 10); - assertEquals(10, weighting.calcTurnWeight(edge0.getEdge(), 1, edge1.getEdge()), .1); + assertEquals(100, weighting.calcTurnWeight(edge0.getEdge(), 1, edge1.getEdge())); // now use turn costs with query graph Snap res1 = createLocationResult(0.000, 0.005, edge0, 0, Snap.Position.EDGE); @@ -505,7 +497,7 @@ public void testTurnCostsProperlyPropagated_Issue282() { int fromQueryEdge = GHUtility.getEdge(qGraph, res1.getClosestNode(), 1).getEdge(); int toQueryEdge = GHUtility.getEdge(qGraph, res2.getClosestNode(), 1).getEdge(); - assertEquals(10, weighting.calcTurnWeight(fromQueryEdge, 1, toQueryEdge), .1); + assertEquals(100, weighting.calcTurnWeight(fromQueryEdge, 1, toQueryEdge)); graphWithTurnCosts.close(); } @@ -531,12 +523,11 @@ public void testEnforceHeading() { // x | // | | // 0 1 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0, 2); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60). + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(2, 0, 2, 2)); EdgeIteratorState edge = GHUtility.getEdge(g, 0, 1); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0, 2); // snap on first vertical part of way (upward, base is in south) Snap snap = fakeEdgeSnap(edge, 1.5, 0, 0); @@ -597,16 +588,15 @@ public void testEnforceHeading() { @Test public void testUnfavoredEdgeDirections() { - NodeAccess na = g.getNodeAccess(); // 0 <-> x <-> 1 // 2 - na.setNode(0, 0, 0); - na.setNode(1, 0, 2); - EdgeIteratorState edge = g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60); + EdgeIteratorState edge = g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0, 2); Snap snap = fakeEdgeSnap(edge, 0, 1, 0); QueryGraph queryGraph = QueryGraph.create(g, snap); - queryGraph.unfavorVirtualEdge(1); + queryGraph.unfavorVirtualEdges(IntArrayList.from(1)); // this sets the unfavored flag for both 'directions' (not sure if this is really what we want, but this is how // it is). for example we can not set the virtual edge 0-2 unfavored when going from 0 to 2 but *not* unfavored // when going from 2 to 0. this would be a problem for edge-based routing where we might apply a penalty when @@ -629,19 +619,18 @@ public void testUnfavorVirtualEdgePair() { // | | // | | // 0 1 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0, 2); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60). + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(2, 0, 2, 2)); EdgeIteratorState edge = GHUtility.getEdge(g, 0, 1); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0, 2); // snap on first vertical part of way (upward) Snap snap = fakeEdgeSnap(edge, 1.5, 0, 0); QueryGraph queryGraph = lookup(snap); // enforce coming in north - queryGraph.unfavorVirtualEdge(1); + queryGraph.unfavorVirtualEdges(IntArrayList.from(1)); // test penalized south VirtualEdgeIteratorState incomingEdge = (VirtualEdgeIteratorState) queryGraph.getEdgeIteratorState(1, 2); VirtualEdgeIteratorState incomingEdgeReverse = (VirtualEdgeIteratorState) queryGraph.getEdgeIteratorState(1, incomingEdge.getBaseNode()); @@ -688,13 +677,12 @@ public void testInternalAPIOriginalEdgeKey() { public void testWayGeometry_edge() { // drawn as horizontal linear graph for simplicity // 0 - * - x - * - 1 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0.3, 0.3); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60). + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(0.1, 0.1, 0.2, 0.2)); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0.3, 0.3); - LocationIndexTree locationIndex = new LocationIndexTree(g, new RAMDirectory()); + LocationIndexTree locationIndex = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); locationIndex.prepareIndex(); Snap snap = locationIndex.findClosest(0.15, 0.15, EdgeFilter.ALL_EDGES); assertTrue(snap.isValid()); @@ -730,13 +718,12 @@ public void testWayGeometry_pillar() { // * // / // 0 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0.5, 0.1); - g.edge(0, 1).setDistance(10).set(speedEnc, 60, 60). + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(0.1, 0.1, 0.2, 0.2)); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0.5, 0.1); - LocationIndexTree locationIndex = new LocationIndexTree(g, new RAMDirectory()); + LocationIndexTree locationIndex = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); locationIndex.prepareIndex(); Snap snap = locationIndex.findClosest(0.2, 0.21, EdgeFilter.ALL_EDGES); assertTrue(snap.isValid()); @@ -769,19 +756,13 @@ public void testVirtualEdgeDistance() { // ----- // | | // 0 1 - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 0, 0); - na.setNode(1, 0, 1); - // dummy node to make sure graph bounds are valid - na.setNode(2, 2, 2); - DistanceCalc distCalc = DistancePlaneProjection.DIST_PLANE; - double dist = 0; - dist += distCalc.calcDist(0, 0, 1, 0); - dist += distCalc.calcDist(1, 0, 1, 1); - dist += distCalc.calcDist(1, 1, 0, 1); - g.edge(0, 1).setDistance(dist).set(speedEnc, 60, 60). + g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60). setWayGeometry(Helper.createPointList(1, 0, 1, 1)); - LocationIndexTree index = new LocationIndexTree(g, new RAMDirectory()); + updateDistancesFor(g, 0, 0, 0); + updateDistancesFor(g, 1, 0, 1); + // dummy node to make sure graph bounds are valid + updateDistancesFor(g, 2, 2, 2); + LocationIndexTree index = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); index.prepareIndex(); Snap snap = index.findClosest(1.01, 0.7, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = lookup(snap); @@ -806,13 +787,11 @@ public void testVirtualEdgeIds() { DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); BaseGraph g = new BaseGraph.Builder(em).create(); - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 50.00, 10.10); - na.setNode(1, 50.00, 10.20); - double dist = DistanceCalcEarth.DIST_EARTH.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1)); - EdgeIteratorState edge = g.edge(0, 1).setDistance(dist).set(speedEnc, 60, 60); + EdgeIteratorState edge = g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60); edge.set(speedEnc, 50); edge.setReverse(speedEnc, 100); + updateDistancesFor(g, 0, 50.00, 10.10); + updateDistancesFor(g, 1, 50.00, 10.20); // query graph Snap snap = createLocationResult(50.00, 10.15, edge, 0, EDGE); @@ -880,13 +859,11 @@ public void testVirtualEdgeIds_reverse() { DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); BaseGraph g = new BaseGraph.Builder(em).create(); - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 50.00, 10.10); - na.setNode(1, 50.00, 10.20); - double dist = DistanceCalcEarth.DIST_EARTH.calcDist(na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1)); // this time we store the edge the other way - EdgeIteratorState edge = g.edge(1, 0).setDistance(dist).set(speedEnc, 60, 60); + EdgeIteratorState edge = g.edge(1, 0).setDistance(0).set(speedEnc, 60, 60); edge.set(speedEnc, 100, 50); + updateDistancesFor(g, 0, 50.00, 10.10); + updateDistancesFor(g, 1, 50.00, 10.20); // query graph Snap snap = createLocationResult(50.00, 10.15, edge, 0, EDGE); @@ -956,10 +933,9 @@ public void testTotalEdgeCount() { // 0 - x --- x - 1 // virtual edges: 1 2/3 4 BaseGraph g = new BaseGraph.Builder(1).create(); - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 50.00, 10.00); - na.setNode(1, 50.00, 10.30); g.edge(0, 1); + updateDistancesFor(g, 0, 50.00, 10.00); + updateDistancesFor(g, 1, 50.00, 10.30); LocationIndexTree locationIndex = new LocationIndexTree(g, g.getDirectory()); locationIndex.prepareIndex(); @@ -1000,12 +976,11 @@ public void testExternalEV() { g.edge(2, 3).set(intEnc, 7).set(enumEnc, RoadClass.PRIMARY).set(roadClassLincEnc, true).set(externalEnc, true, false); g.edge(3, 4).set(intEnc, 1).set(enumEnc, RoadClass.MOTORWAY).set(roadClassLincEnc, false).set(externalEnc, true, false); - NodeAccess na = g.getNodeAccess(); - na.setNode(0, 50.00, 10.00); - na.setNode(1, 50.10, 10.10); - na.setNode(2, 50.20, 10.20); - na.setNode(3, 50.30, 10.30); - na.setNode(4, 50.40, 10.40); + updateDistancesFor(g, 0, 50.00, 10.00); + updateDistancesFor(g, 1, 50.10, 10.10); + updateDistancesFor(g, 2, 50.20, 10.20); + updateDistancesFor(g, 3, 50.30, 10.30); + updateDistancesFor(g, 4, 50.40, 10.40); LocationIndexTree locationIndex = new LocationIndexTree(g, g.getDirectory()); locationIndex.prepareIndex(); @@ -1034,6 +1009,186 @@ public void testExternalEV() { assertFalse(virt64.getReverse(externalEnc)); } + @Test + public void directedKeyValues() { + Map kvs = new HashMap<>(); + kvs.put("a", new KVStorage.KValue("hello", null)); + kvs.put("b", new KVStorage.KValue(null, "world")); + EdgeIteratorState origEdge = g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60).setKeyValues(kvs); + updateDistancesFor(g, 0, 1, 0); + updateDistancesFor(g, 1, 1, 2.5); + + // keyValues List stays the same + assertEquals(origEdge.getKeyValues().toString(), origEdge.detach(true).getKeyValues().toString()); + // determine if edge is reverse via origEdge.get(EdgeIteratorState.REVERSE_STATE) + + // but getValue is sensitive to direction + assertEquals("hello", origEdge.getValue("a")); + assertNull(origEdge.detach(true).getValue("a")); + assertEquals("world", origEdge.detach(true).getValue("b")); + assertNull(origEdge.getValue("b")); + + LocationIndexTree index = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); + index.prepareIndex(); + Snap snap = index.findClosest(1.01, 0.7, EdgeFilter.ALL_EDGES); + QueryGraph queryGraph = lookup(snap); + EdgeIteratorState edge0ToSnap = queryGraph.getEdgeIteratorState(1, 2); + + assertEquals(edge0ToSnap.getKeyValues().toString(), edge0ToSnap.detach(true).getKeyValues().toString()); + + assertEquals("hello", edge0ToSnap.getValue("a")); + assertNull(edge0ToSnap.detach(true).getValue("a")); + assertEquals("world", edge0ToSnap.detach(true).getValue("b")); + assertNull(edge0ToSnap.getValue("b")); + } + + @Test + void veryShortEdge() { + EdgeIteratorState e = g.edge(0, 1); + updateDistancesFor(g, 0, 40.000_000, 6.000_000); + updateDistancesFor(g, 1, 40.000_000, 6.000_001); + NodeAccess na = g.getNodeAccess(); + // the edge is very short + assertEquals(0.085, e.getDistance(), 1.e-3); + double queryLat = 40.001_000; + double queryLon = 6.000_0009; + double queryTo0 = DIST_PLANE.calcDist(queryLat, queryLon, na.getLat(0), na.getLon(0)); + double queryTo1 = DIST_PLANE.calcDist(queryLat, queryLon, na.getLat(1), na.getLon(1)); + // the query point is relatively far away from the edge + assertEquals(111.1949530, queryTo0, 1.e-7); + assertEquals(111.1949269, queryTo1, 1.e-7); + GHPoint crossingPoint = DIST_PLANE.calcCrossingPointToEdge(queryLat, queryLon, na.getLat(0), na.getLon(0), na.getLat(1), na.getLon(1)); + double distCrossingTo0 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(0), na.getLon(0)); + double distCrossingTo1 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(1), na.getLon(1)); + // ... but the crossing point is very close to both nodes of the edge + assertEquals(0.0766, distCrossingTo0, 1.e-4); + assertEquals(0.0085, distCrossingTo1, 1.e-4); + // ... and closer to node 1 than to node 0 + assertTrue(distCrossingTo1 < distCrossingTo0); + + LocationIndexTree index = new LocationIndexTree(g, new GHDirectory("", DAType.RAM)); + index.prepareIndex(); + Snap snap = index.findClosest(queryLat, queryLon, EdgeFilter.ALL_EDGES); + // Although this is technically an 'edge-snap', we snap to the tower node, because the **crossing** point + // is so close to a tower node (in our case it is even close to both tower nodes). + assertEquals(TOWER, snap.getSnappedPosition()); + // We do not enforce that the closer of the two tower nodes is chosen. It does not really matter. + // Here it is node 0, because we first try the base node. + int closestNode = snap.getClosestNode(); + assertEquals(0, closestNode); + // ... but what does matter is that the coordinates of the snapped point match the coordinates of the closest node! + // This isn't entirely obvious here, because `index.findClosest` first considers the snap an edge snap and only + // later updates it to a tower snap. See #3009 + assertEquals(na.getLat(closestNode), snap.getSnappedPoint().getLat()); + assertEquals(na.getLon(closestNode), snap.getSnappedPoint().getLon()); + // also the distance should be correct + assertEquals(queryTo0, snap.getQueryDistance()); + assertEquals(0, snap.getWayIndex()); + } + + @Test + void adjustDistances_noNegativeVirtualEdgeDistance() { + EdgeIteratorState edge = g.edge(0, 1).setDistance(0).set(speedEnc, 10, 10); + updateDistancesFor(g, 0, 60.0, 10.0); + updateDistancesFor(g, 1, 60.0, 11.0); + long originalDistance = 55596934; + assertEquals(originalDistance, edge.getDistance_mm()); + // snap very close to point 0 -> very short virtual edge + Snap snap = createLocationResult(60.01, 10.000002, edge, 0, EDGE); + QueryGraph queryGraph = lookup(snap); + long sumFwd = 0, sumBwd = 0; + List virtualEdges = queryGraph.getVirtualEdges(); + for (int i = 0; i < virtualEdges.size(); i++) { + EdgeIteratorState ve = virtualEdges.get(i); + assertTrue(ve.getDistance_mm() >= 0, "virtual edge distance must not be negative, got: " + ve.getDistance_mm()); + if (i % 2 == 0) + sumFwd += ve.getDistance_mm(); + else + sumBwd += ve.getDistance_mm(); + } + // since the edge is long there is around 0.5m difference between the original distance calculated by dist_earth and + // the virtual edge distance sum calculated by dist_plane -> make sure we do not shorten the short edge too much + assertEquals(edge.getDistance_mm() + 529, sumFwd); + assertEquals(edge.getDistance_mm() + 529, sumBwd); + assertEquals(4, virtualEdges.size()); + // no correction since it is above limits + assertEquals(111, virtualEdges.get(0).getDistance_mm()); + assertEquals(111, virtualEdges.get(1).getDistance_mm()); + assertEquals(sumFwd - 111, virtualEdges.get(2).getDistance_mm()); + assertEquals(sumBwd - 111, virtualEdges.get(3).getDistance_mm()); + } + + @Test + public void testEleInterpolation() { + g = new BaseGraph.Builder(encodingManager).set3D(true).create(); + g.edge(0, 1); + + NodeAccess na = g.getNodeAccess(); + na.setNode(0, 40.0000, 10.0000, 300.0); + na.setNode(1, 40.0005, 10.0005, 500.0); + + LocationIndex index = new LocationIndexTree(g, g.getDirectory()).prepareIndex(); + Snap snap1 = index.findClosest(40.0002, 10.0002, EdgeFilter.ALL_EDGES); + Snap snap2 = index.findClosest(40.0003, 10.0003, EdgeFilter.ALL_EDGES); + Snap snap3 = index.findClosest(40.0004, 10.0004, EdgeFilter.ALL_EDGES); + + QueryGraph queryGraph = lookup(Arrays.asList(snap1, snap2, snap3)); + // we expect linear elevation interpolation between the adjacent points + assertEquals(300 + 0.4 * 200, queryGraph.getNodeAccess().getEle(g.getNodes() + 0), 1.e-1); + assertEquals(300 + 0.6 * 200, queryGraph.getNodeAccess().getEle(g.getNodes() + 1), 1.e-1); + assertEquals(300 + 0.8 * 200, queryGraph.getNodeAccess().getEle(g.getNodes() + 2), 1.e-1); + } + + @Test + public void testUTurnAtVirtualNodesOnLongBidirectionalEdge() { + // Create a single long bidirectional edge: 0 --- 1 + // with pillar nodes so we can snap along the way + // + // 0 ---(snap A)---(snap B)---(snap C)--- 1 + // + EdgeIteratorState edge = g.edge(0, 1).setDistance(0).set(speedEnc, 60, 60) + .setWayGeometry(Helper.createPointList(1, 1, 1, 2, 1, 3)); + updateDistancesFor(g, 0, 1, 0); + updateDistancesFor(g, 1, 1, 4); + + // Snap three points along the edge in EDGE mode + Snap snapA = createLocationResult(1, 0.5, edge, 0, EDGE); + Snap snapB = createLocationResult(1, 1.5, edge, 1, EDGE); + Snap snapC = createLocationResult(1, 2.5, edge, 2, EDGE); + + QueryGraph queryGraph = QueryGraph.create(g, Arrays.asList(snapA, snapB, snapC)); + int nodeA = snapA.getClosestNode(); + int nodeB = snapB.getClosestNode(); + int nodeC = snapC.getClosestNode(); + + // All three snaps should have created virtual nodes + assertEquals(5, queryGraph.getNodes()); // 2 real + 3 virtual + + // Each virtual node should have exactly 2 edges (toward base side and toward adj side) + EdgeExplorer explorer = queryGraph.createEdgeExplorer(); + assertEquals(2, GHUtility.count(explorer.setBaseNode(nodeA))); + assertEquals(2, GHUtility.count(explorer.setBaseNode(nodeB))); + assertEquals(2, GHUtility.count(explorer.setBaseNode(nodeC))); + + // Now test turn costs at virtual nodes via QueryGraphWeighting + Weighting baseWeighting = new SpeedWeighting(speedEnc); + Weighting weighting = queryGraph.wrapWeighting(baseWeighting); + + // At virtual node B: get the two edges + EdgeIteratorState edgeBtowardA = GHUtility.getEdge(queryGraph, nodeB, nodeA); + EdgeIteratorState edgeBtowardC = GHUtility.getEdge(queryGraph, nodeB, nodeC); + assertNotNull(edgeBtowardA); + assertNotNull(edgeBtowardC); + + // Going straight through B (A->B->C or C->B->A) should have zero turn cost + assertEquals(0, weighting.calcTurnWeight(edgeBtowardA.getEdge(), nodeB, edgeBtowardC.getEdge())); + assertEquals(0, weighting.calcTurnWeight(edgeBtowardC.getEdge(), nodeB, edgeBtowardA.getEdge())); + + // U-turn at B (coming from A side, going back toward A) should be infinite + assertEquals(Double.POSITIVE_INFINITY, weighting.calcTurnWeight(edgeBtowardA.getEdge(), nodeB, edgeBtowardA.getEdge())); + assertEquals(Double.POSITIVE_INFINITY, weighting.calcTurnWeight(edgeBtowardC.getEdge(), nodeB, edgeBtowardC.getEdge())); + } + private QueryGraph lookup(Snap res) { return lookup(Collections.singletonList(res)); } diff --git a/core/src/test/java/com/graphhopper/routing/querygraph/QueryOverlayTest.java b/core/src/test/java/com/graphhopper/routing/querygraph/QueryOverlayTest.java new file mode 100644 index 00000000000..c521401a9ed --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/querygraph/QueryOverlayTest.java @@ -0,0 +1,91 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.querygraph; + +import com.carrotsearch.hppc.LongArrayList; +import org.junit.jupiter.api.Test; + +import static com.carrotsearch.hppc.LongArrayList.from; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class QueryOverlayTest { + + @Test + void adjustValues() { + // no adjustment needed + checkAdjustValues(from(3, 4, 3), 10, 1, from(3, 4, 3)); + // positive diff, add 1 to each + checkAdjustValues(from(3, 3, 3), 12, 1, from(4, 4, 4)); + // negative diff, subtract 1 from each + checkAdjustValues(from(5, 5, 5), 12, 1, from(4, 4, 4)); + // skip zero when subtracting + checkAdjustValues(from(2, 0, 3), 3, 1, from(1, 0, 2)); + // target zero, unchanged + checkAdjustValues(from(1, 1, 1), 0, 1, from(1, 1, 1)); + // diff exceeds n, unchanged + checkAdjustValues(from(1, 1, 1), 10, 1, from(1, 1, 1)); + // negative diff exceeds n, unchanged + checkAdjustValues(from(4, 3, 3), 3, 1, from(4, 3, 3)); + // empty array + checkAdjustValues(from(), 5, 1, from()); + // single value + checkAdjustValues(from(5), 6, 1, from(6)); + // single value, diff exceeds n, unchanged + checkAdjustValues(from(5), 8, 1, from(5)); + // all zeros, positive target + checkAdjustValues(from(0, 0, 0), 3, 1, from(1, 1, 1)); + // all zeros, target zero + checkAdjustValues(from(0, 0, 0), 0, 1, from(0, 0, 0)); + // partial addition + checkAdjustValues(from(3, 3, 4), 12, 1, from(4, 4, 4)); + // infeasible: first must increase to 1, but third can only decrease by 1, giving min_sum=4 > target=3 + checkAdjustValues(from(0, 0, 4), 3, 1, from(0, 0, 4)); + // reduce to zero + checkAdjustValues(from(1, 1), 1, 1, from(1, 0)); + + // diff=+6 + checkAdjustValues(from(3, 3, 4), 16, 2, from(5, 5, 6)); + // diff=7, unchanged + checkAdjustValues(from(1, 1, 1), 10, 2, from(1, 1, 1)); + // diff=4, would exceed max=1 but fits max=2 + checkAdjustValues(from(1, 1, 1), 7, 2, from(3, 3, 1)); + // subtract evenly + checkAdjustValues(from(4, 4, 4), 6, 2, from(2, 2, 2)); + // infeasible: first must increase to 1, elements 1,2 can decrease by at most 2 each giving min_sum=2 > target=1 + checkAdjustValues(from(0, 3, 2), 1, 2, from(0, 3, 2)); + // infeasible: elements can decrease by at most 4 each giving min_sum=4 > target=1 + checkAdjustValues(from(8, 3, 2), 1, 4, from(8, 3, 2)); + // single element, diff=2 fits + checkAdjustValues(from(5), 7, 2, from(7)); + // single element, diff=3, max exceeded, unchanged + checkAdjustValues(from(5), 8, 2, from(5)); + // greedy + checkAdjustValues(from(3, 3, 3), 13, 2, from(5, 5, 3)); + // feasible + checkAdjustValues(from(1, 3, 3, 3), 4, 2, from(1, 1, 1, 1)); + // infeasible + checkAdjustValues(from(1, 3, 3, 3), 3, 2, from(1, 3, 3, 3)); + checkAdjustValues(from(1, 5), 3, 2, from(1, 5)); + checkAdjustValues(from(1, 3, 3, 3), 2, 2, from(1, 3, 3, 3)); + } + + private void checkAdjustValues(LongArrayList values, long target, int maxPerElement, LongArrayList expectedValues) { + QueryOverlay.adjustValues(values, target, maxPerElement); + assertEquals(expectedValues, values); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java index 5f5c36feafb..afbf48ab15a 100644 --- a/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java +++ b/core/src/test/java/com/graphhopper/routing/subnetwork/EdgeBasedTarjanSCCTest.java @@ -28,10 +28,10 @@ import com.graphhopper.routing.util.AllEdgesIterator; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.GHUtility; +import com.graphhopper.util.RandomGraph; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; -import java.util.Random; import java.util.Set; import static com.graphhopper.routing.subnetwork.TarjanSCCTest.buildComponentSet; @@ -48,11 +48,10 @@ public EdgeBasedTarjanSCCTest() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); speedEnc.init(evConf); - g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); fwdAccessFilter = (prev, edge) -> edge.get(speedEnc) > 0; } - @Test public void linearSingle() { // 0 - 1 @@ -239,16 +238,19 @@ public void withTurnRestriction() { } } - @RepeatedTest(20) - public void implicitVsExplicitRecursion() { + @RepeatedTest(10) + public void implicitVsExplicitRecursionExcludeSingle() { doImplicitVsExplicit(true); + } + + @RepeatedTest(10) + public void implicitVsExplicitRecursion() { doImplicitVsExplicit(false); } private void doImplicitVsExplicit(boolean excludeSingle) { long seed = System.nanoTime(); - Random rnd = new Random(seed); - GHUtility.buildRandomGraph(g, rnd, 500, 2, true, speedEnc, 60d, 0.7, 0); + RandomGraph.start().seed(seed).nodes(500).speed(10d).speedZero(0.1).fill(g, speedEnc); ConnectedComponents implicit = EdgeBasedTarjanSCC.findComponentsRecursive(g, fwdAccessFilter, excludeSingle); ConnectedComponents explicit = EdgeBasedTarjanSCC.findComponents(g, fwdAccessFilter, excludeSingle); assertEquals(2 * g.getEdges(), implicit.getEdgeKeys(), "total number of edge keys in connected components should equal twice the number of edges in graph"); @@ -280,21 +282,13 @@ public void withStartEdges_simple() { components = EdgeBasedTarjanSCC.findComponentsForStartEdges(g, (prev, edge) -> true, IntArrayList.from(0, 4, 7)); assertEquals(16, components.getEdgeKeys()); assertEquals(3, components.getComponents().size()); - - // here we initialize as for all islands but the filter still prevents some edges to be found - components = EdgeBasedTarjanSCC.findComponentsForStartEdges(g, - (prev, edge) -> edge.getEdge() > 3 && edge.getEdge() < 7, IntArrayList.from(0, 4, 7)); - assertEquals(6, components.getEdgeKeys()); - assertEquals(1, components.getComponents().size()); - } @RepeatedTest(20) public void withStartEdges_comparison() { // we test the case where we specify all start edges (in this case the behavior should be the same for both methods) long seed = System.nanoTime(); - Random rnd = new Random(seed); - GHUtility.buildRandomGraph(g, rnd, 500, 2, true, speedEnc, 60d, 0.7, 0); + RandomGraph.start().seed(seed).nodes(500).speed(10d).speedZero(0.1).fill(g, speedEnc); ConnectedComponents components = EdgeBasedTarjanSCC.findComponents(g, fwdAccessFilter, true); IntArrayList edges = new IntArrayList(); AllEdgesIterator iter = g.getAllEdges(); diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/SubnetworkStorageTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/SubnetworkStorageTest.java index 821dc7595ff..7a2f9c8ad06 100644 --- a/core/src/test/java/com/graphhopper/routing/subnetwork/SubnetworkStorageTest.java +++ b/core/src/test/java/com/graphhopper/routing/subnetwork/SubnetworkStorageTest.java @@ -1,6 +1,7 @@ package com.graphhopper.routing.subnetwork; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.DAType; +import com.graphhopper.storage.GHDirectory; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -9,7 +10,7 @@ public class SubnetworkStorageTest { @Test public void testSimple() { - SubnetworkStorage storage = new SubnetworkStorage(new RAMDirectory().create("test")); + SubnetworkStorage storage = new SubnetworkStorage(new GHDirectory("", DAType.RAM).create("test")); storage.create(2000); storage.setSubnetwork(1, 88); assertEquals(88, storage.getSubnetwork(1)); diff --git a/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java b/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java index c26c759896a..711b2a89d1e 100644 --- a/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java +++ b/core/src/test/java/com/graphhopper/routing/subnetwork/TarjanSCCTest.java @@ -26,6 +26,7 @@ import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.GHUtility; +import com.graphhopper.util.RandomGraph; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; @@ -43,7 +44,7 @@ public TarjanSCCTest() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); speedEnc.init(evConf); - graph = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + graph = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); edgeFilter = edge -> edge.get(speedEnc) > 0; } @@ -185,16 +186,19 @@ public void testTarjan_issue761() { assertEquals(IntArrayList.from(8, 7, 6, 3, 4, 9, 10, 11, 2, 1, 0), scc.getComponents().get(1)); } - @RepeatedTest(30) + @RepeatedTest(10) public void implicitVsExplicitRecursion() { - doImplicitVsExplicit(true); doImplicitVsExplicit(false); } + @RepeatedTest(10) + public void implicitVsExplicitRecursionExcludeSingle() { + doImplicitVsExplicit(true); + } + private void doImplicitVsExplicit(boolean excludeSingle) { long seed = System.nanoTime(); - Random rnd = new Random(seed); - GHUtility.buildRandomGraph(graph, rnd, 1_000, 2, true, speedEnc, 60d, 0.7, 0); + RandomGraph.start().seed(seed).nodes(500).speed(10d).speedZero(0.1).fill(graph, speedEnc); TarjanSCC.ConnectedComponents implicit = TarjanSCC.findComponentsRecursive(graph, edgeFilter, excludeSingle); TarjanSCC.ConnectedComponents explicit = TarjanSCC.findComponents(graph, edgeFilter, excludeSingle); diff --git a/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java index 3405025ac6d..d9a4a277b92 100644 --- a/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/CurvatureCalculatorTest.java @@ -1,53 +1,62 @@ package com.graphhopper.routing.util; -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.ArrayEdgeIntAccess; import com.graphhopper.routing.ev.Curvature; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.FetchMode; import com.graphhopper.util.PointList; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class CurvatureCalculatorTest { private final EncodingManager em = EncodingManager.start().add(Curvature.create()).build(); + private final DecimalEncodedValue curvatureEnc = em.getDecimalEncodedValue(Curvature.KEY); @Test public void testCurvature() { - CurvatureCalculator calculator = new CurvatureCalculator(em.getDecimalEncodedValue(Curvature.KEY)); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - int edgeId = 0; - calculator.handleWayTags(edgeId, intAccess, getStraightWay(), null); - double valueStraight = em.getDecimalEncodedValue(Curvature.KEY).getDecimal(false, edgeId, intAccess); + BaseGraph graph = new BaseGraph.Builder(em).create(); + NodeAccess na = graph.getNodeAccess(); - intAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - calculator.handleWayTags(edgeId, intAccess, getCurvyWay(), null); - double valueCurvy = em.getDecimalEncodedValue(Curvature.KEY).getDecimal(false, edgeId, intAccess); + // straight way: 2 tower nodes, ~100m + na.setNode(0, 50.9, 13.13); + na.setNode(1, 50.899, 13.13); + EdgeIteratorState straightEdge = graph.edge(0, 1).setDistance(100); - assertTrue(valueCurvy < valueStraight, "The bendiness of the straight road is smaller than the one of the curvy road"); - } + // curvy way: 2 tower nodes + 1 pillar, ~160m + na.setNode(2, 50.9, 13.13); + na.setNode(3, 50.899, 13.13); + PointList pillar = new PointList(); + pillar.add(50.899, 13.129); + EdgeIteratorState curvyEdge = graph.edge(2, 3).setWayGeometry(pillar).setDistance(160); - private ReaderWay getStraightWay() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "primary"); - PointList pointList = new PointList(); - pointList.add(50.9, 13.13); - pointList.add(50.899, 13.13); - way.setTag("point_list", pointList); - way.setTag("edge_distance", 100d); - return way; - } + new CurvatureCalculator(curvatureEnc).execute(graph); - private ReaderWay getCurvyWay() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "primary"); - PointList pointList = new PointList(); - pointList.add(50.9, 13.13); - pointList.add(50.899, 13.129); - pointList.add(50.899, 13.13); - way.setTag("point_list", pointList); - way.setTag("edge_distance", 160d); - return way; + double valueStraight = straightEdge.get(curvatureEnc); + double valueCurvy = curvyEdge.get(curvatureEnc); + assertTrue(valueCurvy < valueStraight, "The bendiness of the straight road is smaller than the one of the curvy road"); } -} \ No newline at end of file + @Test + public void testCurvatureWithElevation() { + BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 50.9, 13.13, 0); + na.setNode(1, 50.899, 13.13, 100); + PointList pointList = new PointList(2, true); + pointList.add(50.9, 13.13, 0); + pointList.add(50.899, 13.13, 100); + double distance = DistanceCalcEarth.DIST_EARTH.calcDistance(pointList); + EdgeIteratorState edge = graph.edge(0, 1).setDistance(distance); + + new CurvatureCalculator(curvatureEnc).execute(graph); + + double curvature = edge.get(curvatureEnc); + assertEquals(1, curvature, 0.01); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java index 2ecac791e91..017d45a3032 100644 --- a/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/EncodingManagerTest.java @@ -17,10 +17,7 @@ */ package com.graphhopper.routing.util; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.ev.VehicleAccess; -import com.graphhopper.routing.ev.VehicleSpeed; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.parsers.BikeAccessParser; import com.graphhopper.routing.util.parsers.CarAccessParser; import com.graphhopper.routing.util.parsers.FootAccessParser; @@ -36,30 +33,6 @@ */ public class EncodingManagerTest { - @Test - public void testSupportFords() { - EncodingManager manager = new EncodingManager.Builder() - .add(VehicleEncodedValues.car(new PMap())) - .add(VehicleEncodedValues.bike(new PMap())) - .add(VehicleEncodedValues.foot(new PMap())). - build(); - - // 1) default -> no block fords - assertFalse(new CarAccessParser(manager, new PMap()).isBlockFords()); - assertFalse(new BikeAccessParser(manager, new PMap()).isBlockFords()); - assertFalse(new FootAccessParser(manager, new PMap()).isBlockFords()); - - // 2) true - assertTrue(new CarAccessParser(manager, new PMap("block_fords=true")).isBlockFords()); - assertTrue(new BikeAccessParser(manager, new PMap("block_fords=true")).isBlockFords()); - assertTrue(new FootAccessParser(manager, new PMap("block_fords=true")).isBlockFords()); - - // 3) false - assertFalse(new CarAccessParser(manager, new PMap("block_fords=false")).isBlockFords()); - assertFalse(new BikeAccessParser(manager, new PMap("block_fords=false")).isBlockFords()); - assertFalse(new FootAccessParser(manager, new PMap("block_fords=false")).isBlockFords()); - } - @Test public void testRegisterOnlyOnceAllowed() { DecimalEncodedValueImpl speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); diff --git a/core/src/test/java/com/graphhopper/routing/util/FerrySpeedCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/FerrySpeedCalculatorTest.java index 5c27296a9e3..01c3956cec6 100644 --- a/core/src/test/java/com/graphhopper/routing/util/FerrySpeedCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/FerrySpeedCalculatorTest.java @@ -37,35 +37,35 @@ class FerrySpeedCalculatorTest { final FerrySpeedCalculator calc = new FerrySpeedCalculator(ferrySpeedEnc); @Test - public void testSpeed() { + public void + testSpeed() { ReaderWay way = new ReaderWay(1); way.setTag("route", "ferry"); - way.setTag("edge_distance", 30000.0); - way.setTag("speed_from_duration", 30 / 0.5); + way.setTag("way_distance_2d", 30_000.0); + way.setTag("duration_in_seconds", 1800L); EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); int edgeId = 0; calc.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); - assertEquals(44, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); + assertEquals(30, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); way = new ReaderWay(1); way.setTag("route", "shuttle_train"); way.setTag("motorcar", "yes"); way.setTag("bicycle", "no"); - // Provide the duration value in seconds: - way.setTag("way_distance", 50000.0); - way.setTag("speed_from_duration", 50 / (35.0 / 60)); + + way.setTag("way_distance_2d", 50000.0); + way.setTag("duration_in_seconds", 2100L); edgeIntAccess = new ArrayEdgeIntAccess(1); - // calculate speed from tags: speed_from_duration * 1.4 (+ rounded using the speed factor) calc.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); - assertEquals(62, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); + assertEquals(46, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); // test for very short and slow 0.5km/h still realistic ferry way = new ReaderWay(1); way.setTag("route", "ferry"); way.setTag("motorcar", "yes"); - way.setTag("way_distance", 100.0); - way.setTag("speed_from_duration", 0.1 / (12.0 / 60)); + way.setTag("way_distance_2d", 100.0); + way.setTag("duration_in_seconds", 720L); // we can't store 0.5km/h, but we expect the lowest possible speed edgeIntAccess = new ArrayEdgeIntAccess(1); @@ -83,33 +83,36 @@ public void testSpeed() { way.setTag("edge_distance", 100.0); calc.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); // we use the unknown speed - assertEquals(2, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); + assertEquals(6, ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); } @Test void testRawSpeed() { - // speed_from_duration is set (edge_distance is not even needed) - checkSpeed(30.0, null, Math.round(30 / 1.4)); - checkSpeed(45.0, null, Math.round(45 / 1.4)); + checkSpeed(3600L, 30_000.0, null, 20); + checkSpeed(3600L, 45_000.0, null, 30); // above max (when including waiting time) (capped to max) - checkSpeed(100.0, null, ferrySpeedEnc.getMaxStorableDecimal()); + checkSpeed(3600L, 100_000.0, null, ferrySpeedEnc.getMaxStorableDecimal()); // below smallest storable non-zero value - checkSpeed(0.5, null, ferrySpeedEnc.getSmallestNonZeroValue()); + checkSpeed(3600L, 1000.0, null, ferrySpeedEnc.getSmallestNonZeroValue()); - // no speed_from_duration, but edge_distance is present + // no duration_in_seconds, but edge_distance is present // minimum speed for short ferries - checkSpeed(null, 100.0, ferrySpeedEnc.getSmallestNonZeroValue()); - // unknown speed for longer ones - checkSpeed(null, 1000.0, 6); + checkSpeed(null, null, 100.0, 5); + // longer ferries... + checkSpeed(null, null, 2_000.0, 15); + checkSpeed(null, null, 40_000.0, 30); // no speed, no distance -> error. this should never happen as we always set the edge distance. - assertThrows(IllegalStateException.class, () -> checkSpeed(null, null, 6)); + assertThrows(IllegalStateException.class, () -> + checkSpeed(null, null, null, 6)); } - private void checkSpeed(Double speedFromDuration, Double edgeDistance, double expected) { + private void checkSpeed(Long durationInSeconds, Double wayDistance, Double edgeDistance, double expected) { ReaderWay way = new ReaderWay(0L); - if (speedFromDuration != null) - way.setTag("speed_from_duration", speedFromDuration); + if (durationInSeconds != null) { + way.setTag("way_distance_2d", wayDistance); + way.setTag("duration_in_seconds", durationInSeconds); + } if (edgeDistance != null) way.setTag("edge_distance", edgeDistance); assertEquals(expected, FerrySpeedCalculator.minmax(getSpeed(way), ferrySpeedEnc)); diff --git a/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java index e5986186279..fee8589ae97 100644 --- a/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/MaxSpeedCalculatorTest.java @@ -4,7 +4,8 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.parsers.OSMMaxSpeedParser; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.RAMDirectory; +import com.graphhopper.storage.DAType; +import com.graphhopper.storage.GHDirectory; import com.graphhopper.util.EdgeIteratorState; import de.westnordost.osm_legal_default_speeds.LegalDefaultSpeeds; import org.junit.jupiter.api.BeforeEach; @@ -14,8 +15,8 @@ import java.util.HashMap; import java.util.Map; -import static com.graphhopper.routing.ev.MaxSpeed.UNLIMITED_SIGN_SPEED; -import static com.graphhopper.routing.ev.MaxSpeed.UNSET_SPEED; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_150; +import static com.graphhopper.routing.ev.MaxSpeed.MAXSPEED_MISSING; import static com.graphhopper.routing.ev.UrbanDensity.CITY; import static com.graphhopper.routing.ev.UrbanDensity.RURAL; import static org.junit.jupiter.api.Assertions.*; @@ -48,34 +49,34 @@ public void setup() { parsers.addWayTagParser(new OSMMaxSpeedParser(maxSpeedEnc)); parsers.addWayTagParser(calc.getParser()); - calc.createDataAccessForParser(new RAMDirectory()); + calc.createDataAccessForParser(new GHDirectory("", DAType.RAM)); } @Test public void internalMaxSpeed() { EdgeIntAccess storage = calc.getInternalMaxSpeedStorage(); DecimalEncodedValue ruralEnc = calc.getRuralMaxSpeedEnc(); - ruralEnc.setDecimal(false, 0, storage, UNSET_SPEED); - assertEquals(UNSET_SPEED, ruralEnc.getDecimal(false, 0, storage)); + ruralEnc.setDecimal(false, 0, storage, MAXSPEED_MISSING); + assertEquals(MAXSPEED_MISSING, ruralEnc.getDecimal(false, 0, storage)); ruralEnc.setDecimal(false, 1, storage, 33); assertEquals(34, ruralEnc.getDecimal(false, 1, storage)); DecimalEncodedValue urbanEnc = calc.getUrbanMaxSpeedEnc(); - urbanEnc.setDecimal(false, 1, storage, UNSET_SPEED); - assertEquals(UNSET_SPEED, urbanEnc.getDecimal(false, 1, storage)); + urbanEnc.setDecimal(false, 1, storage, MAXSPEED_MISSING); + assertEquals(MAXSPEED_MISSING, urbanEnc.getDecimal(false, 1, storage)); urbanEnc.setDecimal(false, 0, storage, 46); assertEquals(46, urbanEnc.getDecimal(false, 0, storage)); // check that they are not modified - assertEquals(UNSET_SPEED, ruralEnc.getDecimal(false, 0, storage)); + assertEquals(MAXSPEED_MISSING, ruralEnc.getDecimal(false, 0, storage)); assertEquals(34, ruralEnc.getDecimal(false, 1, storage)); } EdgeIteratorState createEdge(ReaderWay way) { EdgeIteratorState edge = graph.edge(0, 1); - EdgeIntAccess edgeIntAccess = graph.createEdgeIntAccess(); + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); parsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, em.createRelationFlags()); return edge; } @@ -95,7 +96,7 @@ public void testCityGermany() { way.setTag("highway", "motorway"); edge = createEdge(way).set(urbanDensity, CITY); calc.fillMaxSpeed(graph, em); - assertEquals(UNLIMITED_SIGN_SPEED, edge.get(maxSpeedEnc), 1); + assertEquals(MAXSPEED_150, edge.get(maxSpeedEnc), 1); way = new ReaderWay(0L); way.setTag("country", Country.DEU); @@ -129,7 +130,7 @@ public void testRuralGermany() { way.setTag("highway", "motorway"); edge = createEdge(way).set(urbanDensity, RURAL); calc.fillMaxSpeed(graph, em); - assertEquals(UNLIMITED_SIGN_SPEED, edge.get(maxSpeedEnc), 1); + assertEquals(MAXSPEED_150, edge.get(maxSpeedEnc), 1); way = new ReaderWay(0L); way.setTag("country", Country.DEU); @@ -305,6 +306,6 @@ public void testUnsupportedCountry() { way.setTag("highway", "primary"); EdgeIteratorState edge = createEdge(way).set(urbanDensity, CITY); calc.fillMaxSpeed(graph, em); - assertEquals(UNSET_SPEED, edge.get(maxSpeedEnc), 1); + assertEquals(MAXSPEED_MISSING, edge.get(maxSpeedEnc), 1); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java index 32e328c3342..4256fc3278f 100644 --- a/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/NameSimilarityEdgeFilterTest.java @@ -20,7 +20,6 @@ import com.graphhopper.routing.ev.DecimalEncodedValue; import com.graphhopper.routing.ev.DecimalEncodedValueImpl; import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; -import com.graphhopper.search.KVStorage; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.NodeAccess; import com.graphhopper.util.EdgeIteratorState; @@ -28,8 +27,10 @@ import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.Test; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import java.util.Map; + +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -105,11 +106,11 @@ public void testDistanceFiltering() { na.setNode(nodeID200, point200mAway.lat, point200mAway.lon); // Check that it matches a street 50m away - EdgeIteratorState edge1 = g.edge(nodeId50, farAwayId).setKeyValues(createKV(STREET_NAME, "Wentworth Street")); + EdgeIteratorState edge1 = g.edge(nodeId50, farAwayId).setKeyValues(Map.of(STREET_NAME, new KValue("Wentworth Street"))); assertTrue(createNameSimilarityEdgeFilter("Wentworth Street").accept(edge1)); // Check that it doesn't match streets 200m away - EdgeIteratorState edge2 = g.edge(nodeID200, farAwayId).setKeyValues(createKV(STREET_NAME, "Wentworth Street")); + EdgeIteratorState edge2 = g.edge(nodeID200, farAwayId).setKeyValues(Map.of(STREET_NAME, new KValue("Wentworth Street"))); assertFalse(createNameSimilarityEdgeFilter("Wentworth Street").accept(edge2)); } @@ -277,16 +278,16 @@ public void curvedWayGeometry_issue2319() { graph.getNodeAccess().setNode(1, 43.842775, -79.264649); EdgeIteratorState doubtfire = graph.edge(0, 1).setWayGeometry(pointList).set(accessEnc, true, true). - set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Doubtfire Crescent")); + set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Doubtfire Crescent"))); EdgeIteratorState golden = graph.edge(0, 1).set(accessEnc, true, true).set(speedEnc, 60, 60). - setKeyValues(createKV(STREET_NAME, "Golden Avenue")); + setKeyValues(Map.of(STREET_NAME, new KValue("Golden Avenue"))); graph.getNodeAccess().setNode(2, 43.841501560244744, -79.26366394602502); graph.getNodeAccess().setNode(3, 43.842247922172724, -79.2605663670726); PointList pointList2 = new PointList(1, false); pointList2.add(43.84191413615452, -79.261912128223); EdgeIteratorState denison = graph.edge(2, 3).setWayGeometry(pointList2).set(accessEnc, true, true). - set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "Denison Street")); + set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("Denison Street"))); double qlat = 43.842122; double qLon = -79.262162; @@ -310,7 +311,7 @@ private EdgeIteratorState createTestEdgeIterator(String name) { EdgeIteratorState edge = new BaseGraph.Builder(1).create().edge(0, 1) .setWayGeometry(pointList); if (name != null) - edge.setKeyValues(KVStorage.KeyValue.createKV(KVStorage.KeyValue.STREET_NAME, name)); + edge.setKeyValues(Map.of(STREET_NAME, new KValue(name))); return edge; } diff --git a/core/src/test/java/com/graphhopper/routing/util/SlopeCalculatorTest.java b/core/src/test/java/com/graphhopper/routing/util/SlopeCalculatorTest.java index 0a66a3b2811..5cd4b3376b8 100644 --- a/core/src/test/java/com/graphhopper/routing/util/SlopeCalculatorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/SlopeCalculatorTest.java @@ -1,12 +1,16 @@ package com.graphhopper.routing.util; -import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.*; -import com.graphhopper.storage.IntsRef; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.FetchMode; import com.graphhopper.util.PointList; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; class SlopeCalculatorTest { @@ -14,83 +18,99 @@ class SlopeCalculatorTest { void simpleElevation() { DecimalEncodedValue averageEnc = AverageSlope.create(); DecimalEncodedValue maxEnc = MaxSlope.create(); - new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); - SlopeCalculator creator = new SlopeCalculator(maxEnc, averageEnc); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); - int edgeId = 0; - ReaderWay way = new ReaderWay(1L); - PointList pointList = new PointList(5, true); - pointList.add(51.0, 12.001, 0); - pointList.add(51.0, 12.002, 3.5); // ~70m - pointList.add(51.0, 12.003, 4); // ~140m - pointList.add(51.0, 12.004, 2); // ~210m - way.setTag("point_list", pointList); - creator.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); - - assertEquals(Math.round(2.0 / 210 * 100), averageEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-3); - assertEquals(-Math.round(2.0 / 210 * 100), averageEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-3); - - assertEquals(Math.round(1.75 / 105 * 100), maxEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-3); - assertEquals(Math.round(1.75 / 105 * 100), maxEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-3); + EncodingManager em = new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 51.0, 12.001, 0); + na.setNode(1, 51.0, 12.004, 2); + PointList pillarNodes = new PointList(3, true); + pillarNodes.add(51.0, 12.002, 3.5); + pillarNodes.add(51.0, 12.003, 4); + EdgeIteratorState edge = graph.edge(0, 1).setWayGeometry(pillarNodes); + edge.setDistance(DistanceCalcEarth.calcDistance(edge.fetchWayGeometry(FetchMode.ALL), false)); + + new SlopeCalculator(maxEnc, averageEnc).execute(graph); + + assertEquals(Math.round(2.0 / 210 * 100), edge.get(averageEnc), 1e-3); + assertEquals(-Math.round(2.0 / 210 * 100), edge.getReverse(averageEnc), 1e-3); + + assertEquals(Math.round(1.75 / 105 * 100), edge.get(maxEnc), 1e-3); + assertEquals(-Math.round(1.75 / 105 * 100), edge.getReverse(maxEnc), 1e-3); } @Test public void testAveragingOfMaxSlope() { - // point=49.977518%2C11.564285&point=49.979878%2C11.563663&profile=bike DecimalEncodedValue averageEnc = AverageSlope.create(); DecimalEncodedValue maxEnc = MaxSlope.create(); - new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); - SlopeCalculator creator = new SlopeCalculator(maxEnc, averageEnc); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(1); - int edgeId = 0; - ReaderWay way = new ReaderWay(1L); - PointList pointList = new PointList(5, true); - pointList.add(51.0, 12.0010, 10); - pointList.add(51.0, 12.0014, 8); // 28m - pointList.add(51.0, 12.0034, 8); // 140m - pointList.add(51.0, 12.0054, 0); // 140m - pointList.add(51.0, 12.0070, 7); // 112m - way.setTag("point_list", pointList); - creator.handleWayTags(edgeId, intAccess, way, IntsRef.EMPTY); - - assertEquals(Math.round(8.0 / 210 * 100), maxEnc.getDecimal(false, edgeId, intAccess), 1e-3); - assertEquals(Math.round(8.0 / 210 * 100), maxEnc.getDecimal(true, edgeId, intAccess), 1e-3); + EncodingManager em = new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 51.0, 12.0010, 10); + na.setNode(1, 51.0, 12.0070, 7); + PointList pillarNodes = new PointList(3, true); + pillarNodes.add(51.0, 12.0014, 8); + pillarNodes.add(51.0, 12.0034, 8); + pillarNodes.add(51.0, 12.0054, 0); + EdgeIteratorState edge = graph.edge(0, 1).setWayGeometry(pillarNodes); + edge.setDistance(DistanceCalcEarth.calcDistance(edge.fetchWayGeometry(FetchMode.ALL), false)); + + new SlopeCalculator(maxEnc, averageEnc).execute(graph); + + assertEquals(-Math.round(8.0 / 210 * 100), edge.get(maxEnc), 1e-3); + assertEquals(Math.round(8.0 / 210 * 100), edge.getReverse(maxEnc), 1e-3); + } + + @Test + public void testMaxSlopeLargerThanMaxStorableDecimal() { + DecimalEncodedValue averageEnc = AverageSlope.create(); + DecimalEncodedValue maxEnc = MaxSlope.create(); + EncodingManager em = new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 47.7281561, 11.9993135, 1163.0); + na.setNode(1, 47.7283135, 11.9991135, 1178.0); + PointList pillarNodes = new PointList(1, true); + pillarNodes.add(47.7282782, 11.9991944, 1163.0); + EdgeIteratorState edge = graph.edge(0, 1).setWayGeometry(pillarNodes); + edge.setDistance(DistanceCalcEarth.calcDistance(edge.fetchWayGeometry(FetchMode.ALL), false)); + + new SlopeCalculator(maxEnc, averageEnc).execute(graph); + + assertEquals(31, edge.get(maxEnc), 1e-3); + assertEquals(-31, edge.getReverse(averageEnc), 1e-3); } @Test - public void test2() { - PointList pointList = new PointList(5, true); - pointList.add(47.7281561, 11.9993135, 1163.0); - pointList.add(47.7282782, 11.9991944, 1163.0); - pointList.add(47.7283135, 11.9991135, 1178.0); - ReaderWay way = new ReaderWay(1); - way.setTag("point_list", pointList); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(1); - int edgeId = 0; + public void testMaxSlopeSmallerThanMinStorableDecimal() { DecimalEncodedValue averageEnc = AverageSlope.create(); DecimalEncodedValue maxEnc = MaxSlope.create(); - new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); - SlopeCalculator creator = new SlopeCalculator(maxEnc, averageEnc); - creator.handleWayTags(edgeId, intAccess, way, IntsRef.EMPTY); - assertEquals(31, maxEnc.getDecimal(false, edgeId, intAccess), 1e-3); - assertEquals(31, averageEnc.getDecimal(false, edgeId, intAccess), 1e-3); + EncodingManager em = new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + BaseGraph graph = new BaseGraph.Builder(em).set3D(true).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 47.7283135, 11.9991135, 1178.0); + na.setNode(1, 47.7281561, 11.9993135, 1163.0); + PointList pillarNodes = new PointList(1, true); + pillarNodes.add(47.7282782, 11.9991944, 1163.0); + EdgeIteratorState edge = graph.edge(0, 1).setWayGeometry(pillarNodes); + edge.setDistance(DistanceCalcEarth.calcDistance(edge.fetchWayGeometry(FetchMode.ALL), false)); + + new SlopeCalculator(maxEnc, averageEnc).execute(graph); + + assertEquals(-31, edge.get(maxEnc), 1e-3); + assertEquals(31, edge.getReverse(averageEnc), 1e-3); } @Test - public void test2D() { - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(1); - int edgeId = 0; - PointList pointList = new PointList(5, false); - pointList.add(47.7283135, 11.9991135); - ReaderWay way = new ReaderWay(1); - way.setTag("point_list", pointList); + public void testThrowErrorFor2D() { DecimalEncodedValue averageEnc = AverageSlope.create(); DecimalEncodedValue maxEnc = MaxSlope.create(); - new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + EncodingManager em = new EncodingManager.Builder().add(averageEnc).add(maxEnc).build(); + BaseGraph graph = new BaseGraph.Builder(em).create(); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 47.7283135, 11.9991135); + na.setNode(1, 47.7281561, 11.9993135); + graph.edge(0, 1).setDistance(100); - SlopeCalculator creator = new SlopeCalculator(maxEnc, averageEnc); - creator.handleWayTags(edgeId, intAccess, way, IntsRef.EMPTY); - assertEquals(0, maxEnc.getDecimal(false, edgeId, intAccess), 1e-3); - assertEquals(0, averageEnc.getDecimal(false, edgeId, intAccess), 1e-3); + assertThrows(IllegalArgumentException.class, () -> new SlopeCalculator(maxEnc, averageEnc).execute(graph)); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java index ae55b75487e..ff71f7c2e66 100644 --- a/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/SnapPreventionEdgeFilterTest.java @@ -17,12 +17,12 @@ public class SnapPreventionEdgeFilterTest { @Test public void accept() { EdgeFilter trueFilter = edgeState -> true; - EncodingManager em = new EncodingManager.Builder().build(); + EncodingManager em = new EncodingManager.Builder().add(RoadClass.create()).add(RoadEnvironment.create()).build(); EnumEncodedValue rcEnc = em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); EnumEncodedValue reEnc = em.getEnumEncodedValue(RoadEnvironment.KEY, RoadEnvironment.class); SnapPreventionEdgeFilter filter = new SnapPreventionEdgeFilter(trueFilter, rcEnc, reEnc, Arrays.asList("motorway", "ferry")); BaseGraph graph = new BaseGraph.Builder(em).create(); - EdgeIteratorState edge = graph.edge(0, 1).setDistance(1); + EdgeIteratorState edge = graph.edge(0, 1).setDistance(100); assertTrue(filter.accept(edge)); edge.set(reEnc, RoadEnvironment.FERRY); @@ -35,4 +35,4 @@ public void accept() { edge.set(rcEnc, RoadClass.MOTORWAY); assertFalse(filter.accept(edge)); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/countryrules/CountryRuleTest.java b/core/src/test/java/com/graphhopper/routing/util/countryrules/CountryRuleTest.java deleted file mode 100644 index 274dcfd5f67..00000000000 --- a/core/src/test/java/com/graphhopper/routing/util/countryrules/CountryRuleTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.util.countryrules; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.RoadAccess; -import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.routing.util.countryrules.europe.AustriaCountryRule; -import com.graphhopper.routing.util.countryrules.europe.GermanyCountryRule; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class CountryRuleTest { - - @Test - void germany() { - GermanyCountryRule rule = new GermanyCountryRule(); - assertEquals(RoadAccess.DESTINATION, rule.getAccess(createReaderWay("track"), TransportationMode.CAR, RoadAccess.YES)); - assertEquals(RoadAccess.YES, rule.getAccess(createReaderWay("primary"), TransportationMode.CAR, RoadAccess.YES)); - } - - @Test - void austria() { - AustriaCountryRule rule = new AustriaCountryRule(); - assertEquals(RoadAccess.FORESTRY, rule.getAccess(createReaderWay("track"), TransportationMode.CAR, RoadAccess.YES)); - assertEquals(RoadAccess.YES, rule.getAccess(createReaderWay("primary"), TransportationMode.CAR, RoadAccess.YES)); - assertEquals(RoadAccess.DESTINATION, rule.getAccess(createReaderWay("living_street"), TransportationMode.CAR, RoadAccess.YES)); - } - - private ReaderWay createReaderWay(String highway) { - ReaderWay readerWay = new ReaderWay(123L); - readerWay.setTag("highway", highway); - return readerWay; - } - -} \ No newline at end of file diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java index f412ff7244d..e0e6431ada4 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/AbstractBikeTagParserTester.java @@ -24,16 +24,11 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.OSMParsers; import com.graphhopper.routing.util.PriorityCode; -import com.graphhopper.routing.util.VehicleTagParsers; import com.graphhopper.storage.IntsRef; -import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.text.DateFormat; -import java.util.Date; - import static com.graphhopper.routing.util.PriorityCode.*; import static org.junit.jupiter.api.Assertions.*; @@ -54,13 +49,12 @@ public abstract class AbstractBikeTagParserTester { @BeforeEach public void setUp() { encodingManager = createEncodingManager(); - VehicleTagParsers parsers = createBikeTagParsers(encodingManager, new PMap("block_fords=true")); - accessParser = (BikeCommonAccessParser) parsers.getAccessParser(); - speedParser = (BikeCommonAverageSpeedParser) parsers.getSpeedParser(); - priorityParser = (BikeCommonPriorityParser) parsers.getPriorityParser(); + accessParser = createAccessParser(encodingManager, new PMap()); + speedParser = createAverageSpeedParser(encodingManager); + priorityParser = createPriorityParser(encodingManager); osmParsers = new OSMParsers() - .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig)) - .addRelationTagParser(relConfig -> new OSMMtbNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig)) + .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig, "bicycle")) + .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(encodingManager.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig, "mtb")) .addWayTagParser(new OSMSmoothnessParser(encodingManager.getEnumEncodedValue(Smoothness.KEY, Smoothness.class))) .addWayTagParser(accessParser).addWayTagParser(speedParser).addWayTagParser(priorityParser); priorityEnc = priorityParser.getPriorityEnc(); @@ -70,11 +64,15 @@ public void setUp() { protected abstract EncodingManager createEncodingManager(); - protected abstract VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap); + protected abstract BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap); + + protected abstract BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup); + + protected abstract BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup); protected void assertPriority(PriorityCode expectedPrio, ReaderWay way) { IntsRef relFlags = osmParsers.handleRelationTags(new ReaderRelation(0), osmParsers.createRelationFlags()); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 0.01); @@ -86,7 +84,7 @@ protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expected protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expectedSpeed, ReaderWay way, ReaderRelation rel) { IntsRef relFlags = osmParsers.handleRelationTags(rel, osmParsers.createRelationFlags()); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 0.01); @@ -96,7 +94,7 @@ protected void assertPriorityAndSpeed(PriorityCode expectedPrio, double expected protected double getSpeedFromFlags(ReaderWay way) { IntsRef relFlags = osmParsers.createRelationFlags(); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); return avgSpeedEnc.getDecimal(false, edgeId, intAccess); @@ -138,6 +136,20 @@ public void testAccess() { way.clearTags(); way.setTag("highway", "path"); assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("vehicle", "no"); + assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "path"); + assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("access", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + way.setTag("bicycle", "yes"); + assertTrue(accessParser.getAccess(way).isWay()); way.setTag("highway", "path"); way.setTag("bicycle", "yes"); @@ -173,13 +185,6 @@ public void testAccess() { way.setTag("motorroad", "yes"); assertTrue(accessParser.getAccess(way).canSkip()); - way.clearTags(); - way.setTag("highway", "track"); - way.setTag("ford", "yes"); - assertTrue(accessParser.getAccess(way).canSkip()); - way.setTag("bicycle", "yes"); - assertTrue(accessParser.getAccess(way).isWay()); - way.clearTags(); way.setTag("highway", "secondary"); way.setTag("access", "no"); @@ -190,9 +195,11 @@ public void testAccess() { way.clearTags(); way.setTag("highway", "secondary"); way.setTag("vehicle", "no"); - assertTrue(accessParser.getAccess(way).canSkip()); + assertTrue(accessParser.getAccess(way).isWay()); way.setTag("bicycle", "dismount"); assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); way.clearTags(); @@ -215,26 +222,10 @@ public void testAccess() { way.setTag("bicycle", "no"); assertTrue(accessParser.getAccess(way).canSkip()); - DateFormat simpleDateFormat = Helper.createFormatter("yyyy MMM dd"); - - way.clearTags(); - way.setTag("highway", "road"); - way.setTag("bicycle:conditional", "no @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); - assertTrue(accessParser.getAccess(way).canSkip()); - - way.setTag("bicycle", "yes"); // the conditional tag even overrules "yes" - assertTrue(accessParser.getAccess(way).canSkip()); - - way.clearTags(); - way.setTag("highway", "road"); - way.setTag("access", "no"); - way.setTag("bicycle:conditional", "yes @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); - assertTrue(accessParser.getAccess(way).isWay()); - way.clearTags(); way.setTag("highway", "track"); way.setTag("vehicle", "forestry"); - assertTrue(accessParser.getAccess(way).canSkip()); + assertTrue(accessParser.getAccess(way).isWay()); way.setTag("bicycle", "yes"); assertTrue(accessParser.getAccess(way).isWay()); } @@ -263,7 +254,7 @@ public void testRelation() { // two relation tags => we currently cannot store a list, so pick the lower ordinal 'regional' // Example https://www.openstreetmap.org/way/213492914 => two hike 84544, 2768803 and two bike relations 3162932, 5254650 IntsRef relFlags = osmParsers.handleRelationTags(rel2, osmParsers.handleRelationTags(rel, osmParsers.createRelationFlags())); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; osmParsers.handleWayTags(edgeId, intAccess, way, relFlags); EnumEncodedValue enc = encodingManager.getEnumEncodedValue(RouteNetwork.key("bike"), RouteNetwork.class); @@ -293,7 +284,7 @@ public void testTramStations() { way = new ReaderWay(1); way.setTag("railway", "platform"); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, intAccess, way, null); speedParser.handleWayTags(edgeId, intAccess, way, null); @@ -314,7 +305,7 @@ public void testTramStations() { way.setTag("railway", "platform"); way.setTag("bicycle", "no"); - intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, intAccess, way); assertEquals(0.0, avgSpeedEnc.getDecimal(false, edgeId, intAccess)); assertFalse(accessEnc.getBool(false, edgeId, intAccess)); @@ -357,7 +348,14 @@ public void testService() { assertPriorityAndSpeed(PREFER, 12, way); way.setTag("service", "parking_aisle"); - assertPriorityAndSpeed(SLIGHT_AVOID, 4, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 8, way); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(VERY_NICE, 12, way); + + way.clearTags(); + way.setTag("highway", "residential"); + way.setTag("service", "alley"); + assertPriorityAndSpeed(PREFER, 18, way); } @Test @@ -370,18 +368,6 @@ public void testSteps() { assertPriorityAndSpeed(BAD, 2, way); } - @Test - public void testSacScale() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "service"); - way.setTag("sac_scale", "hiking"); - // allow - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("sac_scale", "alpine_hiking"); - assertTrue(accessParser.getAccess(way).canSkip()); - } - @Test public void testReduceToMaxSpeed() { ReaderWay way = new ReaderWay(12); @@ -389,19 +375,12 @@ public void testReduceToMaxSpeed() { assertEquals(12, speedParser.applyMaxSpeed(way, 12, true), 1e-2); } - @Test - public void testPreferenceForSlowSpeed() { - ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "tertiary"); - assertPriority(PREFER, osmWay); - } - @Test public void testHandleWayTagsCallsHandlePriority() { ReaderWay osmWay = new ReaderWay(1); osmWay.setTag("highway", "cycleway"); - ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; priorityParser.handleWayTags(edgeId, intAccess, osmWay, null); assertEquals(PriorityCode.getValue(VERY_NICE.getValue()), priorityEnc.getDecimal(false, edgeId, intAccess), 1e-3); @@ -413,6 +392,8 @@ public void testLockedGate() { node.setTag("barrier", "gate"); node.setTag("locked", "yes"); assertTrue(accessParser.isBarrier(node)); + node.setTag("bicycle", "yes"); + assertFalse(accessParser.isBarrier(node)); } @Test @@ -462,18 +443,6 @@ public void testBarrierAccess() { assertFalse(accessParser.isBarrier(node)); } - @Test - public void testBarrierAccessFord() { - ReaderNode node = new ReaderNode(1, -1, -1); - node.setTag("ford", "yes"); - // barrier! - assertTrue(accessParser.isBarrier(node)); - - node.setTag("bicycle", "yes"); - // no barrier! - assertFalse(accessParser.isBarrier(node)); - } - @Test public void testFerries() { ReaderWay way = new ReaderWay(1); @@ -526,21 +495,19 @@ public void testFerries() { } @Test - void privateAndFords() { - // defaults: do not block fords, block private - BikeCommonAccessParser bike = (BikeCommonAccessParser) createBikeTagParsers(encodingManager, new PMap()).getAccessParser(); - assertFalse(bike.isBlockFords()); + void testPrivate() { + // defaults: block private + BikeCommonAccessParser bike = createAccessParser(encodingManager, new PMap()); assertTrue(bike.restrictedValues.contains("private")); - assertFalse(bike.intendedValues.contains("private")); + assertFalse(bike.allowedValues.contains("private")); ReaderNode node = new ReaderNode(1, 1, 1); node.setTag("access", "private"); assertTrue(bike.isBarrier(node)); - // block fords, unblock private - bike = (BikeCommonAccessParser) createBikeTagParsers(encodingManager, new PMap("block_fords=true|block_private=false")).getAccessParser(); - assertTrue(bike.isBlockFords()); + // unblock private + bike = createAccessParser(encodingManager, new PMap("block_private=false")); assertFalse(bike.restrictedValues.contains("private")); - assertTrue(bike.intendedValues.contains("private")); + assertTrue(bike.allowedValues.contains("private")); assertFalse(bike.isBarrier(node)); } @@ -662,6 +629,44 @@ public void testOneway() { assertAccess(way, true, false); } + @Test + public void testOnewayIssue3162() { + ReaderWay way = new ReaderWay(1); + way.clearTags(); + way.setTag("highway", "secondary"); + // default for left hand traffic, see https://wiki.openstreetmap.org/wiki/Key:cycleway:right:oneway + way.setTag("cycleway:left:oneway" , "yes"); + way.setTag("cycleway:right:oneway", "-1"); + assertAccess(way, true, true); + // default for right hand traffic, see https://wiki.openstreetmap.org/wiki/Key:cycleway:right:oneway + way.setTag("cycleway:left:oneway","-1"); + way.setTag("cycleway:right:oneway", "yes"); + assertAccess(way, true, true); + // other cases identified in #3162: + way.setTag("cycleway:left:oneway","1"); + way.setTag("cycleway:right:oneway","-1"); + assertAccess(way, true, true); + way.setTag("cycleway:left:oneway","no"); + way.setTag("cycleway:right:oneway", "yes"); + assertAccess(way, true, true); + way.setTag("cycleway:left:oneway","no"); + way.setTag("cycleway:right:oneway" ,"1"); + assertAccess(way, true, true); + way.setTag("cycleway:left:oneway","no"); + way.setTag("cycleway:right:oneway" ,"-1"); + assertAccess(way, true, true); + way.setTag("cycleway:left:oneway","yes"); + way.setTag("cycleway:right:oneway","no"); + assertAccess(way, true, true); + way.setTag("cycleway:left:oneway","-1"); + way.setTag("cycleway:right:oneway","no"); + assertAccess(way, true, true); + // most likely a tagging error, allowing both directions: + way.setTag("cycleway:left:oneway","-1"); + way.setTag("cycleway:right:oneway","-1"); + assertAccess(way, true, true); + } + private void assertAccess(ReaderWay way, boolean fwd, boolean bwd) { EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); int edge = 0; diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java new file mode 100644 index 00000000000..7887c18630f --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java @@ -0,0 +1,396 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderRelation; +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.util.OSMParsers; +import com.graphhopper.routing.util.PriorityCode; +import com.graphhopper.routing.util.TransportationMode; +import com.graphhopper.routing.weighting.custom.CustomModelParser; +import com.graphhopper.routing.weighting.custom.CustomWeighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.IntsRef; +import com.graphhopper.util.CustomModel; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.PMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BikeCustomModelTest { + + private EncodingManager em; + private OSMParsers parsers; + + @BeforeEach + public void setup() { + IntEncodedValue bikeRating = MtbRating.create(); + IntEncodedValue hikeRating = HikeRating.create(); + EnumEncodedValue bikeRA = BikeRoadAccess.create(); + em = new EncodingManager.Builder(). + add(VehicleAccess.create("bike")). + add(VehicleSpeed.create("bike", 4, 2, false)). + add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false)). + add(VehicleAccess.create("mtb")). + add(VehicleSpeed.create("mtb", 4, 2, false)). + add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false)). + add(VehicleAccess.create("racingbike")). + add(VehicleSpeed.create("racingbike", 4, 2, false)). + add(VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false)). + add(FerrySpeed.create()). + add(Country.create()). + add(RoadClass.create()). + add(RoadEnvironment.create()). + add(RouteNetwork.create(BikeNetwork.KEY)). + add(RouteNetwork.create(MtbNetwork.KEY)). + add(Roundabout.create()). + add(Smoothness.create()). + add(RoadAccess.create()). + add(bikeRA). + add(FootRoadAccess.create()). + add(bikeRating). + add(hikeRating).build(); + + parsers = new OSMParsers(). + addWayTagParser(new OSMMtbRatingParser(bikeRating)). + addWayTagParser(new OSMHikeRatingParser(hikeRating)). + addWayTagParser(new BikeAccessParser(em, new PMap())). + addWayTagParser(new MountainBikeAccessParser(em, new PMap())). + addWayTagParser(new RacingBikeAccessParser(em, new PMap())). + addWayTagParser(new BikeAverageSpeedParser(em)). + addWayTagParser(new MountainBikeAverageSpeedParser(em)). + addWayTagParser(new RacingBikeAverageSpeedParser(em)). + addWayTagParser(new BikePriorityParser(em)). + addWayTagParser(new MountainBikePriorityParser(em)). + addWayTagParser(new RacingBikePriorityParser(em)). + addWayTagParser(OSMRoadAccessParser.forBike(bikeRA)); + + parsers.addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(em.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class), relConfig, "bicycle")). + addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(em.getEnumEncodedValue(MtbNetwork.KEY, RouteNetwork.class), relConfig, "mtb")); + } + + EdgeIteratorState createEdge(ReaderWay way, ReaderRelation... readerRelation) { + BaseGraph graph = new BaseGraph.Builder(em).create(); + EdgeIteratorState edge = graph.edge(0, 1); + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); + IntsRef rel = em.createRelationFlags(); + if (readerRelation.length == 1) + parsers.handleRelationTags(readerRelation[0], rel); + parsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, rel); + return edge; + } + + @Test + public void testCustomBike() { + CustomModel baseCM = GHUtility.loadCustomModelFromJar("bike.json"); + CustomModel bikeAvoidPrivate = GHUtility.loadCustomModelFromJar("bike_avoid_private_node.json"); + CustomModel cm = CustomModel.merge(baseCM, bikeAvoidPrivate); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "path"); + way.setTag("surface", "ground"); + EdgeIteratorState edge = createEdge(way); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "0"); + edge = createEdge(way); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "1+"); + edge = createEdge(way); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "2-"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.removeTag("mtb:scale"); + way.setTag("sac_scale", "hiking"); + edge = createEdge(way); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + // other scales than hiking are nearly impossible by an ordinary bike, see http://wiki.openstreetmap.org/wiki/Key:sac_scale + way.setTag("sac_scale", "mountain_hiking"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(8.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.clearTags(); + way.setTag("highway", "tertiary"); + way.setTag("vehicle", "private"); + edge = createEdge(way); + assertEquals(0.1, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } + + @Test + public void testCountryAccessDefault() { + CustomModel cm = GHUtility.loadCustomModelFromJar("bike.json"); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "bridleway"); + EdgeIteratorState edge = createEdge(way); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0.8, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("country", Country.DEU); + edge = createEdge(way); + p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("bicycle", "yes"); + edge = createEdge(way); + p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0.8, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way = new ReaderWay(0L); + way.setTag("highway", "trunk_link"); + way.setTag("country", Country.CHE); + edge = createEdge(way); + p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } + + @Test + public void testCustomMtbBike() { + CustomModel cm = GHUtility.loadCustomModelFromJar("mtb.json"); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "path"); + way.setTag("surface", "ground"); // bad surface means slow speed for mtb too + EdgeIteratorState edge = createEdge(way); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(10.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "3"); + edge = createEdge(way); + assertEquals(0.6, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "5"); + edge = createEdge(way); + assertEquals(0.6, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(4.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "6"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.removeTag("mtb:scale"); + way.setTag("sac_scale", "hiking"); + edge = createEdge(way); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("sac_scale", "mountain_hiking"); + edge = createEdge(way); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("sac_scale", "alpine_hiking"); + edge = createEdge(way); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("sac_scale", "demanding_alpine_hiking"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } + + @Test + public void testCustomRacingBike() { + CustomModel cm = GHUtility.loadCustomModelFromJar("racingbike.json"); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "path"); + EdgeIteratorState edge = createEdge(way); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(6.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "0"); + edge = createEdge(way); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "1"); + edge = createEdge(way); + assertEquals(0.45, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("mtb:scale", "2"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.removeTag("mtb:scale"); + way.setTag("sac_scale", "hiking"); + edge = createEdge(way); + assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + way.setTag("sac_scale", "mountain_hiking"); + edge = createEdge(way); + assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } + + @Test + public void testCalcPriority() { + CustomModel cm = GHUtility.loadCustomModelFromJar("bike.json"); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "tertiary"); + + EdgeIteratorState edge = createEdge(way); + assertEquals(1, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + ReaderRelation osmRel = new ReaderRelation(1); + osmRel.setTag("route", "bicycle"); + osmRel.setTag("network", "icn"); + + edge = createEdge(way, osmRel); + assertEquals(1.4, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + // unknown highway tags will be excluded + way = new ReaderWay(1); + way.setTag("highway", "whatever"); + edge = createEdge(way, osmRel); + assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } + + @Test + public void testHandleWayTagsInfluencedByRelation() { + CustomModel cm = GHUtility.loadCustomModelFromJar("bike.json"); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "road"); + + EdgeIteratorState edge = createEdge(way); + assertEquals(1, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + // "lcn=yes" is in fact no relation, but shall be treated the same like a relation with "network=lcn" + way.setTag("lcn", "yes"); + edge = createEdge(way); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // relation code is VERY_NICE + ReaderRelation rel = new ReaderRelation(1); + rel.setTag("route", "bicycle"); + way = new ReaderWay(1); + way.setTag("highway", "road"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // relation code is NICE + rel.setTag("network", "rcn"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // no "double boosting" due because way lcn=yes is only considered if no route relation + way.setTag("lcn", "yes"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // relation code is BEST + rel.setTag("network", "ncn"); + edge = createEdge(way, rel); + assertEquals(1.4, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // PREFER relation, but tertiary road => no get off the bike but road wayTypeCode and faster + way.clearTags(); + way.setTag("highway", "tertiary"); + rel.setTag("route", "bicycle"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.clearTags(); + way.clearTags(); + way.setTag("highway", "track"); + edge = createEdge(way, rel); + assertEquals(1, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("route", "bicycle"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + } + + @Test + public void testHandleWayTagsInfluencedByBikeAndMtbRelation() { + CustomModel cm = GHUtility.loadCustomModelFromJar("mtb.json"); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "track"); + + ReaderRelation rel = new ReaderRelation(1); + EdgeIteratorState edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // relation code is PREFER + rel.setTag("route", "bicycle"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("network", "rcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("network", "ncn"); + edge = createEdge(way, rel); + assertEquals(1.8, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + // no pushing section but road wayTypeCode and faster + way.clearTags(); + way.setTag("highway", "tertiary"); + rel.setTag("route", "bicycle"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.clearTags(); + rel.clearTags(); + way.setTag("highway", "track"); + edge = createEdge(way, rel); + assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("route", "mtb"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("network", "rcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + rel.setTag("network", "ncn"); + edge = createEdge(way, rel); + assertEquals(1.8, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(12, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.clearTags(); + way.setTag("highway", "tertiary"); + + rel.setTag("route", "mtb"); + rel.setTag("network", "lcn"); + edge = createEdge(way, rel); + assertEquals(1.56, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(18, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + } + +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java index ab2baba8ca2..7cdda742482 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeTagParserTest.java @@ -23,9 +23,7 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.PriorityCode; -import com.graphhopper.routing.util.VehicleEncodedValues; -import com.graphhopper.routing.util.VehicleTagParsers; -import com.graphhopper.storage.IntsRef; +import com.graphhopper.routing.util.WayAccess; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; @@ -42,12 +40,31 @@ public class BikeTagParserTest extends AbstractBikeTagParserTester { @Override protected EncodingManager createEncodingManager() { - return new EncodingManager.Builder().add(VehicleEncodedValues.bike(new PMap())).build(); + return new EncodingManager.Builder() + .add(VehicleAccess.create("bike")) + .add(VehicleSpeed.create("bike", 4, 2, false)) + .add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false)) + .add(Roundabout.create()) + .add(Smoothness.create()) + .add(FerrySpeed.create()) + .add(RouteNetwork.create(BikeNetwork.KEY)) + .add(RouteNetwork.create(MtbNetwork.KEY)) + .build(); } @Override - protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) { - return VehicleTagParsers.bike(lookup, pMap); + protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) { + return new BikeAccessParser(lookup, pMap); + } + + @Override + protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) { + return new BikeAverageSpeedParser(lookup); + } + + @Override + protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) { + return new BikePriorityParser(lookup); } @Test @@ -56,33 +73,93 @@ public void testSpeedAndPriority() { way.setTag("highway", "primary"); assertPriorityAndSpeed(BAD, 18, way); + // ignore scenic as it is a too generic indication and not for bike and can therefor lead to wrong suggestions way.setTag("scenic", "yes"); - assertPriorityAndSpeed(AVOID_MORE, 18, way); + assertPriorityAndSpeed(BAD, 18, way); + + way.clearTags(); + way.setTag("highway", "living_street"); + assertPriorityAndSpeed(UNCHANGED, 6, way); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(UNCHANGED, 10, way); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(PREFER, 12, way); // Pushing section: this is fine as we obey the law! way.clearTags(); way.setTag("highway", "footway"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); // Use pushing section irrespective of the pavement way.setTag("surface", "paved"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); way.clearTags(); - way.setTag("highway", "path"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + way.setTag("highway", "residential"); + way.setTag("bicycle", "use_sidepath"); + assertPriorityAndSpeed(REACH_DESTINATION, 18, way); + + way.setTag("bicycle", "optional_sidepath"); + assertPriorityAndSpeed(AVOID, 18.0, way); way.clearTags(); way.setTag("highway", "secondary"); way.setTag("bicycle", "dismount"); assertPriorityAndSpeed(AVOID, PUSHING_SECTION_SPEED, way); + way.clearTags(); + way.setTag("highway", "primary"); + way.setTag("surface", "fine_gravel"); + assertPriorityAndSpeed(BAD, 14, way); + + way.clearTags(); + way.setTag("highway", "primary"); + way.setTag("surface", "paved"); + assertPriorityAndSpeed(BAD, 18, way); + + way.clearTags(); + way.setTag("highway", "primary"); + assertPriorityAndSpeed(BAD, 18, way); + + way.clearTags(); + way.setTag("highway", "residential"); + way.setTag("surface", "asphalt"); + assertPriorityAndSpeed(PREFER, 18, way); + + way.clearTags(); + way.setTag("highway", "motorway"); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(REACH_DESTINATION, 18, way); + + way.clearTags(); + way.setTag("highway", "trunk"); + assertPriorityAndSpeed(REACH_DESTINATION, 18, way); + + way.clearTags(); + way.setTag("highway", "platform"); + way.setTag("surface", "paved"); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); + + way.clearTags(); + way.setTag("highway", "platform"); + way.setTag("surface", "paved"); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(PREFER, 12, way); + way.setTag("segregated", "yes"); + assertPriorityAndSpeed(PREFER, 18, way); + + way.clearTags(); + way.setTag("highway", "platform"); + way.setTag("surface", "paved"); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.clearTags(); way.setTag("highway", "footway"); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 10, way); + assertPriorityAndSpeed(PREFER, 12, way); way.setTag("segregated", "no"); - assertPriorityAndSpeed(PREFER, 10, way); + assertPriorityAndSpeed(PREFER, 12, way); way.setTag("segregated", "yes"); assertPriorityAndSpeed(PREFER, 18, way); @@ -90,7 +167,7 @@ public void testSpeedAndPriority() { way.setTag("highway", "footway"); way.setTag("surface", "paved"); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 10, way); + assertPriorityAndSpeed(PREFER, 12, way); way.setTag("surface", "cobblestone"); assertPriorityAndSpeed(PREFER, 8, way); way.setTag("segregated", "yes"); @@ -98,46 +175,68 @@ public void testSpeedAndPriority() { assertPriorityAndSpeed(PREFER, 18, way); way.clearTags(); - way.setTag("highway", "platform"); + way.setTag("highway", "footway"); way.setTag("surface", "paved"); - way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 10, way); - way.setTag("segregated", "yes"); - assertPriorityAndSpeed(PREFER, 18, way); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.clearTags(); + + way.setTag("highway", "footway"); + way.setTag("tracktype", "grade4"); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(VERY_NICE, 10, way); way.clearTags(); - way.setTag("highway", "cycleway"); - assertPriorityAndSpeed(VERY_NICE, 18, way); - int cyclewaySpeed = 18; - way.setTag("foot", "yes"); - way.setTag("segregated", "yes"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - way.setTag("segregated", "no"); - assertPriorityAndSpeed(PREFER, cyclewaySpeed, way); + way.setTag("highway", "steps"); + assertPriorityAndSpeed(BAD, 2, way); + + way.clearTags(); + way.setTag("highway", "steps"); + way.setTag("surface", "wood"); + assertPriorityAndSpeed(BAD, MIN_SPEED, way); + way.setTag("maxspeed", "20"); + assertPriorityAndSpeed(BAD, MIN_SPEED, way); + + way.clearTags(); + way.setTag("highway", "bridleway"); + assertPriorityAndSpeed(AVOID, 6, way); + way.setTag("surface", "gravel"); + assertPriorityAndSpeed(AVOID, 8, way); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(PREFER, 12, way); + } + + @Test + public void testPathAndCycleway() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "path"); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); // Make sure that "highway=cycleway" and "highway=path" with "bicycle=designated" give the same result way.clearTags(); way.setTag("highway", "path"); way.setTag("bicycle", "designated"); // Assume foot=no for designated in absence of a foot tag - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - way.setTag("foot", "yes"); - assertPriorityAndSpeed(PREFER, cyclewaySpeed, way); - + assertPriorityAndSpeed(VERY_NICE, 18, way); way.setTag("foot", "no"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.setTag("foot", "yes"); + assertPriorityAndSpeed(PREFER, 18, way); way.setTag("segregated", "yes"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - + assertPriorityAndSpeed(VERY_NICE, 18, way); way.setTag("segregated", "no"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); + assertPriorityAndSpeed(PREFER, 18, way); + way.clearTags(); + way.setTag("highway", "path"); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 10, way); + way.setTag("foot", "yes"); + way.setTag("segregated", "no"); + assertPriorityAndSpeed(SLIGHT_PREFER, 12, way); way.setTag("segregated", "yes"); - assertPriorityAndSpeed(PREFER, cyclewaySpeed, way); + assertPriorityAndSpeed(PREFER, 18, way); way.setTag("surface", "unpaved"); assertPriorityAndSpeed(PREFER, 12, way); @@ -147,120 +246,109 @@ public void testSpeedAndPriority() { way.clearTags(); way.setTag("highway", "path"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + way.setTag("surface", "paved"); + assertPriorityAndSpeed(SLIGHT_AVOID, 12, way); - // use pushing section way.clearTags(); way.setTag("highway", "path"); - way.setTag("surface", "paved"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + way.setTag("tracktype", "grade1"); + assertPriorityAndSpeed(SLIGHT_AVOID, 12, way); way.clearTags(); way.setTag("highway", "path"); way.setTag("surface", "ground"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 8, way); way.clearTags(); - way.setTag("highway", "platform"); - way.setTag("surface", "paved"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + way.setTag("highway", "path"); + way.setTag("bicycle", "designated"); + way.setTag("tracktype", "grade4"); + assertPriorityAndSpeed(VERY_NICE, 10, way); way.clearTags(); - way.setTag("highway", "footway"); - way.setTag("surface", "paved"); - way.setTag("bicycle", "designated"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); + way.setTag("highway", "cycleway"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.setTag("foot", "yes"); + assertPriorityAndSpeed(PREFER, 18, way); + way.setTag("segregated", "yes"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.setTag("segregated", "no"); + assertPriorityAndSpeed(PREFER, 18, way); way.clearTags(); - way.setTag("highway", "platform"); - way.setTag("surface", "paved"); + way.setTag("highway", "cycleway"); + way.setTag("vehicle", "no"); + assertPriorityAndSpeed(VERY_NICE, PUSHING_SECTION_SPEED, way); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + } + + @Test + public void testTrack() { + ReaderWay way = new ReaderWay(1); + way.clearTags(); + way.setTag("highway", "track"); way.setTag("bicycle", "designated"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); + // lower speed might be better as no surface tag, but strange tagging anyway and rare in real world + assertPriorityAndSpeed(PREFER, 18, way); + way.setTag("segregated", "no"); + assertPriorityAndSpeed(PREFER, 18, way); + way.setTag("surface", "asphalt"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.setTag("tracktype", "grade1"); + assertPriorityAndSpeed(VERY_NICE, 18, way); + way.removeTag("surface"); + assertPriorityAndSpeed(VERY_NICE, 18, way); way.clearTags(); way.setTag("highway", "track"); assertPriorityAndSpeed(UNCHANGED, 12, way); + way.setTag("vehicle", "no"); + assertPriorityAndSpeed(UNCHANGED, PUSHING_SECTION_SPEED, way); + way.setTag("vehicle", "forestry;agricultural"); + assertPriorityAndSpeed(UNCHANGED, PUSHING_SECTION_SPEED, way); + + way.clearTags(); + way.setTag("highway", "track"); + way.setTag("surface", "concrete"); + way.setTag("vehicle", "agricultural"); + assertPriorityAndSpeed(UNCHANGED, PUSHING_SECTION_SPEED, way); + way.clearTags(); + way.setTag("highway", "track"); way.setTag("tracktype", "grade1"); assertPriorityAndSpeed(UNCHANGED, 18, way); way.setTag("highway", "track"); way.setTag("tracktype", "grade2"); - assertPriorityAndSpeed(UNCHANGED, 12, way); + assertPriorityAndSpeed(UNCHANGED, 14, way); // test speed for allowed get off the bike types way.setTag("highway", "track"); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(UNCHANGED, 12, way); - - way.clearTags(); - way.setTag("highway", "steps"); - assertPriorityAndSpeed(BAD, 2, way); - - way.clearTags(); - way.setTag("highway", "residential"); - way.setTag("bicycle", "use_sidepath"); - assertPriorityAndSpeed(REACH_DESTINATION, 18, way); - - way.clearTags(); - way.setTag("highway", "steps"); - way.setTag("surface", "wood"); - assertPriorityAndSpeed(BAD, MIN_SPEED, way); - way.setTag("maxspeed", "20"); - assertPriorityAndSpeed(BAD, MIN_SPEED, way); + assertPriorityAndSpeed(UNCHANGED, 14, way); way.clearTags(); way.setTag("highway", "track"); + assertPriorityAndSpeed(UNCHANGED, 12, way); + way.setTag("surface", "paved"); assertPriorityAndSpeed(UNCHANGED, 18, way); - way.clearTags(); - way.setTag("highway", "path"); - way.setTag("surface", "ground"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); - way.clearTags(); way.setTag("highway", "track"); way.setTag("bicycle", "yes"); way.setTag("surface", "fine_gravel"); - assertPriorityAndSpeed(UNCHANGED, 18, way); + assertPriorityAndSpeed(UNCHANGED, 14, way); way.setTag("surface", "unknown_surface"); assertPriorityAndSpeed(UNCHANGED, PUSHING_SECTION_SPEED, way); - way.clearTags(); - way.setTag("highway", "primary"); - way.setTag("surface", "fine_gravel"); - assertPriorityAndSpeed(BAD, 18, way); - way.clearTags(); way.setTag("highway", "track"); way.setTag("surface", "gravel"); way.setTag("tracktype", "grade2"); assertPriorityAndSpeed(UNCHANGED, 12, way); - - way.clearTags(); - way.setTag("highway", "primary"); - way.setTag("surface", "paved"); - assertPriorityAndSpeed(BAD, 18, way); - - way.clearTags(); - way.setTag("highway", "primary"); - assertPriorityAndSpeed(BAD, 18, way); - - way.clearTags(); - way.setTag("highway", "residential"); - way.setTag("surface", "asphalt"); - assertPriorityAndSpeed(PREFER, 18, way); - - way.clearTags(); - way.setTag("highway", "motorway"); - way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(REACH_DESTINATION, 18, way); - - way.clearTags(); - way.setTag("highway", "trunk"); - assertPriorityAndSpeed(REACH_DESTINATION, 18, way); } @Test @@ -282,23 +370,33 @@ public void testSmoothness() { way.clearTags(); way.setTag("highway", "residential"); way.setTag("surface", "ground"); - assertEquals(12, getSpeedFromFlags(way), 0.01); + assertEquals(10, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "bad"); - assertEquals(8, getSpeedFromFlags(way), 0.01); + assertEquals(10, getSpeedFromFlags(way), 0.01); way.clearTags(); way.setTag("highway", "track"); - way.setTag("tracktype", "grade5"); - assertEquals(4, getSpeedFromFlags(way), 0.01); + way.setTag("tracktype", "grade4"); + assertEquals(10, getSpeedFromFlags(way), 0.01); + // pick smallest of highway, tracktype, and applied smoothness speed + way.setTag("smoothness", "intermediate"); + assertEquals(10, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "bad"); - assertEquals(2, getSpeedFromFlags(way), 0.01); - + assertEquals(8, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "impassable"); assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); } + @Test + public void testLowMaxSpeedIsIgnored() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "residential"); + way.setTag("maxspeed", "3"); + assertEquals(18, getSpeedFromFlags(way), 0.01); + } + @Test public void testCycleway() { ReaderWay way = new ReaderWay(1); @@ -317,6 +415,8 @@ public void testCycleway() { way.setTag("highway", "primary"); way.setTag("cycleway:right", "lane"); assertPriority(SLIGHT_PREFER, way); + way.setTag("cycleway:left", "no"); + assertPriority(SLIGHT_PREFER, way); way.clearTags(); way.setTag("highway", "primary"); @@ -364,13 +464,29 @@ public void testWayAcceptance() { way.setTag("vehicle", "no"); assertTrue(accessParser.getAccess(way).isWay()); - // Sensless tagging: JOSM does create a warning here. We follow the highway tag: + // Senseless tagging: JOSM does create a warning here: way.setTag("bicycle", "no"); - assertTrue(accessParser.getAccess(way).isWay()); + assertTrue(accessParser.getAccess(way).canSkip()); way.setTag("bicycle", "designated"); assertTrue(accessParser.getAccess(way).isWay()); + way.clearTags(); + way.setTag("highway", "cycleway"); + way.setTag("access", "no"); + // Tagging mistake and bikes should have access to their cycleway + // And if it would be a constructions: should be mapped as highway=construction + assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "cycleway"); + way.setTag("access", "agricultural"); + assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + way.clearTags(); way.setTag("highway", "motorway"); assertTrue(accessParser.getAccess(way).canSkip()); @@ -384,56 +500,25 @@ public void testWayAcceptance() { assertTrue(accessParser.getAccess(way).isWay()); way.clearTags(); + // exclude bridleway for a single country via custom model way.setTag("highway", "bridleway"); - assertTrue(accessParser.getAccess(way).canSkip()); + assertTrue(accessParser.getAccess(way).isWay()); way.setTag("bicycle", "yes"); assertTrue(accessParser.getAccess(way).isWay()); way.clearTags(); way.setTag("highway", "track"); way.setTag("vehicle", "forestry"); - assertTrue(accessParser.getAccess(way).canSkip()); + assertTrue(accessParser.getAccess(way).isWay()); way.setTag("vehicle", "agricultural;forestry"); - assertTrue(accessParser.getAccess(way).canSkip()); + assertTrue(accessParser.getAccess(way).isWay()); } @Test - public void testHandleWayTagsInfluencedByRelation() { + public void testPreferenceForSlowSpeed() { ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "road"); - - // unchanged - assertPriorityAndSpeed(UNCHANGED, 12, osmWay); - - // "lcn=yes" is in fact no relation, but shall be treated the same like a relation with "network=lcn" - osmWay.setTag("lcn", "yes"); - assertPriorityAndSpeed(PREFER, 12, osmWay); - osmWay.removeTag("lcn"); - - // relation code is PREFER - ReaderRelation osmRel = new ReaderRelation(1); - osmRel.setTag("route", "bicycle"); - assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); - - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); - - // relation code is NICE - osmRel.setTag("network", "rcn"); - assertPriorityAndSpeed(VERY_NICE, 12, osmWay, osmRel); - osmWay.setTag("lcn", "yes"); - assertPriorityAndSpeed(VERY_NICE, 12, osmWay, osmRel); - - // relation code is BEST - osmRel.setTag("network", "ncn"); - assertPriorityAndSpeed(BEST, 12, osmWay, osmRel); - - // PREFER relation, but tertiary road => no get off the bike but road wayTypeCode and faster - osmWay.clearTags(); osmWay.setTag("highway", "tertiary"); - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriority(UNCHANGED, osmWay); } @Test @@ -446,68 +531,13 @@ public void testUnchangedRelationShouldNotInfluencePriority() { assertPriorityAndSpeed(AVOID, 18, osmWay, osmRel); } - @Test - @Override - public void testSacScale() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "path"); - way.setTag("sac_scale", "hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("highway", "path"); - way.setTag("sac_scale", "mountain_hiking"); - assertTrue(accessParser.getAccess(way).canSkip()); - - way.setTag("highway", "cycleway"); - way.setTag("sac_scale", "hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("highway", "cycleway"); - way.setTag("sac_scale", "mountain_hiking"); - // disallow questionable combination as too dangerous - assertTrue(accessParser.getAccess(way).canSkip()); - } - - @Test - public void testCalcPriority() { - ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "tertiary"); - ReaderRelation osmRel = new ReaderRelation(1); - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "icn"); - IntsRef relFlags = osmParsers.handleRelationTags(osmRel, osmParsers.createRelationFlags()); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); - int edgeId = 0; - osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); - assertEquals(RouteNetwork.INTERNATIONAL, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); - assertEquals(PriorityCode.getValue(BEST.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), .1); - - // for some highways the priority is UNCHANGED - osmRel = new ReaderRelation(1); - osmWay = new ReaderWay(1); - osmWay.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); - osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, osmParsers.createRelationFlags()); - assertEquals(RouteNetwork.MISSING, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); - assertEquals(PriorityCode.getValue(UNCHANGED.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), .1); - - // unknown highway tags will be excluded but priority will be unchanged - osmWay = new ReaderWay(1); - osmWay.setTag("highway", "whatever"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); - osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, osmParsers.createRelationFlags()); - assertFalse(accessParser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); - assertEquals(RouteNetwork.MISSING, encodingManager.getEnumEncodedValue(BikeNetwork.KEY, RouteNetwork.class).getEnum(false, edgeId, edgeIntAccess)); - assertEquals(PriorityCode.getValue(UNCHANGED.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), .1); - } - @Test public void testMaxSpeed() { // the maxspeed is well above our speed and has no effect ReaderWay way = new ReaderWay(1); way.setTag("highway", "tertiary"); way.setTag("maxspeed", "90"); - assertPriorityAndSpeed(UNCHANGED, 18, way); + assertPriorityAndSpeed(AVOID, 18, way); way = new ReaderWay(1); way.setTag("highway", "track"); @@ -569,23 +599,31 @@ public void testClassBicycle() { way.setTag("class:bicycle", "invalidvalue"); assertPriority(UNCHANGED, way); way.setTag("class:bicycle", "-1"); - assertPriority(SLIGHT_AVOID, way); - way.setTag("class:bicycle", "-2"); assertPriority(AVOID, way); + way.setTag("class:bicycle", "-2"); + assertPriority(BAD, way); way.setTag("class:bicycle", "-3"); - assertPriority(AVOID_MORE, way); + assertPriority(REACH_DESTINATION, way); way.setTag("highway", "residential"); way.setTag("bicycle", "designated"); way.setTag("class:bicycle", "3"); assertPriority(BEST, way); - // Now we test overriding by a specific class subtype + // test overriding by a specific class subtype way.setTag("class:bicycle:touring", "2"); assertPriority(VERY_NICE, way); way.setTag("maxspeed", "15"); assertPriority(BEST, way); + + // do not overwrite better priority + way = new ReaderWay(1); + way.setTag("highway", "path"); + way.setTag("bicycle", "designated"); + way.setTag("surface", "asphalt"); + way.setTag("class:bicycle", "1"); + assertPriority(VERY_NICE, way); } @Test @@ -596,4 +634,100 @@ public void testAvoidMotorway() { assertPriority(REACH_DESTINATION, osmWay); } + @Test + public void temporalAccess() { + int edgeId = 0; + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access:conditional", "no @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("bicycle:conditional", "no @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("bicycle", "no"); + way.setTag("access:conditional", "yes @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertFalse(accessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("bicycle", "no"); + way.setTag("bicycle:conditional", "yes @ (21:00-9:00)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + } + + @Test + public void temporalAccessWithPermit() { + BikeCommonAccessParser tmpAccessParser = createAccessParser(encodingManager, new PMap("block_private=false")); + + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("bicycle", "no"); + way.setTag("bicycle:conditional", "permit @ (21:00-9:00)"); + + int edgeId = 0; + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); + tmpAccessParser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + accessParser.handleWayTags(edgeId, access, way, null); + assertFalse(accessEnc.getBool(false, edgeId, access)); + } + + @Test + public void testPedestrian() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "pedestrian"); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(PREFER, 12, way); + way.setTag("surface", "asphalt"); + assertPriorityAndSpeed(PREFER, 12, way); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("cycleway:right", "track"); + assertPriorityAndSpeed(PREFER, 18, way); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(PREFER, 18, way); + } + + @Test + public void testFerry() { + ReaderWay way = new ReaderWay(1); + way.clearTags(); + way.setTag("route", "ferry"); + assertTrue(accessParser.getAccess(way).isFerry()); + way.setTag("bicycle", "no"); + assertTrue(accessParser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("route", "ferry"); + assertTrue(accessParser.getAccess(way).isFerry()); + way.setTag("bicycle", "dismount"); + assertTrue(accessParser.getAccess(way).isFerry()); + + // issue #1432 + way.clearTags(); + way.setTag("route", "ferry"); + way.setTag("oneway", "yes"); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); + int edgeId = 0; + accessParser.handleWayTags(edgeId, edgeIntAccess, way); + assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/BusCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/BusCustomModelTest.java new file mode 100644 index 00000000000..b3a66b3d357 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BusCustomModelTest.java @@ -0,0 +1,72 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.util.FerrySpeedCalculator; +import com.graphhopper.routing.util.OSMParsers; +import com.graphhopper.routing.util.TransportationMode; +import com.graphhopper.routing.weighting.custom.CustomModelParser; +import com.graphhopper.routing.weighting.custom.CustomWeighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.util.CustomModel; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class BusCustomModelTest { + + private EncodingManager em; + private OSMParsers parsers; + + @BeforeEach + public void setup() { + BooleanEncodedValue busAccess = BusAccess.create(); + EnumEncodedValue roadClass = RoadClass.create(); + DecimalEncodedValue maxHeight = MaxHeight.create(); + DecimalEncodedValue maxWidth = MaxWidth.create(); + DecimalEncodedValue maxWeight = MaxWeight.create(); + em = new EncodingManager.Builder(). + add(busAccess). + add(VehicleSpeed.create("car", 5, 5, false)). + add(RoadEnvironment.create()). + add(Roundabout.create()).add(RoadAccess.create()).add(roadClass).add(FerrySpeed.create()). + add(maxWeight).add(maxWidth).add(maxHeight).add(MaxSpeed.create()). + build(); + + parsers = new OSMParsers(). + addWayTagParser(new OSMRoadClassParser(roadClass)). + addWayTagParser(new OSMMaxWeightParser(maxHeight)). + addWayTagParser(new OSMMaxWidthParser(maxWidth)). + addWayTagParser(new OSMMaxWeightParser(maxWeight)). + addWayTagParser(new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BUS), + busAccess, true, em.getBooleanEncodedValue(Roundabout.KEY), + Set.of(), Set.of())); + } + + EdgeIteratorState createEdge(ReaderWay way) { + BaseGraph graph = new BaseGraph.Builder(em).create(); + EdgeIteratorState edge = graph.edge(0, 1); + parsers.handleWayTags(edge.getEdge(), graph.getEdgeAccess(), way, em.createRelationFlags()); + return edge; + } + + @Test + public void testBus() { + CustomModel cm = GHUtility.loadCustomModelFromJar("bus.json"); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "steps"); + EdgeIteratorState edge = createEdge(way); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); + assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + + way.setTag("highway", "busway"); + edge = createEdge(way); + assertEquals(1, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java index 14b722696e1..8371cfb155b 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/CarTagParserTest.java @@ -19,20 +19,16 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.routing.util.WayAccess; -import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.text.DateFormat; import java.util.Arrays; -import java.util.Date; import static org.junit.jupiter.api.Assertions.*; @@ -41,7 +37,7 @@ */ public class CarTagParserTest { private final EncodingManager em = createEncodingManager("car"); - final CarAccessParser parser = createParser(em, new PMap("block_fords=true")); + final CarAccessParser parser = createParser(em, new PMap()); final CarAverageSpeedParser speedParser = new CarAverageSpeedParser(em); private final BooleanEncodedValue roundaboutEnc = em.getBooleanEncodedValue(Roundabout.KEY); private final BooleanEncodedValue accessEnc = parser.getAccessEnc(); @@ -57,13 +53,13 @@ private EncodingManager createEncodingManager(String carName) { .add(VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false)) .add(RouteNetwork.create(BikeNetwork.KEY)) .add(Smoothness.create()) + .add(Roundabout.create()) + .add(FerrySpeed.create()) .build(); } CarAccessParser createParser(EncodedValueLookup lookup, PMap properties) { - CarAccessParser carTagParser = new CarAccessParser(lookup, properties); - carTagParser.init(new DateRangeParser()); - return carTagParser; + return new CarAccessParser(lookup, properties); } @Test @@ -96,21 +92,20 @@ public void testAccess() { assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); - way.setTag("highway", "unclassified"); - way.setTag("ford", "yes"); + way.setTag("access", "yes"); + way.setTag("motor_vehicle", "no"); assertTrue(parser.getAccess(way).canSkip()); - way.setTag("motorcar", "yes"); - assertTrue(parser.getAccess(way).isWay()); way.clearTags(); + way.setTag("highway", "service"); way.setTag("access", "yes"); way.setTag("motor_vehicle", "no"); assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); way.setTag("highway", "service"); - way.setTag("access", "yes"); - way.setTag("motor_vehicle", "no"); + way.setTag("access", "no"); + way.setTag("motor_vehicle", "unknown"); assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); @@ -134,37 +129,28 @@ public void testAccess() { way.clearTags(); way.setTag("highway", "service"); - way.setTag("access", "emergency"); + way.setTag("motor_vehicle", "service"); assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); way.setTag("highway", "service"); - way.setTag("motor_vehicle", "emergency"); - assertTrue(parser.getAccess(way).canSkip()); - - DateFormat simpleDateFormat = Helper.createFormatter("yyyy MMM dd"); - - way.clearTags(); - way.setTag("highway", "road"); - way.setTag("access:conditional", "no @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); + way.setTag("access", "emergency"); assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); - way.setTag("highway", "road"); - way.setTag("access", "no"); - way.setTag("access:conditional", "yes @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); - assertTrue(parser.getAccess(way).isWay()); - - way.clearTags(); - way.setTag("highway", "road"); - way.setTag("access", "yes"); - way.setTag("access:conditional", "no @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); + way.setTag("highway", "service"); + way.setTag("motor_vehicle", "emergency"); assertTrue(parser.getAccess(way).canSkip()); way.clearTags(); way.setTag("highway", "service"); way.setTag("service", "emergency_access"); assertTrue(parser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "unclassified"); + way.setTag("motor_vehicle", "agricultural;destination;forestry"); + assertFalse(parser.getAccess(way).canSkip()); } @Test @@ -175,37 +161,17 @@ public void testMilitaryAccess() { assertTrue(parser.getAccess(way).canSkip()); } - @Test - public void testFordAccess() { - ReaderNode node = new ReaderNode(0, 0.0, 0.0); - node.setTag("ford", "yes"); - - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "unclassified"); - way.setTag("ford", "yes"); - - // Node and way are initially blocking - assertTrue(parser.isBlockFords()); - assertTrue(parser.getAccess(way).canSkip()); - assertTrue(parser.isBarrier(node)); - - CarAccessParser tmpParser = new CarAccessParser(em, new PMap("block_fords=false")); - tmpParser.init(new DateRangeParser()); - assertTrue(tmpParser.getAccess(way).isWay()); - assertFalse(tmpParser.isBarrier(node)); - } - @Test public void testOneway() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); way.setTag("oneway", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -213,7 +179,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -222,7 +188,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); way.setTag("vehicle:forward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -230,7 +196,7 @@ public void testOneway() { way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -239,7 +205,7 @@ public void testOneway() { // This is no one way way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "designated"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -251,62 +217,38 @@ public void shouldBlockPrivate() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("access", "private"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(accessEnc.getBool(false, edgeId, edgeIntAccess)); final CarAccessParser parser = createParser(em, new PMap("block_private=false")); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(parser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); way.setTag("highway", "primary"); way.setTag("motor_vehicle", "permit"); // currently handled like "private", see #2712 - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(parser.getAccessEnc().getBool(false, edgeId, edgeIntAccess)); } - @Test - public void testSetAccess() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - int edgeId = 0; - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); - assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); - - accessEnc.setBool(false, edgeId, edgeIntAccess, true); - accessEnc.setBool(true, edgeId, edgeIntAccess, false); - assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); - assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); - - accessEnc.setBool(false, edgeId, edgeIntAccess, false); - accessEnc.setBool(true, edgeId, edgeIntAccess, true); - assertFalse(accessEnc.getBool(false, edgeId, edgeIntAccess)); - assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); - - accessEnc.setBool(false, edgeId, edgeIntAccess, false); - accessEnc.setBool(true, edgeId, edgeIntAccess, false); - assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); - } - @Test public void testMaxSpeed() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "trunk"); way.setTag("maxspeed", "500"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); - assertEquals(140, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); + assertEquals(136, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("maxspeed:backward", "10"); way.setTag("maxspeed:forward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(18, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); assertEquals(10, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); @@ -314,14 +256,14 @@ public void testMaxSpeed() { way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("maxspeed:forward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(18, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way = new ReaderWay(1); way.setTag("highway", "primary"); way.setTag("maxspeed:backward", "20"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(66, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); assertEquals(18, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); @@ -329,14 +271,14 @@ public void testMaxSpeed() { way = new ReaderWay(1); way.setTag("highway", "motorway"); way.setTag("maxspeed", "none"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(136, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); way = new ReaderWay(1); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "70 mph"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(102, avSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), 1e-1); } @@ -347,7 +289,7 @@ public void testSpeed() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "trunk"); way.setTag("maxspeed", "110"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(100, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -355,27 +297,27 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "residential"); way.setTag("surface", "cobblestone"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(30, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(16, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "track"); way.setTag("tracktype", "grade1"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(20, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "secondary"); way.setTag("surface", "compacted"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(30, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -383,7 +325,7 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "secondary"); way.setTag("motorroad", "yes"); // motorroad should not influence speed. only access for non-motor vehicles - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(60, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -391,14 +333,14 @@ public void testSpeed() { way.clearTags(); way.setTag("highway", "motorway"); way.setTag("motorroad", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(100, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "motorway_link"); way.setTag("motorroad", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(70, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -411,7 +353,7 @@ public void testSpeed() { @Test public void testSetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; avSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); assertEquals(10, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); @@ -419,7 +361,7 @@ public void testSetSpeed() { @Test public void testSetSpeed0_issue367_issue1234() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -445,7 +387,7 @@ public void testSetSpeed0_issue367_issue1234() { @Test public void testRoundabout() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; accessEnc.setBool(false, edgeId, edgeIntAccess, true); accessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -461,7 +403,7 @@ public void testRoundabout() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(accessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -505,16 +447,16 @@ public void testFerry() { way.setTag("motorcar", "yes"); way.setTag("bicycle", "no"); // Provide the duration value in seconds: - way.setTag("way_distance", 50000.0); - way.setTag("speed_from_duration", 50 / (35.0 / 60)); + way.setTag("way_distance_2d", 50000.0); + way.setTag("duration_in_seconds", 35.0); assertTrue(parser.getAccess(way).isFerry()); // test for very short and slow 0.5km/h still realistic ferry way = new ReaderWay(1); way.setTag("route", "ferry"); way.setTag("motorcar", "yes"); - way.setTag("way_distance", 100.0); - way.setTag("speed_from_duration", 0.1 / (12.0 / 60)); + way.setTag("way_distance_2d", 100.0); + way.setTag("duration_in_seconds", 12.0); assertTrue(parser.getAccess(way).isFerry()); // test for missing duration @@ -547,6 +489,21 @@ public void testFerry() { assertTrue(parser.getAccess(way).canSkip()); way.setTag("vehicle", "yes"); assertTrue(parser.getAccess(way).isFerry()); + + // issue #1432 + way.clearTags(); + way.setTag("route", "ferry"); + way.setTag("oneway", "yes"); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + int edgeId = 0; + parser.handleWayTags(edgeId, edgeIntAccess, way); + assertTrue(accessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(accessEnc.getBool(true, edgeId, edgeIntAccess)); + + // speed for ferry is moved out of the encoded value, i.e. it is 0 + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + parser.handleWayTags(edgeId, edgeIntAccess, way); + assertEquals(0, avSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); } @Test @@ -606,13 +563,14 @@ public void testMaxValue() { EncodingManager em = new EncodingManager.Builder() .add(new SimpleBooleanEncodedValue("car_access", true)) .add(smallFactorSpeedEnc) + .add(FerrySpeed.create()) .addTurnCostEncodedValue(TurnCost.create("car", 1)) .build(); CarAverageSpeedParser speedParser = new CarAverageSpeedParser(em); ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "60 mph"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -625,24 +583,11 @@ public void testMaxValue() { way = new ReaderWay(2); way.setTag("highway", "motorway_link"); way.setTag("maxspeed", "70 mph"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(101.5, smallFactorSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); } - @Test - public void testSetToMaxSpeed() { - ReaderWay way = new ReaderWay(12); - way.setTag("maxspeed", "90"); - assertEquals(90, AbstractAverageSpeedParser.getMaxSpeed(way, false), 1e-2); - - way = new ReaderWay(12); - way.setTag("maxspeed", "90"); - way.setTag("maxspeed:backward", "50"); - assertEquals(90, AbstractAverageSpeedParser.getMaxSpeed(way, false), 1e-2); - assertEquals(50, AbstractAverageSpeedParser.getMaxSpeed(way, true), 1e-2); - } - @Test public void testCombination() { ReaderWay way = new ReaderWay(123); @@ -650,10 +595,9 @@ public void testCombination() { way.setTag("sac_scale", "hiking"); BikeAccessParser bikeParser = new BikeAccessParser(em, new PMap()); - bikeParser.init(new DateRangeParser()); assertEquals(WayAccess.CAN_SKIP, parser.getAccess(way)); assertNotEquals(WayAccess.CAN_SKIP, bikeParser.getAccess(way)); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way); bikeParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -673,32 +617,73 @@ public void testApplyBadSurfaceSpeed() { } @Test - public void testIssue_1256() { + public void temporalAccess() { + int edgeId = 0; + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); ReaderWay way = new ReaderWay(1); - way.setTag("route", "ferry"); - way.setTag("edge_distance", 257.0); + way.setTag("highway", "primary"); + way.setTag("access:conditional", "no @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - int edgeId = 0; - speedParser.handleWayTags(edgeId, edgeIntAccess, way); - assertEquals(2, speedParser.getAverageSpeedEnc().getDecimal(false, edgeId, edgeIntAccess), .1); + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("motorcar:conditional", "no @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); - // for a smaller speed factor the minimum speed is also smaller - DecimalEncodedValueImpl lowFactorSpeedEnc = new DecimalEncodedValueImpl(VehicleSpeed.key("car"), 10, 1, false); - EncodingManager lowFactorEm = new EncodingManager.Builder() - .add(new SimpleBooleanEncodedValue(VehicleAccess.key("car"), true)) - .add(lowFactorSpeedEnc) - .build(); - edgeIntAccess = new ArrayEdgeIntAccess(lowFactorEm.getIntsForFlags()); - new CarAverageSpeedParser(lowFactorEm).handleWayTags(edgeId, edgeIntAccess, way); - assertEquals(1, lowFactorSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("motorcar", "no"); + way.setTag("access:conditional", "yes @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertFalse(accessEnc.getBool(false, edgeId, access)); + + // car should ignore unrelated conditional access restrictions of e.g. bicycle + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("vehicle", "no"); + way.setTag("bicycle:conditional", "yes @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertFalse(accessEnc.getBool(false, edgeId, access)); + + // Ignore access restriction if there is a *higher* priority temporal restriction that *could* lift it. + // But this is independent on the date! + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("motorcar:conditional", "yes @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + + // Open access even if we can't parse the conditional + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("motorcar:conditional", "yes @ (10:00 - 11:00)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(accessEnc.getBool(false, edgeId, access)); + + // ... but don't do the same for non-intended values + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("motorcar:conditional", "private @ (10:00 - 11:00)"); + parser.handleWayTags(edgeId, access, way, null); + assertFalse(accessEnc.getBool(false, edgeId, access)); } @ParameterizedTest @ValueSource(strings = {"mofa", "moped", "motorcar", "motor_vehicle", "motorcycle"}) void footway_etc_not_allowed_despite_vehicle_yes(String vehicle) { // these highways are blocked, even when we set one of the vehicles to yes - for (String highway : Arrays.asList("footway", "cycleway", "steps", "pedestrian")) { + for (String highway : Arrays.asList("footway", "cycleway", "steps")) { ReaderWay way = new ReaderWay(1); way.setTag("highway", highway); way.setTag(vehicle, "yes"); @@ -710,30 +695,69 @@ void footway_etc_not_allowed_despite_vehicle_yes(String vehicle) { void nonHighwaysFallbackSpeed_issue2845() { ReaderWay way = new ReaderWay(1); way.setTag("man_made", "pier"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); speedParser.handleWayTags(0, edgeIntAccess, way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("railway", "platform"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("route", "ski"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "abandoned"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); assertEquals(10, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); way.clearTags(); way.setTag("highway", "construction"); way.setTag("maxspeed", "100"); - speedParser.handleWayTags(0, edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()), way); + speedParser.handleWayTags(0, edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()), way); // unknown highways can be quite fast in combination with maxspeed!? assertEquals(90, avSpeedEnc.getDecimal(false, 0, edgeIntAccess), 1e-1); } + + @Test + void testPedestrianAccess() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "pedestrian"); + assertTrue(parser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("motor_vehicle", "no"); + assertTrue(parser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("motor_vehicle", "unknown"); + assertTrue(parser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("motor_vehicle", "destination"); + assertTrue(parser.getAccess(way).isWay()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("motorcar", "no"); + way.setTag("motor_vehicle", "destination"); + assertTrue(parser.getAccess(way).canSkip()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("motor_vehicle:conditional", "destination @ ( 8:00 - 10:00 )"); + assertTrue(parser.getAccess(way).isWay()); + + way.clearTags(); + way.setTag("highway", "pedestrian"); + way.setTag("access", "no"); + way.setTag("motor_vehicle:conditional", "destination @ ( 8:00 - 10:00 )"); + assertTrue(parser.getAccess(way).isWay()); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java index becc43da25d..196e04a909c 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/FootTagParserTest.java @@ -19,18 +19,18 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.*; +import com.graphhopper.util.EdgeExplorer; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; -import java.text.DateFormat; import java.util.Collections; -import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -53,18 +53,18 @@ public class FootTagParserTest { .add(footAccessEnc).add(footAvgSpeedEnc).add(footPriorityEnc).add(RouteNetwork.create(FootNetwork.KEY)) .add(bikeAccessEnc).add(bikeAvgSpeedEnc).add(RouteNetwork.create(BikeNetwork.KEY)) .add(carAccessEnc).add(carAvSpeedEnc) + .add(FerrySpeed.create()) .build(); private final FootAccessParser accessParser = new FootAccessParser(encodingManager, new PMap()); private final FootAverageSpeedParser speedParser = new FootAverageSpeedParser(encodingManager); private final FootPriorityParser prioParser = new FootPriorityParser(encodingManager); public FootTagParserTest() { - accessParser.init(new DateRangeParser()); } @Test public void testGetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; footAccessEnc.setBool(false, edgeId, edgeIntAccess, true); footAccessEnc.setBool(true, edgeId, edgeIntAccess, true); @@ -76,13 +76,13 @@ public void testGetSpeed() { public void testSteps() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "service"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(MEAN_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.setTag("highway", "steps"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(MEAN_SPEED > footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); } @@ -102,7 +102,7 @@ public void testCombined() { assertTrue(edge.get(carAccessEnc)); assertFalse(edge.getReverse(carAccessEnc)); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; footAvgSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); footAccessEnc.setBool(false, edgeId, edgeIntAccess, true); @@ -198,6 +198,11 @@ public void testAccess() { way.setTag("access", "no"); assertTrue(accessParser.getAccess(way).isWay()); + } + + @Test + public void testFerry() { + ReaderWay way = new ReaderWay(1); way.clearTags(); way.setTag("route", "ferry"); assertTrue(accessParser.getAccess(way).isFerry()); @@ -226,28 +231,37 @@ public void testAccess() { way.setTag("access", "private"); assertTrue(accessParser.getAccess(way).canSkip()); - DateFormat simpleDateFormat = Helper.createFormatter("yyyy MMM dd"); - way.clearTags(); - way.setTag("highway", "footway"); - way.setTag("access:conditional", "no @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); - assertTrue(accessParser.getAccess(way).canSkip()); - - way.setTag("foot", "yes"); // the conditional tag even overrules "yes" - assertTrue(accessParser.getAccess(way).canSkip()); + way.setTag("route", "ferry"); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); + int edgeId = 0; + accessParser.handleWayTags(edgeId, edgeIntAccess, way); + assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertTrue(footAccessEnc.getBool(true, edgeId, edgeIntAccess)); + // issue #1432 way.clearTags(); - way.setTag("highway", "footway"); - way.setTag("access", "no"); - way.setTag("access:conditional", "yes @ (" + simpleDateFormat.format(new Date().getTime()) + ")"); - assertTrue(accessParser.getAccess(way).isWay()); + way.setTag("route", "ferry"); + way.setTag("oneway", "yes"); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); + accessParser.handleWayTags(edgeId, edgeIntAccess, way); + assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(footAccessEnc.getBool(true, edgeId, edgeIntAccess)); + + // speed for ferry is moved out of the encoded value, i.e. it is 0 + way = new ReaderWay(0L); + way.setTag("route", "ferry"); + assertTrue(accessParser.getAccess(way).isFerry()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); + accessParser.handleWayTags(edgeId, edgeIntAccess, way); + assertEquals(0, bikeAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); } @Test public void testRailPlatformIssue366() { ReaderWay way = new ReaderWay(1); way.setTag("railway", "platform"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -257,7 +271,7 @@ public void testRailPlatformIssue366() { way.clearTags(); way.setTag("highway", "track"); way.setTag("railway", "platform"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -266,7 +280,7 @@ public void testRailPlatformIssue366() { way.clearTags(); // only tram, no highway => no access way.setTag("railway", "tram"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -277,7 +291,7 @@ public void testRailPlatformIssue366() { public void testPier() { ReaderWay way = new ReaderWay(1); way.setTag("man_made", "pier"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); @@ -290,13 +304,13 @@ public void testOneway() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "path"); way.setTag("foot:forward", "yes"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(footAccessEnc.getBool(true, edgeId, edgeIntAccess)); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); way.clearTags(); way.setTag("highway", "path"); way.setTag("foot:backward", "yes"); @@ -309,7 +323,7 @@ public void testOneway() { public void testMixSpeedAndSafe() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "motorway"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; accessParser.handleWayTags(edgeId, edgeIntAccess, way); assertFalse(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -317,7 +331,7 @@ public void testMixSpeedAndSafe() { assertEquals(0, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess)); way.setTag("sidewalk", "yes"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -326,7 +340,7 @@ public void testMixSpeedAndSafe() { way.clearTags(); way.setTag("highway", "track"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); accessParser.handleWayTags(edgeId, edgeIntAccess, way); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertTrue(footAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -338,51 +352,74 @@ public void testMixSpeedAndSafe() { public void testPriority() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "cycleway"); - assertEquals(PriorityCode.UNCHANGED.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.UNCHANGED, way); way.setTag("highway", "primary"); - assertEquals(PriorityCode.AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.AVOID, way); + + way.setTag("sidewalk", "yes"); + assertPriority(PriorityCode.AVOID, way); + + way.setTag("sidewalk", "no"); + assertPriority(PriorityCode.BAD, way); + + way.clearTags(); + way.setTag("highway", "tertiary"); + assertPriority(PriorityCode.UNCHANGED, way); + way.setTag("foot", "use_sidepath"); + assertPriority(PriorityCode.VERY_BAD, way); + + way.clearTags(); + way.setTag("highway", "tertiary"); + // tertiary without sidewalk is roughly like primary with sidewalk + way.setTag("sidewalk", "no"); + assertPriority(PriorityCode.AVOID, way); way.setTag("highway", "track"); way.setTag("bicycle", "official"); - assertEquals(PriorityCode.SLIGHT_AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.SLIGHT_AVOID, way); way.setTag("highway", "track"); way.setTag("bicycle", "designated"); - assertEquals(PriorityCode.SLIGHT_AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.SLIGHT_AVOID, way); way.setTag("highway", "cycleway"); way.setTag("bicycle", "designated"); way.setTag("foot", "designated"); - assertEquals(PriorityCode.PREFER.getValue(), prioParser.handlePriority(way, null)); - - way.clearTags(); - way.setTag("highway", "primary"); - way.setTag("sidewalk", "yes"); - assertEquals(PriorityCode.SLIGHT_AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.PREFER, way); way.clearTags(); way.setTag("highway", "cycleway"); way.setTag("sidewalk", "no"); - assertEquals(PriorityCode.AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.AVOID, way); way.clearTags(); way.setTag("highway", "road"); way.setTag("bicycle", "official"); way.setTag("sidewalk", "no"); - assertEquals(PriorityCode.SLIGHT_AVOID.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.SLIGHT_AVOID, way); way.clearTags(); - way.setTag("highway", "trunk"); + way.setTag("highway", "secondary"); + assertPriority(PriorityCode.AVOID, way); + way.setTag("highway", "trunk"); // secondary should be better to mostly avoid trunk e.g. here 46.9889,10.5664->47.0172,10.6059 + assertPriority(PriorityCode.BAD, way); + way.setTag("sidewalk", "no"); - assertEquals(PriorityCode.VERY_BAD.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.REACH_DESTINATION, way); way.setTag("sidewalk", "none"); - assertEquals(PriorityCode.VERY_BAD.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.REACH_DESTINATION, way); way.clearTags(); way.setTag("highway", "residential"); way.setTag("sidewalk", "yes"); - assertEquals(PriorityCode.PREFER.getValue(), prioParser.handlePriority(way, null)); + assertPriority(PriorityCode.PREFER, way); + } + + void assertPriority(PriorityCode code, ReaderWay way) { + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); + prioParser.handleWayTags(0, access, way, null); + assertEquals(PriorityCode.getValue(code.getValue()), footPriorityEnc.getDecimal(false, 0, access), 0.01); } @Test @@ -390,14 +427,14 @@ public void testSlowHiking() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "track"); way.setTag("sac_scale", "hiking"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(MEAN_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); way.setTag("highway", "track"); way.setTag("sac_scale", "mountain_hiking"); - edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); speedParser.handleWayTags(edgeId, edgeIntAccess, way); assertEquals(SLOW_SPEED, footAvgSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), 1e-1); } @@ -405,7 +442,7 @@ public void testSlowHiking() { @Test public void testReadBarrierNodesFromWay() { int edgeId = 0; - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); ReaderWay way = new ReaderWay(1); way.setTag("highway", "secondary"); way.setTag("gh:barrier_edge", true); @@ -447,12 +484,16 @@ public void testBarrierAccess() { node = new ReaderNode(1, -1, -1); node.setTag("barrier", "gate"); node.setTag("access", "no"); + assertTrue(accessParser.isBarrier(node)); node.setTag("foot", "yes"); - // no barrier! + assertFalse(accessParser.isBarrier(node)); + node.setTag("locked", "yes"); + // no barrier for foot=yes! assertFalse(accessParser.isBarrier(node)); + node.clearTags(); + node.setTag("barrier", "yes"); node.setTag("locked", "yes"); - // barrier! assertTrue(accessParser.isBarrier(node)); node.clearTags(); @@ -471,31 +512,6 @@ public void testChainBarrier() { assertTrue(accessParser.isBarrier(node)); } - @Test - public void testFord() { - // by default do not block access due to fords! - ReaderNode node = new ReaderNode(1, -1, -1); - node.setTag("ford", "no"); - assertFalse(accessParser.isBarrier(node)); - - node = new ReaderNode(1, -1, -1); - node.setTag("ford", "yes"); - assertFalse(accessParser.isBarrier(node)); - - // barrier! - node.setTag("foot", "no"); - assertTrue(accessParser.isBarrier(node)); - - FootAccessParser blockFordsParser = new FootAccessParser(encodingManager, new PMap("block_fords=true")); - node = new ReaderNode(1, -1, -1); - node.setTag("ford", "no"); - assertFalse(blockFordsParser.isBarrier(node)); - - node = new ReaderNode(1, -1, -1); - node.setTag("ford", "yes"); - assertTrue(blockFordsParser.isBarrier(node)); - } - @Test public void testBlockByDefault() { ReaderNode node = new ReaderNode(1, -1, -1); @@ -546,4 +562,38 @@ public void maxSpeed() { // note that this test made more sense when we used encoders that defined a max speed. assertEquals(16, speedEnc.getNextStorableValue(15)); } + + @Test + public void temporalAccess() { + int edgeId = 0; + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access:conditional", "no @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(footAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("foot:conditional", "no @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(footAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("foot", "no"); + way.setTag("access:conditional", "yes @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertFalse(footAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("foot:conditional", "yes @ (May - June)"); + accessParser.handleWayTags(edgeId, access, way, null); + assertTrue(footAccessEnc.getBool(false, edgeId, access)); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java index 234f0a2576a..06f0f5daf4d 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/HikeCustomModelTest.java @@ -1,94 +1,81 @@ package com.graphhopper.routing.util.parsers; -import com.graphhopper.jackson.Jackson; import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.OSMParsers; -import com.graphhopper.routing.util.VehicleEncodedValues; -import com.graphhopper.routing.util.VehicleTagParsers; +import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.routing.weighting.custom.CustomWeighting; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.*; +import com.graphhopper.util.CustomModel; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.PMap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.InputStreamReader; - import static org.junit.jupiter.api.Assertions.assertEquals; public class HikeCustomModelTest { private EncodingManager em; - private DecimalEncodedValue roadsSpeedEnc; private OSMParsers parsers; @BeforeEach public void setup() { IntEncodedValue hikeRating = HikeRating.create(); em = new EncodingManager.Builder(). - add(VehicleEncodedValues.roads(new PMap())). - add(VehicleEncodedValues.foot(new PMap())). + add(VehicleAccess.create("foot")). + add(VehicleSpeed.create("foot", 4, 1, false)). + add(VehiclePriority.create("foot", 4, PriorityCode.getFactor(1), false)). + add(FerrySpeed.create()). + add(RoadEnvironment.create()). + add(RouteNetwork.create(FootNetwork.KEY)). + add(FootRoadAccess.create()). add(hikeRating).build(); - roadsSpeedEnc = em.getDecimalEncodedValue(VehicleSpeed.key("roads")); parsers = new OSMParsers(). addWayTagParser(new OSMHikeRatingParser(hikeRating)); - for (TagParser p : VehicleTagParsers.foot(em, new PMap()).getTagParsers()) - parsers.addWayTagParser(p); - for (TagParser p : VehicleTagParsers.roads(em).getTagParsers()) - parsers.addWayTagParser(p); + parsers.addWayTagParser(new FootAccessParser(em, new PMap())); + parsers.addWayTagParser(new FootAverageSpeedParser(em)); + parsers.addWayTagParser(new FootPriorityParser(em)); } EdgeIteratorState createEdge(ReaderWay way) { BaseGraph graph = new BaseGraph.Builder(em).create(); EdgeIteratorState edge = graph.edge(0, 1); - EdgeIntAccess edgeIntAccess = graph.createEdgeIntAccess(); - parsers.handleWayTags(edge.getEdge(), edgeIntAccess, way, em.createRelationFlags()); + parsers.handleWayTags(edge.getEdge(), graph.getEdgeAccess(), way, em.createRelationFlags()); return edge; } - static CustomModel getCustomModel(String file) { - try { - String string = Helper.readJSONFileWithoutComments(new InputStreamReader(GHUtility.class.getResourceAsStream("/com/graphhopper/custom_models/" + file))); - return Jackson.newObjectMapper().readValue(string, CustomModel.class); - } catch (Exception ex) { - throw new RuntimeException(ex); - } - } - @Test public void testHikePrivate() { + CustomModel cm = GHUtility.loadCustomModelFromJar("hike.json"); ReaderWay way = new ReaderWay(0L); way.setTag("highway", "track"); EdgeIteratorState edge = createEdge(way); - CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(getCustomModel("hike.json"), em, roadsSpeedEnc, null); + CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); way.setTag("motor_vehicle", "private"); edge = createEdge(way); - p = CustomModelParser.createWeightingParameters(getCustomModel("hike.json"), em, roadsSpeedEnc, null); assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); way.setTag("sac_scale", "alpine_hiking"); edge = createEdge(way); - p = CustomModelParser.createWeightingParameters(getCustomModel("hike.json"), em, roadsSpeedEnc, null); assertEquals(1.2, p.getEdgeToPriorityMapping().get(edge, false), 0.01); - assertEquals(2, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + assertEquals(1.5, p.getEdgeToSpeedMapping().get(edge, false), 0.01); way = new ReaderWay(0L); way.setTag("highway", "track"); way.setTag("access", "private"); edge = createEdge(way); - p = CustomModelParser.createWeightingParameters(getCustomModel("hike.json"), em, roadsSpeedEnc, null); assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); way.setTag("sac_scale", "alpine_hiking"); edge = createEdge(way); - p = CustomModelParser.createWeightingParameters(getCustomModel("hike.json"), em, roadsSpeedEnc, null); - // TODO this would be wrong tagging but still we should exclude the way - will be fixed with #2819 - // assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); + assertEquals(0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/MaxWeightExceptParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/MaxWeightExceptParserTest.java index 2c769627d04..1604238a7f9 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/MaxWeightExceptParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/MaxWeightExceptParserTest.java @@ -44,7 +44,7 @@ public void testConditionalTags() { readerWay.setTag("highway", "primary"); readerWay.setTag("hgv:conditional", "no @ (weight > 7.5)"); parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(MaxWeightExcept.NONE, mwEnc.getEnum(false, edgeId, edgeIntAccess)); + assertEquals(MaxWeightExcept.MISSING, mwEnc.getEnum(false, edgeId, edgeIntAccess)); // weight=5 is missing edgeIntAccess = new ArrayEdgeIntAccess(1); @@ -52,7 +52,7 @@ public void testConditionalTags() { readerWay.setTag("highway", "primary"); readerWay.setTag("vehicle:conditional", "delivery @ (weight > 5)"); parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(MaxWeightExcept.NONE, mwEnc.getEnum(false, edgeId, edgeIntAccess)); + assertEquals(MaxWeightExcept.MISSING, mwEnc.getEnum(false, edgeId, edgeIntAccess)); edgeIntAccess = new ArrayEdgeIntAccess(1); readerWay.clearTags(); @@ -70,4 +70,4 @@ public void testConditionalTags() { parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); assertEquals(MaxWeightExcept.DESTINATION, mwEnc.getEnum(false, edgeId, edgeIntAccess)); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java index 646a4f3e4f4..e0e45d77049 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/ModeAccessParserTest.java @@ -7,23 +7,63 @@ import org.junit.jupiter.api.Test; import java.util.Arrays; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; class ModeAccessParserTest { - private final EncodingManager em = new EncodingManager.Builder().add(BusAccess.create()).build(); - private final ModeAccessParser parser = new ModeAccessParser(TransportationMode.BUS, em.getBooleanEncodedValue(BusAccess.KEY), em.getBooleanEncodedValue(Roundabout.KEY)); + private final EncodingManager em = new EncodingManager.Builder().add(Roundabout.create()).add(BusAccess.create()).build(); + private final ModeAccessParser parser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BUS), + em.getBooleanEncodedValue(BusAccess.KEY), true, + em.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of()); private final BooleanEncodedValue busAccessEnc = em.getBooleanEncodedValue(BusAccess.KEY); @Test public void testAccess() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "primary"); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + int edgeId = 0; + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + } + + @Test + public void testFerry() { + // by default do not allow ferry + ReaderWay way = new ReaderWay(1); + way.setTag("route", "ferry"); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + int edgeId = 0; + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertFalse(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + + way.setTag("vehicle", "yes"); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + + // issue #1432 + way.setTag("oneway", "yes"); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + } + + @Test + public void testPrivate() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "private"); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); @@ -37,7 +77,7 @@ public void testOneway() { way.setTag("oneway", "yes"); int edgeId = 0; - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -45,7 +85,7 @@ public void testOneway() { way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("vehicle:forward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertFalse(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -53,7 +93,22 @@ public void testOneway() { way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("vehicle:backward", "no"); - edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + + way.setTag("bus:backward", "yes"); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); + parser.handleWayTags(edgeId, edgeIntAccess, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); + assertTrue(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); + + way.clearTags(); + way.setTag("highway", "tertiary"); + way.setTag("vehicle:backward", "yes"); + way.setTag("bus:backward", "no"); + edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); parser.handleWayTags(edgeId, edgeIntAccess, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, edgeIntAccess)); assertFalse(busAccessEnc.getBool(true, edgeId, edgeIntAccess)); @@ -73,6 +128,22 @@ public void testBusYes() { way.setTag("bus", "yes"); parser.handleWayTags(edgeId, access, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(0); + way.setTag("highway", "primary"); + way.setTag("oneway", "yes"); + way.setTag("oneway:bus", "no"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + assertTrue(busAccessEnc.getBool(true, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way.setTag("oneway:psv", "no"); + way.setTag("oneway:bus", "yes"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + assertFalse(busAccessEnc.getBool(true, edgeId, access)); } @Test @@ -90,39 +161,80 @@ public void testBusNo() { assertFalse(busAccessEnc.getBool(false, edgeId, access)); } + @Test + public void testBusDesignated() { + EdgeIntAccess access = new ArrayEdgeIntAccess(1); + ReaderWay way = new ReaderWay(0); + way.setTag("highway", "tertiary"); + int edgeId = 0; + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way.setTag("bus", "designated"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + } + @Test public void testBusNodeAccess() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "secondary"); way.setTag("gh:barrier_edge", true); - Map nodeTags = new HashMap<>(); - nodeTags.put("access", "no"); - nodeTags.put("bus", "yes"); - way.setTag("node_tags", Arrays.asList(nodeTags, new HashMap<>())); + way.setTag("node_tags", List.of(Map.of("access", "no", "bus", "yes"), Map.of())); EdgeIntAccess access = new ArrayEdgeIntAccess(1); int edgeId = 0; parser.handleWayTags(edgeId, access, way, null); assertTrue(busAccessEnc.getBool(false, edgeId, access)); - nodeTags = new HashMap<>(); - nodeTags.put("access", "yes"); - nodeTags.put("bus", "no"); - way.setTag("node_tags", Arrays.asList(nodeTags)); + way.setTag("node_tags", List.of(Map.of("access", "yes", "bus", "no"))); access = new ArrayEdgeIntAccess(1); parser.handleWayTags(edgeId, access, way, null); assertFalse(busAccessEnc.getBool(false, edgeId, access)); // ensure that allowing node tags (bus=yes) do not unblock the inaccessible way way.setTag("access", "no"); - nodeTags = new HashMap<>(); - nodeTags.put("bus", "yes"); - way.setTag("node_tags", Arrays.asList(nodeTags, new HashMap<>())); + way.setTag("node_tags", List.of(Map.of("bus", "yes"), Map.of())); access = new ArrayEdgeIntAccess(1); parser.handleWayTags(edgeId, access, way, null); assertFalse(busAccessEnc.getBool(false, edgeId, access)); } + @Test + public void testBarrier() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "secondary"); + way.setTag("gh:barrier_edge", true); + + way.setTag("node_tags", Arrays.asList(Map.of("barrier", "bollard"), Map.of())); + EdgeIntAccess access = new ArrayEdgeIntAccess(1); + int edgeId = 0; + parser.handleWayTags(edgeId, access, way, null); + assertFalse(busAccessEnc.getBool(false, edgeId, access)); + + way.setTag("node_tags", Arrays.asList(Map.of("barrier", "gate"), Map.of())); + access = new ArrayEdgeIntAccess(1); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + + // this special mode ignores all barriers except kissing_gate + BooleanEncodedValue tmpAccessEnc = new SimpleBooleanEncodedValue("tmp_access", true); + EncodingManager tmpEM = new EncodingManager.Builder().add(tmpAccessEnc).add(Roundabout.create()).build(); + ModeAccessParser tmpParser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR), + tmpAccessEnc, true, + tmpEM.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of("kissing_gate")); + + way = new ReaderWay(1); + way.setTag("highway", "secondary"); + way.setTag("gh:barrier_edge", true); + + way.setTag("node_tags", List.of(Map.of("barrier", "bollard"), Map.of())); + access = new ArrayEdgeIntAccess(1); + tmpParser.handleWayTags(edgeId, access, way, null); + assertTrue(tmpAccessEnc.getBool(false, edgeId, access)); + } + @Test public void testPsvYes() { EdgeIntAccess access = new ArrayEdgeIntAccess(1); @@ -147,8 +259,10 @@ public void testPsvYes() { @Test public void testMotorcycleYes() { BooleanEncodedValue mcAccessEnc = new SimpleBooleanEncodedValue("motorcycle_access", true); - EncodingManager mcEM = new EncodingManager.Builder().add(mcAccessEnc).build(); - ModeAccessParser mcParser = new ModeAccessParser(TransportationMode.MOTORCYCLE, mcAccessEnc, mcEM.getBooleanEncodedValue(Roundabout.KEY)); + EncodingManager mcEM = new EncodingManager.Builder().add(mcAccessEnc).add(Roundabout.create()).build(); + ModeAccessParser mcParser = new ModeAccessParser(OSMRoadAccessParser.toOSMRestrictions(TransportationMode.MOTORCYCLE), + mcAccessEnc, true, + mcEM.getBooleanEncodedValue(Roundabout.KEY), Set.of(), Set.of()); int edgeId = 0; EdgeIntAccess access = new ArrayEdgeIntAccess(1); @@ -163,4 +277,38 @@ public void testMotorcycleYes() { mcParser.handleWayTags(0, access, way, null); assertTrue(mcAccessEnc.getBool(false, edgeId, access)); } + + @Test + public void temporalAccess() { + int edgeId = 0; + ArrayEdgeIntAccess access = new ArrayEdgeIntAccess(1); + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access:conditional", "no @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("psv:conditional", "no @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("psv", "no"); + way.setTag("access:conditional", "yes @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertFalse(busAccessEnc.getBool(false, edgeId, access)); + + access = new ArrayEdgeIntAccess(1); + way = new ReaderWay(1); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("psv:conditional", "yes @ (May - June)"); + parser.handleWayTags(edgeId, access, way, null); + assertTrue(busAccessEnc.getBool(false, edgeId, access)); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java index af1cc5f2aa4..b13a5bb235a 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java @@ -20,27 +20,44 @@ import com.graphhopper.reader.ReaderNode; import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.EncodedValueLookup; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.VehicleEncodedValues; -import com.graphhopper.routing.util.VehicleTagParsers; +import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; import static com.graphhopper.routing.util.PriorityCode.*; import static com.graphhopper.routing.util.parsers.BikeCommonAverageSpeedParser.MIN_SPEED; -import static com.graphhopper.routing.util.parsers.BikeCommonAverageSpeedParser.PUSHING_SECTION_SPEED; import static org.junit.jupiter.api.Assertions.*; public class MountainBikeTagParserTest extends AbstractBikeTagParserTester { @Override protected EncodingManager createEncodingManager() { - return new EncodingManager.Builder().add(VehicleEncodedValues.mountainbike(new PMap())).build(); + return new EncodingManager.Builder() + .add(VehicleAccess.create("mtb")) + .add(VehicleSpeed.create("mtb", 4, 2, false)) + .add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false)) + .add(Roundabout.create()) + .add(Smoothness.create()) + .add(FerrySpeed.create()) + .add(RouteNetwork.create(BikeNetwork.KEY)) + .add(RouteNetwork.create(MtbNetwork.KEY)) + .build(); } @Override - protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) { - return VehicleTagParsers.mtb(lookup, pMap); + protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) { + return new MountainBikeAccessParser(lookup, pMap); + } + + @Override + protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) { + return new MountainBikeAverageSpeedParser(lookup); + } + + @Override + protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) { + return new MountainBikePriorityParser(lookup); } @Test @@ -50,32 +67,32 @@ public void testSpeedAndPriority() { assertPriorityAndSpeed(BAD, 18, way); way.setTag("highway", "residential"); - assertPriorityAndSpeed(PREFER, 16, way); + assertPriorityAndSpeed(PREFER, 18, way); // Test pushing section speeds way.setTag("highway", "footway"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); way.setTag("highway", "track"); - assertPriorityAndSpeed(PREFER, 18, way); + assertPriorityAndSpeed(PREFER, 12, way); - // test speed for allowed pushing section types - way.setTag("highway", "track"); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 18, way); + assertPriorityAndSpeed(PREFER, 12, way); way.setTag("highway", "track"); way.setTag("bicycle", "yes"); way.setTag("tracktype", "grade3"); assertPriorityAndSpeed(VERY_NICE, 12, way); + way.setTag("tracktype", "grade1"); + assertPriorityAndSpeed(SLIGHT_PREFER, 18, way); way.setTag("surface", "paved"); - assertPriorityAndSpeed(VERY_NICE, 18, way); + assertPriorityAndSpeed(SLIGHT_PREFER, 18, way); way.clearTags(); way.setTag("highway", "path"); way.setTag("surface", "ground"); - assertPriorityAndSpeed(PREFER, 16, way); + assertPriorityAndSpeed(PREFER, 10, way); } @Test @@ -83,7 +100,7 @@ public void testSmoothness() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "residential"); way.setTag("smoothness", "excellent"); - assertEquals(18, getSpeedFromFlags(way), 0.01); + assertEquals(20, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "bad"); assertEquals(12, getSpeedFromFlags(way), 0.01); @@ -97,97 +114,22 @@ public void testSmoothness() { way.clearTags(); way.setTag("highway", "residential"); way.setTag("surface", "ground"); - assertEquals(16, getSpeedFromFlags(way), 0.01); + assertEquals(14, getSpeedFromFlags(way), 0.01); + // pick smallest of highway, tracktype, and applied smoothness speed way.setTag("smoothness", "bad"); assertEquals(12, getSpeedFromFlags(way), 0.01); way.clearTags(); way.setTag("highway", "track"); - way.setTag("tracktype", "grade5"); - assertEquals(6, getSpeedFromFlags(way), 0.01); - - way.setTag("smoothness", "bad"); - assertEquals(4, getSpeedFromFlags(way), 0.01); + way.setTag("tracktype", "grade4"); + assertEquals(8, getSpeedFromFlags(way), 0.01); + // ignored smoothness if surface is set way.setTag("smoothness", "impassable"); assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); } - @Test - @Override - public void testSacScale() { - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "service"); - way.setTag("sac_scale", "hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("highway", "service"); - way.setTag("sac_scale", "mountain_hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("sac_scale", "alpine_hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - way.setTag("sac_scale", "demanding_alpine_hiking"); - assertTrue(accessParser.getAccess(way).canSkip()); - } - - @Test - public void testHandleWayTagsInfluencedByBikeAndMtbRelation() { - ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "track"); - - ReaderRelation osmRel = new ReaderRelation(1); - // unchanged - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - // relation code is PREFER - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(BEST, 18, osmWay, osmRel); - - // relation code is PREFER - osmRel.setTag("network", "rcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - // relation code is PREFER - osmRel.setTag("network", "ncn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - // PREFER relation, but tertiary road - // => no pushing section but road wayTypeCode and faster - osmWay.clearTags(); - osmWay.setTag("highway", "tertiary"); - - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(BEST, 18, osmWay, osmRel); - - osmWay.clearTags(); - osmRel.clearTags(); - osmWay.setTag("highway", "track"); - // unchanged - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - osmRel.setTag("route", "mtb"); - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - osmRel.setTag("network", "rcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - osmRel.setTag("network", "ncn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - - osmWay.clearTags(); - osmWay.setTag("highway", "tertiary"); - - osmRel.setTag("route", "mtb"); - osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); - } - // Issue 407 : Always block kissing_gate except for mountainbikes @Test @Override @@ -213,4 +155,10 @@ public void testBarrierAccess() { assertFalse(accessParser.isBarrier(node)); } + @Test + public void testPreferenceForSlowSpeed() { + ReaderWay osmWay = new ReaderWay(1); + osmWay.setTag("highway", "tertiary"); + assertPriority(PREFER, osmWay); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMCyclewayParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMCyclewayParserTest.java new file mode 100644 index 00000000000..2acb53579f4 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMCyclewayParserTest.java @@ -0,0 +1,118 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.*; +import com.graphhopper.storage.IntsRef; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.graphhopper.routing.ev.Cycleway.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OSMCyclewayParserTest { + private EnumEncodedValue cyclewayEnc; + private OSMCyclewayParser parser; + + @BeforeEach + public void setUp() { + cyclewayEnc = Cycleway.create(); + cyclewayEnc.init(new EncodedValue.InitializerConfig()); + parser = new OSMCyclewayParser(cyclewayEnc); + } + + private void assertValue(Cycleway expectedFwd, Cycleway expectedBwd, ReaderWay way) { + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + parser.handleWayTags(0, edgeIntAccess, way, new IntsRef(2)); + assertEquals(expectedFwd, cyclewayEnc.getEnum(false, 0, edgeIntAccess)); + assertEquals(expectedBwd, cyclewayEnc.getEnum(true, 0, edgeIntAccess)); + } + + @Test + public void testMissing() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "primary"); + assertValue(MISSING, MISSING, way); + } + + @Test + public void testGenericCycleway_setsBothDirections() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "lane"); + assertValue(LANE, LANE, way); + } + + @Test + public void testDirectionalKeys() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway:right", "track"); + assertValue(TRACK, MISSING, way); + + way = new ReaderWay(1); + way.setTag("cycleway:left", "lane"); + assertValue(MISSING, LANE, way); + + way = new ReaderWay(1); + way.setTag("cycleway:both", "shared_lane"); + assertValue(SHARED_LANE, SHARED_LANE, way); + } + + @Test + public void testLeftAndRight_independentDirections() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway:right", "track"); + way.setTag("cycleway:left", "lane"); + assertValue(TRACK, LANE, way); + } + + @Test + public void testOpposite_setsReverseOnly() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "opposite_lane"); + assertValue(MISSING, LANE, way); + + way = new ReaderWay(1); + way.setTag("cycleway", "opposite_track"); + assertValue(MISSING, TRACK, way); + } + + @Test + public void testOppositeAlone_isIgnored() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "opposite"); + assertValue(MISSING, MISSING, way); + } + + @Test + public void testBetterValueWins_sameDirection() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "no"); + way.setTag("cycleway:right", "track"); + assertValue(TRACK, NO, way); + } + + @Test + public void testUnknownValue() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "something_unknown"); + assertValue(MISSING, MISSING, way); + } + + @Test + public void testSynonyms() { + ReaderWay way = new ReaderWay(1); + way.setTag("cycleway", "sidepath"); + assertValue(SEPARATE, SEPARATE, way); + + way = new ReaderWay(1); + way.setTag("cycleway", "crossing"); + assertValue(TRACK, TRACK, way); + + way = new ReaderWay(1); + way.setTag("cycleway", "share_busway"); + assertValue(SHARED_LANE, SHARED_LANE, way); + + way = new ReaderWay(1); + way.setTag("cycleway", "shared"); + assertValue(SHARED_LANE, SHARED_LANE, way); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java index c3dc0a29ea8..b49de072de8 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMGetOffBikeParserTest.java @@ -1,13 +1,8 @@ package com.graphhopper.routing.util.parsers; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.DateRangeParser; -import com.graphhopper.routing.ev.ArrayEdgeIntAccess; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.GetOffBike; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.VehicleEncodedValues; import com.graphhopper.storage.IntsRef; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; @@ -21,9 +16,8 @@ public class OSMGetOffBikeParserTest { private final OSMGetOffBikeParser getOffParser; public OSMGetOffBikeParserTest() { - EncodingManager em = new EncodingManager.Builder().add(offBikeEnc).add(VehicleEncodedValues.bike(new PMap()).getAccessEnc()).build(); + EncodingManager em = new EncodingManager.Builder().add(offBikeEnc).add(VehicleAccess.create("bike")).add(Roundabout.create()).build(); accessParser = new BikeAccessParser(em, new PMap()); - accessParser.init(new DateRangeParser()); getOffParser = new OSMGetOffBikeParser(offBikeEnc, accessParser.getAccessEnc()); } @@ -105,9 +99,30 @@ public void testHandleCommonWayTags() { way = new ReaderWay(1); way.setTag("highway", "path"); way.setTag("foot", "yes"); - assertFalse(isGetOffBike(way)); // for now only designated will trigger true + assertFalse(isGetOffBike(way)); way.setTag("foot", "designated"); assertTrue(isGetOffBike(way)); + + way = new ReaderWay(1); + way.setTag("highway", "track"); + way.setTag("vehicle", "no"); + assertTrue(isGetOffBike(way)); + way.setTag("bicycle", "yes"); + assertFalse(isGetOffBike(way)); + + way = new ReaderWay(1); + way.setTag("highway", "cycleway"); + way.setTag("vehicle", "no"); + assertFalse(isGetOffBike(way)); + way.setTag("bicycle", "designated"); + assertFalse(isGetOffBike(way)); + + way = new ReaderWay(1); + way.setTag("highway", "track"); + way.setTag("vehicle", "forestry"); + assertTrue(isGetOffBike(way)); + way.setTag("vehicle", "forestry;agricultural"); + assertTrue(isGetOffBike(way)); } @Test @@ -134,4 +149,4 @@ private boolean isGetOffBike(ReaderWay way) { getOffParser.handleWayTags(edgeId, edgeIntAccess, way, rel); return offBikeEnc.getBool(false, edgeId, edgeIntAccess); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMLitParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMLitParserTest.java new file mode 100644 index 00000000000..9e8c52bd1a9 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMLitParserTest.java @@ -0,0 +1,41 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.*; +import com.graphhopper.storage.IntsRef; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OSMLitParserTest { + private BooleanEncodedValue LitEnc; + private OSMLitParser parser; + + @BeforeEach + public void setUp() { + LitEnc = Lit.create(); + LitEnc.init(new EncodedValue.InitializerConfig()); + parser = new OSMLitParser(LitEnc); + } + + @Test + public void testLitTags() { + IntsRef relFlags = new IntsRef(1); + ReaderWay readerWay = new ReaderWay(1); + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + readerWay.setTag("highway", "cycleway"); + parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); + assertEquals(false, LitEnc.getBool(false, edgeId, edgeIntAccess)); + + readerWay.setTag("lit", "yes"); + parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); + assertEquals(true, LitEnc.getBool(false, edgeId, edgeIntAccess)); + + readerWay.setTag("lit", "24/7"); + parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); + assertEquals(true, LitEnc.getBool(false, edgeId, edgeIntAccess)); + + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParserTest.java index 74abd7ace03..ec04ecde62a 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxSpeedParserTest.java @@ -21,6 +21,8 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.storage.IntsRef; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -51,4 +53,114 @@ void countryRule() { assertEquals(40, maxSpeedEnc.getDecimal(true, edgeId, edgeIntAccess), .1); } + @Test + public void parseMaxSpeed() { + ReaderWay way = new ReaderWay(12); + way.setTag("maxspeed", "90"); + assertEquals(90, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + way.setTag("maxspeed", "90"); + way.setTag("maxspeed:backward", "50"); + assertEquals(90, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + assertEquals(50, OSMMaxSpeedParser.parseMaxSpeed(way, true), 1e-2); + + way = new ReaderWay(12); + way.setTag("maxspeed", "none"); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + way.setTag("maxspeed", "none"); + way.setTag("highway", "secondary"); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + way.setTag("maxspeed", "none"); + way.setTag("highway", "motorway"); + assertEquals(MaxSpeed.MAXSPEED_150, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + // we ignore low maxspeeds as they are mostly bugs, see discussion in #3077 + way.setTag("maxspeed", "3"); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + // maxspeed=5 is rather popular + way.setTag("maxspeed", "5"); + assertEquals(5, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + + way = new ReaderWay(12); + // maxspeed=3mph is used for a few traffic signs, so this is the smallest we accept + way.setTag("maxspeed", "3mph"); + assertEquals(4.83, OSMMaxSpeedParser.parseMaxSpeed(way, false), 1e-2); + } + + @ParameterizedTest + @ValueSource(strings = { + "motorway", + "motorway_link", + "trunk", + "trunk_link", + "primary" + }) + void maxSpeedNone(String highway) { + DecimalEncodedValue maxSpeedEnc = MaxSpeed.create(); + maxSpeedEnc.init(new EncodedValue.InitializerConfig()); + OSMMaxSpeedParser parser = new OSMMaxSpeedParser(maxSpeedEnc); + IntsRef relFlags = new IntsRef(2); + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + assertEquals(0, maxSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); + ReaderWay way = new ReaderWay(29L); + way.setTag("highway", highway); + way.setTag("maxspeed", "none"); + parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + assertEquals(MaxSpeed.MAXSPEED_150, maxSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); + } + + @Test + void smallMaxSpeed() { + DecimalEncodedValue maxSpeedEnc = MaxSpeed.create(); + maxSpeedEnc.init(new EncodedValue.InitializerConfig()); + OSMMaxSpeedParser parser = new OSMMaxSpeedParser(maxSpeedEnc); + IntsRef relFlags = new IntsRef(2); + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + ReaderWay way = new ReaderWay(29L); + way.setTag("highway", "service"); + way.setTag("maxspeed", "3 mph"); + parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + assertEquals(4, maxSpeedEnc.getDecimal(false, edgeId, edgeIntAccess), .1); + } + + @Test + public void parseMaxspeedString() { + assertEquals(40, OSMMaxSpeedParser.parseMaxspeedString("40 km/h"), 0.1); + assertEquals(40, OSMMaxSpeedParser.parseMaxspeedString("40km/h"), 0.1); + assertEquals(40, OSMMaxSpeedParser.parseMaxspeedString("40kmh"), 0.1); + assertEquals(64.4, OSMMaxSpeedParser.parseMaxspeedString("40mph"), 0.1); + assertEquals(48.3, OSMMaxSpeedParser.parseMaxspeedString("30 mph"), 0.1); + assertEquals(18.5, OSMMaxSpeedParser.parseMaxspeedString("10 knots"), 0.1); + assertEquals(19, OSMMaxSpeedParser.parseMaxspeedString("19 kph"), 0.1); + assertEquals(19, OSMMaxSpeedParser.parseMaxspeedString("19kph"), 0.1); + assertEquals(100, OSMMaxSpeedParser.parseMaxspeedString("100"), 0.1); + assertEquals(100.5, OSMMaxSpeedParser.parseMaxspeedString("100.5"), 0.1); + assertEquals(4.8, OSMMaxSpeedParser.parseMaxspeedString("3 mph"), 0.1); + + assertEquals(OSMMaxSpeedParser.MAXSPEED_NONE, OSMMaxSpeedParser.parseMaxspeedString("none"), 0.1); + } + + @Test + public void parseMaxspeedString_invalid() { + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString(null)); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("-20")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("0")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("1")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("1km/h")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("1mph")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("2")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("3")); + assertEquals(MaxSpeed.MAXSPEED_MISSING, OSMMaxSpeedParser.parseMaxspeedString("4")); + } + } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java index 5091cca6e87..61b80ad95f1 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMMaxWeightParserTest.java @@ -24,42 +24,47 @@ public void setUp() { @Test public void testSimpleTags() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); ReaderWay readerWay = new ReaderWay(1); readerWay.setTag("highway", "primary"); readerWay.setTag("maxweight", "5"); - parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(5.0, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + assertEquals(5.0, getMaxWeight(readerWay), .01); // if value is beyond the maximum then do not use infinity instead fallback to more restrictive maximum - edgeIntAccess = new ArrayEdgeIntAccess(1); - readerWay.setTag("maxweight", "50"); - parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(25.4, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + readerWay.setTag("maxweight", "54"); + assertEquals(51, getMaxWeight(readerWay), .01); } @Test public void testConditionalTags() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); ReaderWay readerWay = new ReaderWay(1); readerWay.setTag("highway", "primary"); + readerWay.setTag("access:conditional", "no @ (weight > 7.5)"); + assertEquals(7.5, getMaxWeight(readerWay), .01); + + readerWay.setTag("access:conditional", "no @ weight > 7"); + assertEquals(7, getMaxWeight(readerWay), .01); + + readerWay = new ReaderWay(1); + readerWay.setTag("highway", "primary"); readerWay.setTag("hgv:conditional", "no @ (weight > 7.5)"); - parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(7.5, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + assertEquals(7.5, getMaxWeight(readerWay), .01); - edgeIntAccess = new ArrayEdgeIntAccess(1); + readerWay = new ReaderWay(1); + readerWay.setTag("highway", "primary"); readerWay.setTag("hgv:conditional", "none @ (weight > 10t)"); - parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(10, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + assertEquals(10, getMaxWeight(readerWay), .01); - edgeIntAccess = new ArrayEdgeIntAccess(1); readerWay.setTag("hgv:conditional", "no@ (weight > 7)"); - parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(7, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + assertEquals(7, getMaxWeight(readerWay), .01); - edgeIntAccess = new ArrayEdgeIntAccess(1); readerWay.setTag("hgv:conditional", "no @ (maxweight > 6)"); // allow different tagging + assertEquals(6, getMaxWeight(readerWay), .01); + } + + double getMaxWeight(ReaderWay readerWay) { + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(6, mwEnc.getDecimal(false, edgeId, edgeIntAccess), .01); + return mwEnc.getDecimal(false, edgeId, edgeIntAccess); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRestrictionSetterTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRestrictionSetterTest.java new file mode 100644 index 00000000000..8ad30aed84e --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRestrictionSetterTest.java @@ -0,0 +1,539 @@ +package com.graphhopper.routing.util.parsers; + +import com.carrotsearch.hppc.BitSet; +import com.carrotsearch.hppc.IntArrayList; +import com.graphhopper.reader.osm.Pair; +import com.graphhopper.reader.osm.RestrictionTopology; +import com.graphhopper.reader.osm.RestrictionType; +import com.graphhopper.routing.Dijkstra; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValueImpl; +import com.graphhopper.routing.ev.TurnRestriction; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.util.TraversalMode; +import com.graphhopper.routing.weighting.SpeedWeighting; +import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static com.graphhopper.reader.osm.OSMRestrictionConverter.buildRestrictionsForOSMRestriction; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * Here we test turn restrictions on a basic graph, but specifically for OSM (no- and only-restrictions). + * This is somewhat redundant with {@link RestrictionSetterTest}. Generally, lower-level tests in + * {@link RestrictionSetterTest} should be preferred. + */ +public class OSMRestrictionSetterTest { + private static final IntArrayList NO_PATH = IntArrayList.from(); + private DecimalEncodedValue speedEnc; + private BooleanEncodedValue turnRestrictionEnc; + private BaseGraph graph; + private RestrictionSetter r; + + @BeforeEach + void setup() { + speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); + turnRestrictionEnc = TurnRestriction.create("car1"); + EncodingManager encodingManager = EncodingManager.start() + .add(speedEnc) + .add(turnRestrictionEnc) + .build(); + graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); + r = new RestrictionSetter(graph, List.of(turnRestrictionEnc)); + } + + @Test + void viaNode_no() { + // 0-1-2 + // | | + // 3-4 + int a = edge(0, 1); + int b = edge(1, 2); + edge(1, 3); + edge(2, 4); + edge(3, 4); + RestrictionTopology topology = RestrictionTopology.node(a, 1, b); + setRestrictions(List.of(new Pair<>(topology, RestrictionType.NO))); + assertEquals(nodes(0, 1, 3, 4, 2), calcPath(0, 2)); + } + + @Test + void viaNode_only() { + // 0-1-2 + // | | + // 3-4 + int a = edge(0, 1); + int b = edge(1, 2); + edge(1, 3); + edge(2, 4); + edge(3, 4); + RestrictionTopology topology = RestrictionTopology.node(a, 1, b); + setRestrictions(List.of(new Pair<>(topology, RestrictionType.ONLY))); + assertEquals(nodes(0, 1, 2, 4, 3), calcPath(0, 3)); + } + + @Test + void viaWay_no() { + // 4 + // a b|c + // 0-1-2-3 + // | | + // 5 6 + // | | + // 8-9 + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(2, 3); + edge(2, 4); + edge(1, 5); + edge(5, 8); + edge(2, 6); + edge(6, 9); + edge(8, 9); + RestrictionTopology topology = RestrictionTopology.way(a, b, c, nodes(1, 2)); + setRestrictions(List.of( + new Pair<>(topology, RestrictionType.NO) + )); + // turning from a to b and then to c is not allowed + assertEquals(nodes(0, 1, 5, 8, 9, 6, 2, 3), calcPath(0, 3)); + // turning from a to b, or b to c is still allowed + assertEquals(nodes(0, 1, 2, 4), calcPath(0, 4)); + assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3)); + } + + @Test + void viaWay_no_withOverlap() { + // a b c d + // 0---1---2---3---4 + // |s |t |u + // 5 6 7 + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(2, 3); + int d = edge(3, 4); + int s = edge(1, 5); + int t = edge(2, 6); + int u = edge(3, 7); + + setRestrictions(List.of( + new Pair<>(RestrictionTopology.way(a, b, c, nodes(1, 2)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(b, c, d, nodes(2, 3)), RestrictionType.NO) + )); + + assertEquals(NO_PATH, calcPath(0, 3)); // a-b-c + assertEquals(nodes(0, 1, 2, 6), calcPath(0, 6)); // a-b-t + assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3)); // s-b-c + assertEquals(nodes(5, 1, 2, 6), calcPath(5, 6)); // s-b-t + + assertEquals(NO_PATH, calcPath(1, 4)); // b-c-d + assertEquals(nodes(1, 2, 3, 7), calcPath(1, 7)); // b-c-u + assertEquals(nodes(6, 2, 3, 4), calcPath(6, 4)); // t-c-d + assertEquals(nodes(6, 2, 3, 7), calcPath(6, 7)); // t-c-u + } + + @Test + void viaWay_no_withOverlap_more_complex() { + // 0 1 + // | a | + // 2--3---4--5 + // b| |d + // 6--7---8--9 + // | c | + // 10---11 + int s = edge(0, 3); + edge(1, 4); + edge(2, 3); + int a = edge(3, 4); + int t = edge(4, 5); + int b = edge(3, 7); + int d = edge(4, 8); + edge(6, 7); + int c = edge(7, 8); + edge(8, 9); + edge(7, 10); + edge(8, 11); + edge(10, 11); + setRestrictions(List.of( + new Pair<>(RestrictionTopology.node(t, 4, d), RestrictionType.NO), + new Pair<>(RestrictionTopology.node(s, 3, a), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(a, b, c, nodes(3, 7)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(b, c, d, nodes(7, 8)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(c, d, a, nodes(8, 4)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(d, a, b, nodes(4, 3)), RestrictionType.NO) + )); + + assertEquals(nodes(0, 3, 7, 8, 9), calcPath(0, 9)); + assertEquals(nodes(5, 4, 3, 7, 10, 11, 8, 9), calcPath(5, 9)); + assertEquals(nodes(5, 4, 3, 2), calcPath(5, 2)); + assertEquals(nodes(0, 3, 7, 10), calcPath(0, 10)); + assertEquals(nodes(6, 7, 8, 9), calcPath(6, 9)); + } + + @Test + void viaWay_common_via_edge_opposite_direction() { + // a b + // 0---1---2 + // |c + // 3---4---5 + // d e + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(1, 4); + int d = edge(3, 4); + int e = edge(4, 5); + + setRestrictions(List.of( + // A rather common case where u-turns between the a-b and d-e lanes are forbidden. + new Pair<>(RestrictionTopology.way(b, c, e, nodes(1, 4)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(d, c, a, nodes(4, 1)), RestrictionType.NO) + )); + + assertEquals(nodes(0, 1, 2), calcPath(0, 2)); + assertEquals(nodes(0, 1, 4, 5), calcPath(0, 5)); + assertEquals(nodes(0, 1, 4, 3), calcPath(0, 3)); + assertEquals(nodes(2, 1, 0), calcPath(2, 0)); + assertEquals(nodes(2, 1, 4, 3), calcPath(2, 3)); + assertEquals(NO_PATH, calcPath(2, 5)); + assertEquals(NO_PATH, calcPath(3, 0)); + assertEquals(nodes(3, 4, 1, 2), calcPath(3, 2)); + assertEquals(nodes(3, 4, 5), calcPath(3, 5)); + assertEquals(nodes(5, 4, 1, 0), calcPath(5, 0)); + assertEquals(nodes(5, 4, 1, 2), calcPath(5, 2)); + assertEquals(nodes(5, 4, 3), calcPath(5, 3)); + } + + @Test + void viaWay_common_via_edge_same_direction() { + // a b + // 0---1---2 + // |c + // 3---4---5 + // d e + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(1, 4); + int d = edge(3, 4); + int e = edge(4, 5); + + assertEquals(nodes(0, 1, 4, 3), calcPath(0, 3)); + assertEquals(nodes(2, 1, 4, 5), calcPath(2, 5)); + // Here edge c is used by both restrictions in the same direction. This requires a second + // artificial edge and our first implementation did not allow this, see #2907 + setRestrictions(List.of( + new Pair<>(RestrictionTopology.way(a, c, d, nodes(1, 4)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(b, c, e, nodes(1, 4)), RestrictionType.NO) + )); + assertEquals(NO_PATH, calcPath(0, 3)); + assertEquals(nodes(3, 4, 1, 0), calcPath(3, 0)); + assertEquals(NO_PATH, calcPath(2, 5)); + assertEquals(nodes(5, 4, 1, 2), calcPath(5, 2)); + assertEquals(nodes(0, 1, 2), calcPath(0, 2)); + assertEquals(nodes(1, 4, 3), calcPath(1, 3)); + assertEquals(nodes(0, 1, 4, 5), calcPath(0, 5)); + assertEquals(nodes(2, 1, 4, 3), calcPath(2, 3)); + } + + @Test + void viaWay_only() { + // 0 + // a |b c + // 1----2----3 + // |d + // 4----5----6 + // e |f g + // 7 + int a = edge(1, 2); + int b = edge(0, 2); + int c = edge(2, 3); + int d = edge(2, 5); + int e = edge(4, 5); + int f = edge(5, 7); + int g = edge(5, 6); + setRestrictions(List.of( + new Pair<>(RestrictionTopology.way(a, d, f, nodes(2, 5)), RestrictionType.ONLY), + // we add a few more restrictions, because that happens a lot in real data + new Pair<>(RestrictionTopology.node(d, 5, e), RestrictionType.NO), + new Pair<>(RestrictionTopology.node(e, 5, f), RestrictionType.NO) + )); + // following the restriction is allowed of course + assertEquals(nodes(1, 2, 5, 7), calcPath(1, 7)); + // taking another turn at the beginning is not allowed + assertEquals(NO_PATH, calcPath(1, 3)); + // taking another turn after the first turn is not allowed either + assertEquals(NO_PATH, calcPath(1, 4)); + // coming from somewhere else we can go anywhere + assertEquals(nodes(0, 2, 5, 6), calcPath(0, 6)); + assertEquals(nodes(0, 2, 5, 7), calcPath(0, 7)); + } + + @Test + void viaWay_only_twoRestrictionsSharingSameVia() { + // a c d + // 0---1---2---3 + // |b |e + // 5--/ \--4 + int a = edge(0, 1); + int b = edge(5, 1); + int c = edge(1, 2); + int d = edge(2, 3); + int e = edge(2, 4); + assertEquals(nodes(0, 1, 2, 4), calcPath(0, 4)); + assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3)); + setRestrictions(List.of( + // These are two 'only' via-way restrictions that share the same via way. A real-world example can + // be found in Rüdesheim am Rhein (49.97645, 7.91309) where vehicles either have to go straight or enter the ferry depending + // on the from-way, even though they use the same via way before. This is the same + // problem we saw in #2907. + new Pair<>(RestrictionTopology.way(a, c, d, nodes(1, 2)), RestrictionType.ONLY), + new Pair<>(RestrictionTopology.way(b, c, e, nodes(1, 2)), RestrictionType.ONLY) + )); + assertEquals(nodes(0, 1, 2, 3), calcPath(0, 3)); + assertEquals(NO_PATH, calcPath(5, 3)); + assertEquals(nodes(5, 1, 2, 4), calcPath(5, 4)); + assertEquals(NO_PATH, calcPath(0, 4)); + assertEquals(nodes(3, 2, 1, 0), calcPath(3, 0)); + assertEquals(nodes(3, 2, 1, 5), calcPath(3, 5)); + assertEquals(nodes(4, 2, 1, 0), calcPath(4, 0)); + assertEquals(nodes(4, 2, 1, 5), calcPath(4, 5)); + } + + @Test + void viaWay_only_twoRestrictionsSharingSameVia_different_directions() { + // a c d + // 0---1---2---3 + // |b |e + // 5--/ \--4 + int a = edge(0, 1); + int b = edge(5, 1); + int c = edge(1, 2); + int d = edge(2, 3); + int e = edge(2, 4); + setRestrictions(List.of( + // here the via-edge is used in opposite directions (this used to be important with our initial implementation for via-way restrictions) + new Pair<>(RestrictionTopology.way(a, c, d, nodes(1, 2)), RestrictionType.ONLY), + new Pair<>(RestrictionTopology.way(e, c, b, nodes(2, 1)), RestrictionType.ONLY) + )); + assertEquals(nodes(0, 1, 2, 3), calcPath(0, 3)); + assertEquals(NO_PATH, calcPath(0, 4)); + assertEquals(NO_PATH, calcPath(0, 5)); + assertEquals(nodes(3, 2, 1, 0), calcPath(3, 0)); + assertEquals(nodes(3, 2, 4), calcPath(3, 4)); + assertEquals(nodes(3, 2, 1, 5), calcPath(3, 5)); + assertEquals(NO_PATH, calcPath(4, 0)); + assertEquals(NO_PATH, calcPath(4, 3)); + assertEquals(nodes(4, 2, 1, 5), calcPath(4, 5)); + assertEquals(nodes(5, 1, 0), calcPath(5, 0)); + assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3)); + assertEquals(nodes(5, 1, 2, 4), calcPath(5, 4)); + } + + @Test + void viaWayAndNode() { + // 4-0-1-2 + // | + // 3 + int e0_1 = edge(0, 1); + int e0_4 = edge(0, 4); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + assertEquals(nodes(0, 1, 3), calcPath(0, 3)); + assertEquals(nodes(4, 0, 1, 2), calcPath(4, 2)); + setRestrictions(List.of( + new Pair<>(RestrictionTopology.way(e0_4, e0_1, e1_2, nodes(0, 1)), RestrictionType.NO), + new Pair<>(RestrictionTopology.node(e0_1, 1, e1_3), RestrictionType.NO) + )); + assertEquals(NO_PATH, calcPath(4, 2)); + assertEquals(NO_PATH, calcPath(0, 3)); + } + + @Test + void viaWay_overlapping_no_only() { + // 3 + // a b |c + // 0---1---2---4 + // d |e + // 5 + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(2, 3); + int d = edge(2, 4); + int e = edge(4, 5); + setRestrictions(List.of( + // here the via-way of the first and the from-way of the second restriction overlap + new Pair<>(RestrictionTopology.way(a, b, c, nodes(1, 2)), RestrictionType.NO), + new Pair<>(RestrictionTopology.way(b, d, e, nodes(2, 4)), RestrictionType.ONLY) + )); + assertEquals(nodes(0, 1, 2, 4, 5), calcPath(0, 5)); + } + + @Test + void multiViaWay_only() { + // a b c + // 0---1---2---3 + // |d |e |f + // 4---5---6 + // g h + + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(2, 3); + int d = edge(1, 4); + int e = edge(2, 5); + int f = edge(3, 6); + int g = edge(4, 5); + int h = edge(5, 6); + assertEquals(nodes(0, 1, 4), calcPath(0, 4)); + setRestrictions(List.of( + new Pair<>(RestrictionTopology.way(a, IntArrayList.from(b, c), f, nodes(1, 2, 3)), RestrictionType.ONLY) + )); + assertEquals(nodes(0, 1, 2, 3, 6, 5, 4), calcPath(0, 4)); + } + + @Test + void loop_only() { + // 0=1-2 + // |\ + // 3 4 + int e0_1 = edge(0, 1); + int e1_0 = edge(0, 1); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + int e1_4 = edge(1, 4); + setRestrictions(List.of( + new Pair<>(RestrictionTopology.node(e1_3, 1, e0_1), RestrictionType.ONLY), + // here it is important we do not create the ambiguous restriction e0_1-e_1_0-e0_1 when converting the only-restriction + new Pair<>(RestrictionTopology.way(e0_1, e1_0, e1_2, nodes(0, 1)), RestrictionType.ONLY) + )); + assertEquals(nodes(3, 1, 0, 1, 2), calcPath(3, 2)); + assertEquals(NO_PATH, calcPath(3, 4)); + } + + @Test + void multiFrom_viaWay() { + // 1 \ / 5 + // 2 - 0 - 4 - 6 + // 3 / + int e1_0 = edge(1, 0); + int e2_0 = edge(2, 0); + int e3_0 = edge(3, 0); + int e0_4 = edge(0, 4); + int e4_5 = edge(4, 5); + int e4_6 = edge(4, 6); + setRestrictions(List.of( + // "no_entry" via-way restrictions have multiple from edges + new Pair<>(RestrictionTopology.way(edges(e1_0, e2_0, e3_0), edges(e0_4), edges(e4_6), nodes(1, 4)), RestrictionType.NO) + )); + for (int s = 1; s <= 3; s++) { + assertEquals(nodes(s, 0, 4, 5), calcPath(s, 5)); + assertEquals(NO_PATH, calcPath(s, 6)); + assertEquals(nodes(5, 4, 0, s), calcPath(5, s)); + assertEquals(nodes(6, 4, 0, s), calcPath(6, s)); + } + assertEquals(nodes(1, 0, 2), calcPath(1, 2)); + assertEquals(nodes(3, 0, 1), calcPath(3, 1)); + } + + @Test + void multiTo_viaWay() { + // 1 \ / 4 + // 2 - 0 - 3 - 5 + // \ 6 + int e1_0 = edge(1, 0); + int e2_0 = edge(2, 0); + int e0_3 = edge(0, 3); + int e3_4 = edge(3, 4); + int e3_5 = edge(3, 5); + int e3_6 = edge(3, 6); + setRestrictions(List.of( + // "no_exit" via-way restrictions have multiple to edges + new Pair<>(RestrictionTopology.way(edges(e1_0), edges(e0_3), edges(e3_4, e3_5, e3_6), nodes(0, 3)), RestrictionType.NO) + )); + for (int s = 4; s <= 6; s++) { + assertEquals(NO_PATH, calcPath(1, s)); + assertEquals(nodes(2, 0, 3, s), calcPath(2, s)); + assertEquals(nodes(s, 3, 0, 1), calcPath(s, 1)); + assertEquals(nodes(s, 3, 0, 2), calcPath(s, 2)); + } + assertEquals(nodes(4, 3, 5), calcPath(4, 5)); + assertEquals(nodes(5, 3, 6), calcPath(5, 6)); + } + + /** + * Shorthand version that only sets restriction for the first turn restriction encoded value + */ + private void setRestrictions(List> osmRestrictions) { + setRestrictions(osmRestrictions, osmRestrictions.stream().map(r -> encBits(1)).toList()); + } + + private void setRestrictions(List> osmRestrictions, List osmEncBits) { + List restrictions = new ArrayList<>(); + List encBits = new ArrayList<>(); + for (int i = 0; i < osmRestrictions.size(); i++) { + Pair p = osmRestrictions.get(i); + List tmpRestrictions = buildRestrictionsForOSMRestriction(graph, p.first, p.second); + restrictions.addAll(tmpRestrictions); + final BitSet e = osmEncBits.get(i); + tmpRestrictions.forEach(__ -> encBits.add(RestrictionSetter.copyEncBits(e))); + } + r.setRestrictions(restrictions, encBits); + } + + /** + * Shorthand version that calculates the path for the first turn restriction encoded value + */ + private IntArrayList calcPath(int from, int to) { + return calcPath(from, to, turnRestrictionEnc); + } + + private IntArrayList calcPath(int from, int to, BooleanEncodedValue turnRestrictionEnc) { + return calcPath(this.graph, from, to, turnRestrictionEnc); + } + + private IntArrayList calcPath(Graph graph, int from, int to, BooleanEncodedValue turnRestrictionEnc) { + return new IntArrayList(new Dijkstra(graph, graph.wrapWeighting(new SpeedWeighting(speedEnc, new TurnCostProvider() { + @Override + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + if (inEdge == outEdge) return Double.POSITIVE_INFINITY; + return graph.getTurnCostStorage().get(turnRestrictionEnc, inEdge, viaNode, outEdge) ? Double.POSITIVE_INFINITY : 0; + } + + @Override + public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { + return Double.isInfinite(calcTurnWeight(inEdge, viaNode, outEdge)) ? Long.MAX_VALUE : 0L; + } + })), TraversalMode.EDGE_BASED).calcPath(from, to).calcNodes()); + } + + private IntArrayList edges(int... edges) { + return IntArrayList.from(edges); + } + + private IntArrayList nodes(int... nodes) { + return IntArrayList.from(nodes); + } + + private BitSet encBits(int... bits) { + BitSet b = new BitSet(bits.length); + for (int i = 0; i < bits.length; i++) { + if (bits[i] != 0 && bits[i] != 1) + throw new IllegalArgumentException("bits must be 0 or 1"); + if (bits[i] > 0) b.set(i); + } + return b; + } + + private int edge(int from, int to) { + return edge(from, to, true); + } + + private int edge(int from, int to, boolean bothDir) { + return graph.edge(from, to).setDistance(100).set(speedEnc, 10, bothDir ? 10 : 0).getEdge(); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java index d88115dcc47..ad685fd80f9 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRoadAccessParserTest.java @@ -21,63 +21,126 @@ import com.graphhopper.reader.ReaderWay; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.TransportationMode; -import com.graphhopper.routing.util.countryrules.CountryRule; import com.graphhopper.storage.IntsRef; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; class OSMRoadAccessParserTest { - EnumEncodedValue roadAccessEnc = RoadAccess.create(); - private OSMRoadAccessParser parser; + private final EnumEncodedValue roadAccessEnc = RoadAccess.create(); + private OSMRoadAccessParser parser; + private final EnumEncodedValue bikeRAEnc = BikeRoadAccess.create(); + private OSMRoadAccessParser bikeRAParser; @BeforeEach public void setup() { roadAccessEnc.init(new EncodedValue.InitializerConfig()); - parser = new OSMRoadAccessParser(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)); + bikeRAEnc.init(new EncodedValue.InitializerConfig()); + parser = OSMRoadAccessParser.forCar(roadAccessEnc); + bikeRAParser = OSMRoadAccessParser.forBike(bikeRAEnc); } + @Test void countryRule() { IntsRef relFlags = new IntsRef(2); - ReaderWay way = new ReaderWay(27L); + ReaderWay way = new ReaderWay(1L); way.setTag("highway", "track"); - way.setTag("country_rule", new CountryRule() { - @Override - public RoadAccess getAccess(ReaderWay readerWay, TransportationMode transportationMode, RoadAccess currentRoadAccess) { - return RoadAccess.DESTINATION; - } - }); + + OSMRoadAccessParser tmpParser = new OSMRoadAccessParser<>(roadAccessEnc, OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR), + (readerWay, country) -> RoadAccess.DESTINATION, RoadAccess::find); + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); int edgeId = 0; - parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + tmpParser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); assertEquals(RoadAccess.DESTINATION, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); - // if there is no country rule we get the default value - edgeIntAccess = new ArrayEdgeIntAccess(1); - way.removeTag("country_rule"); - parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); - assertEquals(RoadAccess.YES, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); + // prefer lower ordinal as this means less restriction + way.setTag("motor_vehicle", "agricultural;destination;forestry"); + tmpParser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + assertEquals(RoadAccess.DESTINATION, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); way.setTag("motor_vehicle", "agricultural;forestry"); - parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + tmpParser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); assertEquals(RoadAccess.AGRICULTURAL, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); way.setTag("motor_vehicle", "forestry;agricultural"); - parser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); + tmpParser.handleWayTags(edgeId, edgeIntAccess, way, relFlags); assertEquals(RoadAccess.AGRICULTURAL, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); - } @Test public void testPermit() { ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); int edgeId = 0; - ReaderWay way = new ReaderWay(27L); + ReaderWay way = new ReaderWay(1L); way.setTag("motor_vehicle", "permit"); parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); assertEquals(RoadAccess.PRIVATE, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); } -} \ No newline at end of file + @Test + public void testCar() { + ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + ReaderWay way = new ReaderWay(1L); + way.setTag("access", "private"); + parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); + assertEquals(RoadAccess.PRIVATE, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("motorcar", "yes"); + parser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); + assertEquals(RoadAccess.YES, roadAccessEnc.getEnum(false, edgeId, edgeIntAccess)); + } + + @Test + public void testBike() { + ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + ReaderWay way = new ReaderWay(1L); + way.setTag("access", "private"); + bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); + assertEquals(BikeRoadAccess.PRIVATE, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess)); + + way = new ReaderWay(1L); + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("vehicle", "private"); + bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); + assertEquals(BikeRoadAccess.PRIVATE, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("bicycle", "yes"); + bikeRAParser.handleWayTags(edgeId, edgeIntAccess, way, new IntsRef(1)); + assertEquals(BikeRoadAccess.YES, bikeRAEnc.getEnum(false, edgeId, edgeIntAccess)); + } + + + @Test + void germany() { + assertEquals(RoadAccess.DESTINATION, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("track"), Country.DEU)); + assertEquals(RoadAccess.YES, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("primary"), Country.DEU)); + } + + @Test + void austria() { + assertEquals(RoadAccess.FORESTRY, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("track"), Country.AUT)); + assertEquals(RoadAccess.YES, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("primary"), Country.AUT)); + assertEquals(RoadAccess.DESTINATION, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("living_street"), Country.AUT)); + } + + @Test + void hungary() { + assertEquals(RoadAccess.YES, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("primary"), Country.HUN)); + assertEquals(RoadAccess.DESTINATION, OSMRoadAccessParser.CAR_HANDLER.getAccess(createReaderWay("living_street"), Country.HUN)); + assertNull(OSMRoadAccessParser.BIKE_HANDLER.getAccess(createReaderWay("living_street"), Country.HUN)); + } + + private ReaderWay createReaderWay(String highway) { + ReaderWay readerWay = new ReaderWay(123L); + readerWay.setTag("highway", highway); + return readerWay; + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMSidewalkParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMSidewalkParserTest.java new file mode 100644 index 00000000000..f6811c6eb75 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMSidewalkParserTest.java @@ -0,0 +1,108 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.*; +import com.graphhopper.storage.IntsRef; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static com.graphhopper.routing.ev.Sidewalk.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class OSMSidewalkParserTest { + private EnumEncodedValue sidewalkEnc; + private OSMSidewalkParser parser; + + @BeforeEach + public void setUp() { + sidewalkEnc = Sidewalk.create(); + sidewalkEnc.init(new EncodedValue.InitializerConfig()); + parser = new OSMSidewalkParser(sidewalkEnc); + } + + private void assertValue(Sidewalk expectedFwd, Sidewalk expectedBwd, ReaderWay way) { + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + parser.handleWayTags(0, edgeIntAccess, way, new IntsRef(2)); + assertEquals(expectedFwd, sidewalkEnc.getEnum(false, 0, edgeIntAccess)); + assertEquals(expectedBwd, sidewalkEnc.getEnum(true, 0, edgeIntAccess)); + } + + @Test + public void testMissing() { + ReaderWay way = new ReaderWay(1); + way.setTag("highway", "residential"); + assertValue(MISSING, MISSING, way); + } + + @Test + public void testMainTag_directionalValues() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk", "both"); + assertValue(YES, YES, way); + + way = new ReaderWay(1); + way.setTag("sidewalk", "right"); + assertValue(YES, MISSING, way); + + way = new ReaderWay(1); + way.setTag("sidewalk", "left"); + assertValue(MISSING, YES, way); + + way = new ReaderWay(1); + way.setTag("sidewalk", "yes"); + assertValue(YES, YES, way); + } + + @Test + public void testMainTag_noAndSeparate() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk", "no"); + assertValue(NO, NO, way); + + way = new ReaderWay(1); + way.setTag("sidewalk", "none"); + assertValue(NO, NO, way); + + way = new ReaderWay(1); + way.setTag("sidewalk", "separate"); + assertValue(SEPARATE, SEPARATE, way); + } + + @Test + public void testDirectionalKeys() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk:right", "yes"); + assertValue(YES, MISSING, way); + + way = new ReaderWay(1); + way.setTag("sidewalk:left", "yes"); + assertValue(MISSING, YES, way); + + way = new ReaderWay(1); + way.setTag("sidewalk:both", "separate"); + assertValue(SEPARATE, SEPARATE, way); + } + + @Test + public void testDirectionalKeys_overrideMainTag() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk", "both"); + way.setTag("sidewalk:right", "separate"); + assertValue(SEPARATE, YES, way); + } + + @Test + public void testLeftAndRight_independent() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk:right", "yes"); + way.setTag("sidewalk:left", "no"); + assertValue(YES, NO, way); + } + + @Test + public void testUnknownValue() { + ReaderWay way = new ReaderWay(1); + way.setTag("sidewalk", "something_unknown"); + assertValue(MISSING, MISSING, way); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParserTest.java new file mode 100644 index 00000000000..7be844ce291 --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParserTest.java @@ -0,0 +1,150 @@ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.ArrayEdgeIntAccess; +import com.graphhopper.routing.ev.CarTemporalAccess; +import com.graphhopper.routing.ev.EnumEncodedValue; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.storage.IntsRef; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +class OSMTemporalAccessParserTest { + + private final EnumEncodedValue restricted = CarTemporalAccess.create(); + private final EncodingManager em = new EncodingManager.Builder().add(restricted).build(); + private final OSMTemporalAccessParser parser = new OSMTemporalAccessParser(CarTemporalAccess.CONDITIONALS, + (edgeId, access, b) -> restricted.setEnum(false, edgeId, access, b ? CarTemporalAccess.YES : CarTemporalAccess.NO), "2023-05-17"); + + @Test + public void testBasics() { + String today = "2023 May 17"; + ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "road"); + way.setTag("access:conditional", "no @ (" + today + ")"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.NO, restricted.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("access:conditional", "no @ ( 2023 Mar 23 - " + today + " )"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.NO, restricted.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.clearTags(); + way.setTag("highway", "road"); + way.setTag("access", "no"); + way.setTag("access:conditional", "yes @ (" + today + ")"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.YES, restricted.getEnum(false, edgeId, edgeIntAccess)); + + // for now consider if seasonal range + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("access:conditional", "no @ ( Mar 23 - Aug 23 )"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.NO, restricted.getEnum(false, edgeId, edgeIntAccess)); + + // range does not match => inverse! + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("access:conditional", "no @ ( Jun 23 - Aug 23 )"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.YES, restricted.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("access:conditional", "no @ ( 2023 Mar 23 )"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.YES, restricted.getEnum(false, edgeId, edgeIntAccess)); + + edgeIntAccess = new ArrayEdgeIntAccess(1); + way.setTag("access:conditional", "yes @ Apr-Nov"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.YES, restricted.getEnum(false, edgeId, edgeIntAccess)); + } + + @Test + public void testTaggingMistake() { + ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "road"); + // ignore incomplete values + way.setTag("access:conditional", "no @ 2023 Mar-Oct"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + // here the "1" will be interpreted as year -> incorrect range + way.setTag("access:conditional", "no @ 1 Nov - 1 Mar"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + } + + @Test + public void testWithoutDay_handleAsOpenAsPossible() { + ArrayEdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("motor_vehicle", "no"); + way.setTag("motor_vehicle:conditional", "yes @ (21:00-9:00)"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("motor_vehicle:conditional", "no @ (21:00-9:00)"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("motor_vehicle:conditional", "no @ (fuel=diesel AND emissions <= euro_6)"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("motor_vehicle:conditional", "no @ (weight > 7)"); + parser.handleWayTags(edgeId, edgeIntAccess, way, IntsRef.EMPTY); + assertEquals(CarTemporalAccess.MISSING, restricted.getEnum(false, edgeId, edgeIntAccess)); + } + + @Test + public void testPermissiveTemporalRestriction() { + List restrictionKeys = List.of("vehicle", "access"); + ReaderWay way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("access", "no"); + way.setTag("vehicle:conditional", "yes @ (May - June)"); + assertTrue(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes"))); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("vehicle", "no"); + way.setTag("access:conditional", "yes @ (May - June)"); + assertTrue(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes"))); + way.setTag("access:conditional", "private @ (May - June)"); + assertFalse(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes"))); + assertTrue(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes", "private"))); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("access", "yes"); + way.setTag("vehicle:conditional", "no @ (May - June)"); + assertFalse(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes"))); + + way = new ReaderWay(0L); + way.setTag("highway", "primary"); + way.setTag("access:conditional", "yes @ weight < 3.5"); + assertFalse(OSMTemporalAccessParser.hasPermissiveTemporalRestriction(way, 1, restrictionKeys, Set.of("yes"))); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java index e5bb19b0189..91cad743834 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMTollParserTest.java @@ -27,7 +27,7 @@ public void testSimpleTags() { int edgeId = 0; readerWay.setTag("highway", "primary"); parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); - assertEquals(Toll.MISSING, tollEnc.getEnum(false, edgeId, edgeIntAccess)); + assertEquals(Toll.NO, tollEnc.getEnum(false, edgeId, edgeIntAccess)); edgeIntAccess = new ArrayEdgeIntAccess(1); readerWay.setTag("highway", "primary"); @@ -62,4 +62,40 @@ public void testSimpleTags() { parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); assertEquals(Toll.ALL, tollEnc.getEnum(false, edgeId, edgeIntAccess)); } -} \ No newline at end of file + + @Test + void country() { + assertEquals(Toll.ALL, getToll("motorway", "", Country.HUN)); + assertEquals(Toll.HGV, getToll("trunk", "", Country.HUN)); + assertEquals(Toll.HGV, getToll("primary", "", Country.HUN)); + assertEquals(Toll.NO, getToll("secondary", "", Country.HUN)); + assertEquals(Toll.NO, getToll("tertiary", "", Country.HUN)); + + assertEquals(Toll.ALL, getToll("motorway", "", Country.FRA)); + assertEquals(Toll.NO, getToll("trunk", "", Country.FRA)); + assertEquals(Toll.NO, getToll("primary", "", Country.FRA)); + + assertEquals(Toll.NO, getToll("motorway", "", Country.MEX)); + assertEquals(Toll.NO, getToll("trunk", "", Country.MEX)); + assertEquals(Toll.NO, getToll("primary", "", Country.MEX)); + + assertEquals(Toll.ALL, getToll("secondary", "toll=yes", Country.HUN)); + assertEquals(Toll.HGV, getToll("secondary", "toll:hgv=yes", Country.HUN)); + assertEquals(Toll.HGV, getToll("secondary", "toll:N3=yes", Country.HUN)); + assertEquals(Toll.NO, getToll("secondary", "toll=no", Country.HUN)); + } + + private Toll getToll(String highway, String toll, Country country) { + ReaderWay readerWay = new ReaderWay(123L); + readerWay.setTag("highway", highway); + readerWay.setTag("country", country); + String[] tollKV = toll.split("="); + if (tollKV.length > 1) + readerWay.setTag(tollKV[0], tollKV[1]); + IntsRef relFlags = new IntsRef(2); + EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(1); + int edgeId = 0; + parser.handleWayTags(edgeId, edgeIntAccess, readerWay, relFlags); + return tollEnc.getEnum(false, edgeId, edgeIntAccess); + } +} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMValueExtractorTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMValueExtractorTest.java index 59599f21fda..1f6cf2cb90b 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMValueExtractorTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMValueExtractorTest.java @@ -1,6 +1,5 @@ package com.graphhopper.routing.util.parsers; -import com.graphhopper.routing.ev.MaxSpeed; import com.graphhopper.routing.util.parsers.helpers.OSMValueExtractor; import org.junit.jupiter.api.Test; @@ -82,27 +81,6 @@ public void stringToMeterNaN3() { assertTrue(Double.isNaN(OSMValueExtractor.stringToMeter("default"))); } - @Test - public void stringToKmh() { - assertEquals(40, OSMValueExtractor.stringToKmh("40 km/h"), DELTA); - assertEquals(40, OSMValueExtractor.stringToKmh("40km/h"), DELTA); - assertEquals(40, OSMValueExtractor.stringToKmh("40kmh"), DELTA); - assertEquals(64.374, OSMValueExtractor.stringToKmh("40mph"), DELTA); - assertEquals(48.28, OSMValueExtractor.stringToKmh("30 mph"), DELTA); - assertEquals(18.52, OSMValueExtractor.stringToKmh("10 knots"), DELTA); - assertEquals(19, OSMValueExtractor.stringToKmh("19 kph"), DELTA); - assertEquals(19, OSMValueExtractor.stringToKmh("19kph"), DELTA); - - assertEquals(MaxSpeed.UNLIMITED_SIGN_SPEED, OSMValueExtractor.stringToKmh("none"), DELTA); - } - - @Test - public void stringToKmhNaN() { - assertTrue(Double.isNaN(OSMValueExtractor.stringToKmh(null))); - assertTrue(Double.isNaN(OSMValueExtractor.stringToKmh("0"))); - assertTrue(Double.isNaN(OSMValueExtractor.stringToKmh("-20"))); - } - @Test public void testConditionalWeightToTons() { assertEquals(7.5, conditionalWeightToTons("no @ (weight>7.5)")); diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java index a0ab5f29a2c..d36246c9428 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RacingBikeTagParserTest.java @@ -22,8 +22,6 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.PriorityCode; -import com.graphhopper.routing.util.VehicleEncodedValues; -import com.graphhopper.routing.util.VehicleTagParsers; import com.graphhopper.util.PMap; import org.junit.jupiter.api.Test; @@ -34,7 +32,6 @@ import static com.graphhopper.routing.util.parsers.BikeCommonAverageSpeedParser.MIN_SPEED; import static com.graphhopper.routing.util.parsers.BikeCommonAverageSpeedParser.PUSHING_SECTION_SPEED; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author ratrun @@ -42,12 +39,31 @@ public class RacingBikeTagParserTest extends AbstractBikeTagParserTester { @Override protected EncodingManager createEncodingManager() { - return new EncodingManager.Builder().add(VehicleEncodedValues.racingbike(new PMap())).build(); + return new EncodingManager.Builder() + .add(VehicleAccess.create("racingbike")) + .add(VehicleSpeed.create("racingbike", 4, 2, false)) + .add(VehiclePriority.create("racingbike", 4, PriorityCode.getFactor(1), false)) + .add(Roundabout.create()) + .add(Smoothness.create()) + .add(FerrySpeed.create()) + .add(RouteNetwork.create(BikeNetwork.KEY)) + .add(RouteNetwork.create(MtbNetwork.KEY)) + .build(); + } + + @Override + protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) { + return new RacingBikeAccessParser(lookup, pMap); + } + + @Override + protected BikeCommonAverageSpeedParser createAverageSpeedParser(EncodedValueLookup lookup) { + return new RacingBikeAverageSpeedParser(lookup); } @Override - protected VehicleTagParsers createBikeTagParsers(EncodedValueLookup lookup, PMap pMap) { - return VehicleTagParsers.racingbike(lookup, pMap); + protected BikeCommonPriorityParser createPriorityParser(EncodedValueLookup lookup) { + return new RacingBikePriorityParser(lookup); } @Test @@ -61,10 +77,10 @@ public void testAvoidTunnel() { osmWay.setTag("highway", "secondary"); osmWay.setTag("tunnel", "yes"); - assertPriorityAndSpeed(UNCHANGED, 20, osmWay); + assertPriorityAndSpeed(UNCHANGED, 24, osmWay); osmWay.setTag("bicycle", "designated"); - assertPriorityAndSpeed(PREFER, 20, osmWay); + assertPriorityAndSpeed(PREFER, 24, osmWay); } @Test @@ -75,55 +91,56 @@ public void testService() { assertPriorityAndSpeed(SLIGHT_AVOID, 12, way); way.setTag("service", "parking_aisle"); - assertPriorityAndSpeed(SLIGHT_AVOID, 4, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 8, way); } @Test - @Override - public void testSacScale() { + public void testTrack() { ReaderWay way = new ReaderWay(1); - way.setTag("highway", "service"); - way.setTag("sac_scale", "mountain_hiking"); - assertTrue(accessParser.getAccess(way).canSkip()); - - way.setTag("highway", "path"); - way.setTag("sac_scale", "hiking"); - assertTrue(accessParser.getAccess(way).isWay()); - - // This looks to be tagging error: - way.setTag("highway", "cycleway"); - way.setTag("sac_scale", "mountain_hiking"); - // we are cautious and disallow this - assertTrue(accessParser.getAccess(way).canSkip()); + way.setTag("highway", "track"); + way.setTag("bicycle", "yes"); + assertPriorityAndSpeed(AVOID_MORE, 2, way); + way.setTag("surface", "asphalt"); + assertPriorityAndSpeed(VERY_NICE, 24, way); + + way.clearTags(); + way.setTag("highway", "track"); + way.setTag("bicycle", "designated"); + way.setTag("segregated","no"); + assertPriorityAndSpeed(AVOID_MORE, 2, way); + way.setTag("surface", "asphalt"); + assertPriorityAndSpeed(VERY_NICE, 24, way); + way.setTag("tracktype","grade1"); + assertPriorityAndSpeed(VERY_NICE, 24, way); } @Test public void testGetSpeed() { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; avgSpeedEnc.setDecimal(false, edgeId, edgeIntAccess, 10); ReaderWay way = new ReaderWay(1); way.setTag("highway", "track"); way.setTag("tracktype", "grade3"); // use pushing section - assertEquals(PUSHING_SECTION_SPEED, getSpeedFromFlags(way), 1e-1); + assertEquals(4, getSpeedFromFlags(way), 1e-1); // Even if it is part of a cycle way way.setTag("bicycle", "yes"); - assertEquals(PUSHING_SECTION_SPEED, getSpeedFromFlags(way), 1e-1); + assertEquals(4, getSpeedFromFlags(way), 1e-1); way.clearTags(); way.setTag("highway", "steps"); - assertEquals(2, getSpeedFromFlags(way), 1e-1); + assertEquals(MIN_SPEED, getSpeedFromFlags(way), 1e-1); way.clearTags(); way.setTag("highway", "primary"); - assertEquals(20, getSpeedFromFlags(way), 1e-1); + assertEquals(24, getSpeedFromFlags(way), 1e-1); way.clearTags(); way.setTag("highway", "primary"); way.setTag("surface", "paved"); - assertEquals(20, getSpeedFromFlags(way), 1e-1); + assertEquals(24, getSpeedFromFlags(way), 1e-1); way.clearTags(); way.setTag("highway", "primary"); @@ -162,9 +179,7 @@ public void testSmoothness() { way.setTag("tracktype", "grade5"); assertEquals(4, getSpeedFromFlags(way), 0.01); - way.setTag("smoothness", "bad"); - assertEquals(2, getSpeedFromFlags(way), 0.01); - + // ignore smoothness if tracktype is set way.setTag("smoothness", "impassable"); assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); } @@ -173,9 +188,8 @@ public void testSmoothness() { public void testHandleWayTagsInfluencedByRelation() { ReaderWay osmWay = new ReaderWay(1); osmWay.setTag("highway", "track"); - assertEquals(MIN_SPEED, getSpeedFromFlags(osmWay), 1e-1); + assertEquals(2, getSpeedFromFlags(osmWay), 1e-1); - // relation code is PREFER ReaderRelation osmRel = new ReaderRelation(1); osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "lcn"); @@ -190,23 +204,24 @@ public void testHandleWayTagsInfluencedByRelation() { // Now we assume bicycle=yes, and paved osmWay.setTag("tracktype", "grade1"); - assertPriorityAndSpeed(PREFER, 20, osmWay, osmRel); + assertPriorityAndSpeed(VERY_NICE, 24, osmWay, osmRel); - // Now we assume bicycle=yes, and unpaved as part of a cycle relation + // Now we assume bicycle=yes, and unpaved and as part of a cycle relation osmWay.setTag("tracktype", "grade2"); osmWay.setTag("bicycle", "yes"); assertPriorityAndSpeed(AVOID_MORE, 10, osmWay, osmRel); - // Now we assume bicycle=yes, and unpaved not part of a cycle relation + // Now we check good surface without tracktype osmWay.clearTags(); osmWay.setTag("highway", "track"); - osmWay.setTag("tracktype", "grade3"); - assertPriorityAndSpeed(AVOID_MORE, PUSHING_SECTION_SPEED, osmWay); + osmWay.setTag("surface", "asphalt"); + assertPriorityAndSpeed(VERY_NICE, 24, osmWay, osmRel); - // Now we assume bicycle=yes, and tracktype = null + // Now we assume bicycle=yes, and unpaved and not part of a cycle relation osmWay.clearTags(); osmWay.setTag("highway", "track"); - assertPriorityAndSpeed(AVOID_MORE, 2, osmWay); + osmWay.setTag("tracktype", "grade3"); + assertPriorityAndSpeed(AVOID_MORE, 4, osmWay); } @Test @@ -220,6 +235,7 @@ public void testPriority_avoidanceOfHighMaxSpeed() { .add(accessEnc).add(speedEnc).add(priorityEnc) .add(RouteNetwork.create(BikeNetwork.KEY)) .add(Smoothness.create()) + .add(FerrySpeed.create()) .build(); List parsers = Arrays.asList( new RacingBikeAverageSpeedParser(encodingManager), @@ -228,19 +244,19 @@ public void testPriority_avoidanceOfHighMaxSpeed() { ReaderWay osmWay = new ReaderWay(1); osmWay.setTag("highway", "tertiary"); osmWay.setTag("maxspeed", "50"); - assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 20, osmWay); + assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 24, osmWay); osmWay.setTag("maxspeed", "60"); - assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 20, osmWay); + assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 24, osmWay); osmWay.setTag("maxspeed", "80"); - assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 20, osmWay); + assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, PREFER, 24, osmWay); osmWay.setTag("maxspeed", "90"); - assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, UNCHANGED, 20, osmWay); + assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, UNCHANGED, 24, osmWay); osmWay.setTag("maxspeed", "120"); - assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, UNCHANGED, 20, osmWay); + assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, UNCHANGED, 24, osmWay); osmWay.setTag("highway", "motorway"); assertPriorityAndSpeed(encodingManager, priorityEnc, speedEnc, parsers, BAD, 18, osmWay); @@ -274,7 +290,7 @@ public void testPriority_avoidanceOfHighMaxSpeed() { private void assertPriorityAndSpeed(EncodingManager encodingManager, DecimalEncodedValue priorityEnc, DecimalEncodedValue speedEnc, List parsers, PriorityCode expectedPrio, double expectedSpeed, ReaderWay way) { - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); + EdgeIntAccess edgeIntAccess = ArrayEdgeIntAccess.createFromBytes(encodingManager.getBytesForFlags()); int edgeId = 0; for (TagParser p : parsers) p.handleWayTags(edgeId, edgeIntAccess, way, null); assertEquals(PriorityCode.getValue(expectedPrio.getValue()), priorityEnc.getDecimal(false, edgeId, edgeIntAccess), 0.01); @@ -291,4 +307,11 @@ public void testClassBicycle() { way.setTag("class:bicycle", "-2"); assertPriority(BEST, way); } + + @Test + public void testPreferenceForSlowSpeed() { + ReaderWay osmWay = new ReaderWay(1); + osmWay.setTag("highway", "tertiary"); + assertPriority(PREFER, osmWay); + } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java index 40574f287fd..ef8c111a8d8 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/RestrictionSetterTest.java @@ -1,35 +1,54 @@ package com.graphhopper.routing.util.parsers; +import com.carrotsearch.hppc.BitSet; import com.carrotsearch.hppc.IntArrayList; -import com.graphhopper.reader.osm.GraphRestriction; -import com.graphhopper.reader.osm.Pair; -import com.graphhopper.reader.osm.RestrictionType; +import com.carrotsearch.hppc.IntIndexedContainer; import com.graphhopper.routing.Dijkstra; -import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.Path; +import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValueImpl; +import com.graphhopper.routing.ev.TurnRestriction; +import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.storage.index.LocationIndex; +import com.graphhopper.storage.index.LocationIndexTree; +import com.graphhopper.storage.index.Snap; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import static com.graphhopper.util.GHUtility.updateDistancesFor; import static org.junit.jupiter.api.Assertions.*; public class RestrictionSetterTest { - private static final IntArrayList NO_PATH = IntArrayList.from(); private DecimalEncodedValue speedEnc; + private BooleanEncodedValue turnRestrictionEnc; + private BooleanEncodedValue turnRestrictionEnc2; private BaseGraph graph; private RestrictionSetter r; @BeforeEach void setup() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); - EncodingManager encodingManager = EncodingManager.start().add(speedEnc).build(); + turnRestrictionEnc = TurnRestriction.create("car1"); + turnRestrictionEnc2 = TurnRestriction.create("car2"); + EncodingManager encodingManager = EncodingManager.start() + .add(speedEnc) + .add(turnRestrictionEnc) + .add(turnRestrictionEnc2) + .build(); graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - r = new RestrictionSetter(graph); + r = new RestrictionSetter(graph, List.of(turnRestrictionEnc, turnRestrictionEnc2)); } @Test @@ -42,30 +61,12 @@ void viaNode_no() { edge(1, 3); edge(2, 4); edge(3, 4); - GraphRestriction graphRestriction = GraphRestriction.node(a, 1, b); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList(new Pair<>(graphRestriction, RestrictionType.NO)), turnRestrictionEnc); - assertEquals(nodes(0, 1, 3, 4, 2), calcPath(0, 2, turnRestrictionEnc)); + setRestrictions(RestrictionSetter.createViaNodeRestriction(a, 1, b)); + assertPath(0, 2, nodes(0, 1, 3, 4, 2)); } @Test - void viaNode_only() { - // 0-1-2 - // | | - // 3-4 - int a = edge(0, 1); - int b = edge(1, 2); - edge(1, 3); - edge(2, 4); - edge(3, 4); - GraphRestriction graphRestriction = GraphRestriction.node(a, 1, b); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList(new Pair<>(graphRestriction, RestrictionType.ONLY)), turnRestrictionEnc); - assertEquals(nodes(0, 1, 2, 4, 3), calcPath(0, 3, turnRestrictionEnc)); - } - - @Test - void viaWay_no() { + void viaEdge_no() { // 4 // a b|c // 0-1-2-3 @@ -82,20 +83,18 @@ void viaWay_no() { edge(2, 6); edge(6, 9); edge(8, 9); - GraphRestriction graphRestriction = GraphRestriction.way(a, b, c, nodes(1, 2)); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( - new Pair<>(graphRestriction, RestrictionType.NO) - ), turnRestrictionEnc); + setRestrictions( + createViaEdgeRestriction(a, b, c) + ); // turning from a to b and then to c is not allowed - assertEquals(nodes(0, 1, 5, 8, 9, 6, 2, 3), calcPath(0, 3, turnRestrictionEnc)); + assertPath(0, 3, nodes(0, 1, 5, 8, 9, 6, 2, 3)); // turning from a to b, or b to c is still allowed - assertEquals(nodes(0, 1, 2, 4), calcPath(0, 4, turnRestrictionEnc)); - assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3, turnRestrictionEnc)); + assertPath(0, 4, nodes(0, 1, 2, 4)); + assertPath(5, 3, nodes(5, 1, 2, 3)); } @Test - void viaWay_no_withOverlap() { + void viaEdge_withOverlap() { // a b c d // 0---1---2---3---4 // |s |t |u @@ -108,25 +107,24 @@ void viaWay_no_withOverlap() { int t = edge(2, 6); int u = edge(3, 7); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( - new Pair<>(GraphRestriction.way(a, b, c, nodes(1, 2)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(b, c, d, nodes(2, 3)), RestrictionType.NO) - ), turnRestrictionEnc); + setRestrictions( + createViaEdgeRestriction(a, b, c), + createViaEdgeRestriction(b, c, d) + ); - assertEquals(NO_PATH, calcPath(0, 3, turnRestrictionEnc)); // a-b-c - assertEquals(nodes(0, 1, 2, 6), calcPath(0, 6, turnRestrictionEnc)); // a-b-t - assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3, turnRestrictionEnc)); // s-b-c - assertEquals(nodes(5, 1, 2, 6), calcPath(5, 6, turnRestrictionEnc)); // s-b-t + assertPath(0, 3, null); + assertPath(0, 6, nodes(0, 1, 2, 6)); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 6, nodes(5, 1, 2, 6)); - assertEquals(NO_PATH, calcPath(1, 4, turnRestrictionEnc)); // b-c-d - assertEquals(nodes(1, 2, 3, 7), calcPath(1, 7, turnRestrictionEnc)); // b-c-u - assertEquals(nodes(6, 2, 3, 4), calcPath(6, 4, turnRestrictionEnc)); // t-c-d - assertEquals(nodes(6, 2, 3, 7), calcPath(6, 7, turnRestrictionEnc)); // t-c-u + assertPath(1, 4, null); + assertPath(1, 7, nodes(1, 2, 3, 7)); + assertPath(6, 4, nodes(6, 2, 3, 4)); + assertPath(6, 7, nodes(6, 2, 3, 7)); } @Test - void viaWay_no_withOverlap_more_complex() { + void viaEdge_no_withOverlap_more_complex() { // 0 1 // | a | // 2--3---4--5 @@ -147,25 +145,24 @@ void viaWay_no_withOverlap_more_complex() { edge(7, 10); edge(8, 11); edge(10, 11); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( - new Pair<>(GraphRestriction.node(t, 4, d), RestrictionType.NO), - new Pair<>(GraphRestriction.node(s, 3, a), RestrictionType.NO), - new Pair<>(GraphRestriction.way(a, b, c, nodes(3, 7)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(b, c, d, nodes(7, 8)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(c, d, a, nodes(8, 4)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(d, a, b, nodes(4, 3)), RestrictionType.NO) - ), turnRestrictionEnc); - - assertEquals(nodes(0, 3, 7, 8, 9), calcPath(0, 9, turnRestrictionEnc)); - assertEquals(nodes(5, 4, 3, 7, 10, 11, 8, 9), calcPath(5, 9, turnRestrictionEnc)); - assertEquals(nodes(5, 4, 3, 2), calcPath(5, 2, turnRestrictionEnc)); - assertEquals(nodes(0, 3, 7, 10), calcPath(0, 10, turnRestrictionEnc)); - assertEquals(nodes(6, 7, 8, 9), calcPath(6, 9, turnRestrictionEnc)); + setRestrictions( + createViaNodeRestriction(t, 4, d), + createViaNodeRestriction(s, 3, a), + createViaEdgeRestriction(a, b, c), + createViaEdgeRestriction(b, c, d), + createViaEdgeRestriction(c, d, a), + createViaEdgeRestriction(d, a, b) + ); + + assertPath(0, 9, nodes(0, 3, 7, 8, 9)); + assertPath(5, 9, nodes(5, 4, 3, 7, 10, 11, 8, 9)); + assertPath(5, 2, nodes(5, 4, 3, 2)); + assertPath(0, 10, nodes(0, 3, 7, 10)); + assertPath(6, 9, nodes(6, 7, 8, 9)); } @Test - void viaWay_common_via_edge_opposite_direction() { + void common_via_edge_opposite_direction() { // a b // 0---1---2 // |c @@ -177,48 +174,49 @@ void viaWay_common_via_edge_opposite_direction() { int d = edge(3, 4); int e = edge(4, 5); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( + setRestrictions( // A rather common case where u-turns between the a-b and d-e lanes are forbidden. - // Importantly, the via-edge c is used only once per direction so a single artificial edge is sufficient. - new Pair<>(GraphRestriction.way(b, c, e, nodes(1, 4)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(d, c, a, nodes(4, 1)), RestrictionType.NO) - ), turnRestrictionEnc); - - assertEquals(nodes(0, 1, 2), calcPath(0, 2, turnRestrictionEnc)); - assertEquals(nodes(0, 1, 4, 5), calcPath(0, 5, turnRestrictionEnc)); - assertEquals(nodes(0, 1, 4, 3), calcPath(0, 3, turnRestrictionEnc)); - assertEquals(nodes(2, 1, 0), calcPath(2, 0, turnRestrictionEnc)); - assertEquals(nodes(2, 1, 4, 3), calcPath(2, 3, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(2, 5, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(3, 0, turnRestrictionEnc)); - assertEquals(nodes(3, 4, 1, 2), calcPath(3, 2, turnRestrictionEnc)); - assertEquals(nodes(3, 4, 5), calcPath(3, 5, turnRestrictionEnc)); - assertEquals(nodes(5, 4, 1, 0), calcPath(5, 0, turnRestrictionEnc)); - assertEquals(nodes(5, 4, 1, 2), calcPath(5, 2, turnRestrictionEnc)); - assertEquals(nodes(5, 4, 3), calcPath(5, 3, turnRestrictionEnc)); + createViaEdgeRestriction(b, c, e), + createViaEdgeRestriction(d, c, a) + ); + + assertPath(0, 2, nodes(0, 1, 2)); + assertPath(0, 5, nodes(0, 1, 4, 5)); + assertPath(0, 3, nodes(0, 1, 4, 3)); + assertPath(2, 0, nodes(2, 1, 0)); + assertPath(2, 3, nodes(2, 1, 4, 3)); + assertPath(2, 5, null); + assertPath(3, 0, null); + assertPath(3, 2, nodes(3, 4, 1, 2)); + assertPath(3, 5, nodes(3, 4, 5)); + assertPath(5, 0, nodes(5, 4, 1, 0)); + assertPath(5, 2, nodes(5, 4, 1, 2)); + assertPath(5, 3, nodes(5, 4, 3)); } @Test - void viaWay_common_via_edge_opposite_direction_edge0() { + void viaEdge_common_via_edge_opposite_direction_edge0() { // a v b // 0---1---2---3 int v = edge(1, 2); int a = edge(0, 1); int b = edge(2, 3); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - IllegalStateException ex = assertThrows(IllegalStateException.class, () -> r.setRestrictions(Arrays.asList( - // This is rather academic, but for the special case where the via edge is edge 0 - // we cannot use two restrictions even though the edge is used in opposite directions. - new Pair<>(GraphRestriction.way(a, v, b, nodes(1, 2)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(b, v, a, nodes(2, 1)), RestrictionType.NO) - ), turnRestrictionEnc)); - assertTrue(ex.getMessage().contains("We cannot deal with multiple via-way restrictions if the via-edge is edge 0")); + setRestrictions( + // This is rather academic, but with our initial implementation for the special case + // where the via edge is edge 0 we could not use two restrictions even though the + // edge is used in opposite directions. + createViaEdgeRestriction(a, v, b), + createViaEdgeRestriction(b, v, a) + ); + assertPath(0, 3, null); + assertPath(1, 3, nodes(1, 2, 3)); + assertPath(3, 0, null); + assertPath(2, 0, nodes(2, 1, 0)); } @Test - void viaWay_common_via_edge_same_direction() { + void common_via_edge_same_direction() { // a b // 0---1---2 // |c @@ -230,114 +228,712 @@ void viaWay_common_via_edge_same_direction() { int d = edge(3, 4); int e = edge(4, 5); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - // Here edge c is used by both restrictions in the same direction. Supporting this - // with our current approach would require a second artificial edge. See #2907 - IllegalStateException ex = assertThrows(IllegalStateException.class, () -> - r.setRestrictions(Arrays.asList( - new Pair<>(GraphRestriction.way(a, c, d, nodes(1, 4)), RestrictionType.NO), - new Pair<>(GraphRestriction.way(b, c, e, nodes(1, 4)), RestrictionType.NO) - ), turnRestrictionEnc)); - assertTrue(ex.getMessage().contains("We cannot deal with multiple via-way restrictions that use the same via edge in the same direction"), ex.getMessage()); + assertPath(0, 3, nodes(0, 1, 4, 3)); + assertPath(2, 5, nodes(2, 1, 4, 5)); + // Here edge c is used by both restrictions in the same direction. This requires a second + // artificial edge and our first implementation did not allow this, see #2907 + setRestrictions( + createViaEdgeRestriction(a, c, d), + createViaEdgeRestriction(b, c, e) + ); + assertPath(0, 3, null); + assertPath(3, 0, nodes(3, 4, 1, 0)); + assertPath(2, 5, null); + assertPath(5, 2, nodes(5, 4, 1, 2)); + assertPath(0, 2, nodes(0, 1, 2)); + assertPath(1, 3, nodes(1, 4, 3)); + assertPath(0, 5, nodes(0, 1, 4, 5)); + assertPath(2, 3, nodes(2, 1, 4, 3)); } @Test - void viaWay_only() { - // 0 - // a |b c - // 1----2----3 - // |d - // 4----5----6 - // e |f g - // 7 - int a = edge(1, 2); - int b = edge(0, 2); - int c = edge(2, 3); - int d = edge(2, 5); - int e = edge(4, 5); - int f = edge(5, 7); + void viaEdgeAndNode() { + // 4-0-1-2 + // | + // 3 + int e0_1 = edge(0, 1); + int e0_4 = edge(0, 4); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + assertPath(0, 3, nodes(0, 1, 3)); + assertPath(4, 2, nodes(4, 0, 1, 2)); + setRestrictions( + createViaEdgeRestriction(e0_4, e0_1, e1_2), + createViaNodeRestriction(e0_1, 1, e1_3) + ); + assertPath(4, 2, null); + assertPath(0, 3, null); + } + + @Test + void pTurn() { + // 0-1-2 + // | + // 3-| + // | | + // 4-| + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + int e3_4 = edge(3, 4); + int e4_3 = edge(4, 3); + + setRestrictions( + createViaNodeRestriction(e0_1, 1, e1_2), + // here the edges e3_4 and e4_3 share two nodes, but the restrictions are still well-defined + createViaEdgeRestriction(e1_3, e4_3, e3_4), + // attention: this restriction looks like it makes the previous one redundant, + // but it doesn't, because it points the other way + createViaNodeRestriction(e4_3, 3, e3_4), + createViaNodeRestriction(e3_4, 4, e4_3) + ); + assertPath(0, 2, null); + } + + @Test + void redundantRestriction_simple() { + // 0-1-2 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + setRestrictions( + createViaNodeRestriction(e0_1, 1, e1_2), + createViaNodeRestriction(e0_1, 1, e1_2) + ); + assertPath(0, 2, null); + } + + @Test + void multiViaEdge_no() { + // a b + // 0---1---2 + // c| e |d + // 3---4 + // g |f + // 5---6---7 + // h + + int a = edge(0, 1); + int b = edge(1, 2); + int c = edge(1, 3); + int d = edge(2, 4); + int e = edge(3, 4); + int f = edge(3, 6); int g = edge(5, 6); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( - new Pair<>(GraphRestriction.way(a, d, f, nodes(2, 5)), RestrictionType.ONLY), - // we add a few more restrictions, because that happens a lot in real data - new Pair<>(GraphRestriction.node(d, 5, e), RestrictionType.NO), - new Pair<>(GraphRestriction.node(e, 5, f), RestrictionType.NO) - ), turnRestrictionEnc); - // following the restriction is allowed of course - assertEquals(nodes(1, 2, 5, 7), calcPath(1, 7, turnRestrictionEnc)); - // taking another turn at the beginning is not allowed - assertEquals(nodes(), calcPath(1, 3, turnRestrictionEnc)); - // taking another turn after the first turn is not allowed either - assertEquals(nodes(), calcPath(1, 4, turnRestrictionEnc)); - // coming from somewhere else we can go anywhere - assertEquals(nodes(0, 2, 5, 6), calcPath(0, 6, turnRestrictionEnc)); - assertEquals(nodes(0, 2, 5, 7), calcPath(0, 7, turnRestrictionEnc)); + int h = edge(6, 7); + setRestrictions( + createViaEdgeRestriction(a, c, f, g) + ); + assertPath(0, 5, nodes(0, 1, 2, 4, 3, 6, 5)); + assertPath(1, 5, nodes(1, 3, 6, 5)); + assertPath(0, 7, nodes(0, 1, 3, 6, 7)); } @Test - void viaWay_only_twoRestrictionsSharingSameVia() { - // a c d - // 0---1---2---3 - // |b |e - // 5--/ \--4 + void multiViaEdge_overlapping() { + // a b c d + // 0---1---2---3---4 + // |e |f + // 5 6 + int a = edge(0, 1); - int b = edge(5, 1); - int c = edge(1, 2); - int d = edge(2, 3); - int e = edge(2, 4); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - assertThrows(IllegalStateException.class, () -> r.setRestrictions(Arrays.asList( - // These are two 'only' via-way restrictions that share the same via way. A real-world example can - // be found in Rüdesheim am Rhein (49.97645, 7.91309) where vehicles either have to go straight or enter the ferry depending - // on the from-way, even though they use the same via way before. This is the same - // problem we saw in #2907. - // We have to make sure such cases are ignored already when we parse the OSM data. - new Pair<>(GraphRestriction.way(a, c, d, nodes(1, 2)), RestrictionType.ONLY), - new Pair<>(GraphRestriction.way(b, c, e, nodes(1, 2)), RestrictionType.ONLY) - ), turnRestrictionEnc) + int b = edge(1, 2); + int c = edge(2, 3); + int d = edge(3, 4); + int e = edge(5, 1); + int f = edge(6, 2); + assertPath(5, 6, nodes(5, 1, 2, 6)); + assertPath(0, 4, nodes(0, 1, 2, 3, 4)); + setRestrictions( + createViaEdgeRestriction(e, b, f), + createViaEdgeRestriction(a, b, c, d) + ); + assertPath(5, 6, null); + assertPath(0, 4, null); + assertPath(0, 6, nodes(0, 1, 2, 6)); + assertPath(5, 4, nodes(5, 1, 2, 3, 4)); + } + + @Test + void singleViaEdgeRestriction() { + // 5 + // | + // 0-1-2-3 + // | + // 4 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e2_4 = edge(2, 4); + int e5_1 = edge(5, 1); + assertPath(0, 3, nodes(0, 1, 2, 3)); + assertPath(0, 4, nodes(0, 1, 2, 4)); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 4, nodes(5, 1, 2, 4)); + setRestrictions( + createViaEdgeRestriction(e0_1, e1_2, e2_4) + ); + assertPath(0, 3, nodes(0, 1, 2, 3)); + // turning right at 2 is forbidden, iff we come from 0 + assertPath(0, 4, null); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 4, nodes(5, 1, 2, 4)); + assertEquals(6, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void multiViaEdgeRestriction() { + // 5 + // | + // 0-1-6-2-3 + // | + // 4 + int e0_1 = edge(0, 1); + int e1_6 = edge(1, 6); + int e6_2 = edge(6, 2); + int e2_3 = edge(2, 3); + int e2_4 = edge(2, 4); + int e5_1 = edge(5, 1); + assertPath(0, 3, nodes(0, 1, 6, 2, 3)); + assertPath(0, 4, nodes(0, 1, 6, 2, 4)); + assertPath(5, 3, nodes(5, 1, 6, 2, 3)); + assertPath(5, 4, nodes(5, 1, 6, 2, 4)); + setRestrictions( + createViaEdgeRestriction(e0_1, e1_6, e6_2, e2_4) + ); + assertPath(0, 3, nodes(0, 1, 6, 2, 3)); + // turning right at 2 is forbidden, iff we come from 0 + assertPath(0, 4, null); + assertPath(5, 3, nodes(5, 1, 6, 2, 3)); + assertPath(5, 4, nodes(5, 1, 6, 2, 4)); + assertEquals(11, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void overlappingSingleViaEdgeRestriction() { + // 7 + // | + // 0-1-2-3 + // | | + // 5 4 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e2_4 = edge(2, 4); + int e5_1 = edge(5, 1); + int e2_7 = edge(2, 7); + for (int i : new int[]{3, 4, 7}) { + assertPath(0, i, nodes(0, 1, 2, i)); + assertPath(5, i, nodes(5, 1, 2, i)); + } + setRestrictions( + createViaEdgeRestriction(e0_1, e1_2, e2_3), + createViaEdgeRestriction(e0_1, e1_2, e2_4) + ); + // coming from 0 we cannot turn onto 3 or 4 + assertPath(0, 3, null); + assertPath(0, 4, null); + assertPath(0, 7, nodes(0, 1, 2, 7)); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 4, nodes(5, 1, 2, 4)); + assertPath(5, 7, nodes(5, 1, 2, 7)); + assertEquals(7, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void overlappingSingleViaEdgeRestriction_differentStarts() { + // 7 + // | + // 0-1-2-3 + // | | + // 5 4 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e5_1 = edge(5, 1); + int e2_4 = edge(2, 4); + int e2_7 = edge(2, 7); + for (int i : new int[]{3, 4, 7}) { + assertPath(0, i, nodes(0, 1, 2, i)); + assertPath(5, i, nodes(5, 1, 2, i)); + } + assertPath(7, 4, nodes(7, 2, 4)); + setRestrictions( + createViaEdgeRestriction(e0_1, e1_2, e2_3), + createViaEdgeRestriction(e5_1, e1_2, e2_7), + createViaEdgeRestriction(e0_1, e1_2, e2_4), + createViaEdgeRestriction(e5_1, e1_2, e2_4) + ); + assertPath(0, 3, null); + assertPath(0, 4, null); + assertPath(0, 7, nodes(0, 1, 2, 7)); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 4, null); + assertPath(5, 7, null); + assertPath(7, 4, nodes(7, 2, 4)); + assertEquals(20, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void overlappingSingleViaEdgeRestriction_oppositeDirections() { + // 7 + // | + // 0-1-2-3 + // | + // 5 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e5_1 = edge(5, 1); + int e2_7 = edge(2, 7); + for (int i : new int[]{3, 7}) { + assertPath(0, i, nodes(0, 1, 2, i)); + assertPath(5, i, nodes(5, 1, 2, i)); + assertPath(i, 0, nodes(i, 2, 1, 0)); + assertPath(i, 5, nodes(i, 2, 1, 5)); + } + setRestrictions( + createViaEdgeRestriction(e0_1, e1_2, e2_3), + createViaEdgeRestriction(e2_7, e1_2, e5_1) + ); + assertPath(0, 3, null); + assertPath(0, 7, nodes(0, 1, 2, 7)); + assertPath(5, 3, nodes(5, 1, 2, 3)); + assertPath(5, 7, nodes(5, 1, 2, 7)); + assertPath(3, 0, nodes(3, 2, 1, 0)); + assertPath(3, 5, nodes(3, 2, 1, 5)); + assertPath(7, 0, nodes(7, 2, 1, 0)); + assertPath(7, 5, null); + assertEquals(18, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void viaNode() { + // 4-0-1-2 + // | + // 3 + int e0_1 = edge(0, 1); + int e0_4 = edge(0, 4); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + assertPath(0, 3, nodes(0, 1, 3)); + assertPath(4, 2, nodes(4, 0, 1, 2)); + setRestrictions( + createViaEdgeRestriction(e0_4, e0_1, e1_2), + createViaNodeRestriction(e0_1, 1, e1_3) + ); + assertPath(4, 2, null); + assertPath(0, 3, null); + assertEquals(8, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void circle() { + // 0 + // / \ + // 1---2-3 + // | + // 4 + int e4_1 = edge(4, 1, false); + int e1_0 = edge(1, 0, false); + int e0_2 = edge(0, 2, false); + int e2_1 = edge(2, 1, false); + int e2_3 = edge(2, 3, false); + assertPath(4, 3, nodes(4, 1, 0, 2, 3)); + setRestrictions( + createViaEdgeRestriction(e4_1, e1_0, e0_2, e2_3) + ); + // does this route make sense? no. is it forbidden according to the given restrictions? also no. + assertPath(4, 3, nodes(4, 1, 0, 2, 1, 0, 2, 3)); + assertEquals(11, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void avoidRedundantRestrictions() { + // /- 1 - 2 - 3 - 4 - 5 + // 0 | | + // \- 6 - 7 - 8 - 9 - 10 + edge(0, 1); + edge(1, 2); + edge(2, 3); + edge(3, 4); + edge(4, 5); + edge(0, 6); + edge(6, 7); + edge(7, 8); + edge(8, 9); + edge(9, 10); + edge(4, 9); + edge(5, 10); + + setRestrictions( + createViaEdgeRestriction(9, 8, 7), + createViaEdgeRestriction(0, 1, 2, 3, 4, 11, 9, 8, 7, 6, 5), + createViaEdgeRestriction(1, 2, 3, 4, 11, 9, 8, 7, 6, 5, 0), + createViaEdgeRestriction(2, 3, 4, 11, 9, 8, 7, 6, 5, 0, 1), + createViaEdgeRestriction(3, 4, 11, 9, 8, 7, 6, 5, 0, 1, 2), + createViaEdgeRestriction(4, 11, 9, 8, 7, 6, 5, 0, 1, 2, 3) + ); + // only six restrictions? yes, because except the first restriction they are all ignored, bc they are redundant anyway. + // without this optimization there would be 415 turn cost entries and many artificial edges! + assertEquals(6, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void duplicateRestrictions() { + // 0-1-2 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + assertPath(0, 2, nodes(0, 1, 2)); + setRestrictions( + createViaNodeRestriction(e0_1, 1, e1_2), + createViaNodeRestriction(e0_1, 1, e1_2) + ); + assertPath(0, 2, null); + assertEquals(1, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void duplicateViaEdgeRestrictions() { + // 0-1-2-3 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + assertPath(0, 3, nodes(0, 1, 2, 3)); + setRestrictions( + // they should not cancel each other out, of course + createViaEdgeRestriction(e0_1, e1_2, e2_3), + createViaEdgeRestriction(e0_1, e1_2, e2_3) ); + assertPath(0, 3, null); + assertEquals(6, graph.getTurnCostStorage().getTurnCostsCount()); } @Test - void viaWay_only_twoRestrictionsSharingSameVia_different_directions() { - // a c d + void duplicateEdgesInViaEdgeRestriction() { + // 0-1-2 + // |\ + // 3 4 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + int e1_4 = edge(1, 4); + setRestrictions( + createViaNodeRestriction(e1_3, 1, e0_1), + createViaEdgeRestriction(e1_2, e1_2, e0_1), + createViaNodeRestriction(e1_3, 1, e1_4) + ); + // todo: this test is incomplete: we'd like to check that 1-2-1-0 is forbidden, but it is forbidden anyway + // because of the infinite default u-turn costs. even if we used finite u-turn costs we could not test + // this atm, bc the turn restriction provider applies the default u-turn costs even when an actual restriction + // is present + assertPath(3, 0, null); +// assertPath(3, 4, nodes(3, 1, 2, 1, 4)); + assertEquals(8, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void circleEdgesInViaEdgeRestriction() { + // 0=1-2 + // |\ + // 3 4 + int e0_1 = edge(0, 1); + int e1_0 = edge(0, 1); + int e1_2 = edge(1, 2); + int e1_3 = edge(1, 3); + int e1_4 = edge(1, 4); + assertPath(3, 2, nodes(3, 1, 2)); + setRestrictions( + createViaEdgeRestriction(e0_1, e1_0, e1_2), + createViaNodeRestriction(e1_3, 1, e1_2), + createViaNodeRestriction(e1_3, 1, e1_0), + createViaNodeRestriction(e1_4, 1, e1_2), + createViaNodeRestriction(e1_4, 1, e0_1) + ); + // coming from 3 we are forced to go onto e0_1, but from there we can't go to 2 bc of the via-edge restriction + assertPath(3, 2, null); + // these work + assertPath(0, 2, nodes(0, 1, 2)); + assertPath(4, 2, nodes(4, 1, 0, 1, 2)); + assertEquals(11, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void similarRestrictions() { + // 4 + // | + // 0-1=2-3 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e2_1 = edge(2, 1); + int e2_3 = edge(2, 3); + int e1_4 = edge(1, 4); + assertPath(4, 0, nodes(4, 1, 0)); + setRestrictions( + createViaNodeRestriction(e1_4, 1, e0_1), + createViaNodeRestriction(e2_1, 2, e1_2), + createViaNodeRestriction(e1_2, 2, e2_1), + // This restriction has the same edges (but a different node) than the previous, + // but it shouldn't affect the others, of course. + createViaNodeRestriction(e1_2, 1, e2_1) + ); + assertPath(4, 0, null); + assertEquals(4, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void similarRestrictions_with_artificial_edges() { // 0---1---2---3 - // |b |e - // 5--/ \--4 - int a = edge(0, 1); - int b = edge(5, 1); - int c = edge(1, 2); - int d = edge(2, 3); - int e = edge(2, 4); - BooleanEncodedValue turnRestrictionEnc = createTurnRestrictionEnc("car"); - r.setRestrictions(Arrays.asList( - // since the via-edge is used in opposite directions we can deal with these restrictions - new Pair<>(GraphRestriction.way(a, c, d, nodes(1, 2)), RestrictionType.ONLY), - new Pair<>(GraphRestriction.way(e, c, b, nodes(2, 1)), RestrictionType.ONLY) - ), turnRestrictionEnc); - assertEquals(nodes(0, 1, 2, 3), calcPath(0, 3, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(0, 4, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(0, 5, turnRestrictionEnc)); - assertEquals(nodes(3, 2, 1, 0), calcPath(3, 0, turnRestrictionEnc)); - assertEquals(nodes(3, 2, 4), calcPath(3, 4, turnRestrictionEnc)); - assertEquals(nodes(3, 2, 1, 5), calcPath(3, 5, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(4, 0, turnRestrictionEnc)); - assertEquals(NO_PATH, calcPath(4, 3, turnRestrictionEnc)); - assertEquals(nodes(4, 2, 1, 5), calcPath(4, 5, turnRestrictionEnc)); - assertEquals(nodes(5, 1, 0), calcPath(5, 0, turnRestrictionEnc)); - assertEquals(nodes(5, 1, 2, 3), calcPath(5, 3, turnRestrictionEnc)); - assertEquals(nodes(5, 1, 2, 4), calcPath(5, 4, turnRestrictionEnc)); - } - - private static BooleanEncodedValue createTurnRestrictionEnc(String name) { - BooleanEncodedValue turnRestrictionEnc = TurnRestriction.create(name); - turnRestrictionEnc.init(new EncodedValue.InitializerConfig()); - return turnRestrictionEnc; - } - - private IntArrayList calcPath(int from, int to, BooleanEncodedValue turnRestrictionEnc) { - return new IntArrayList(new Dijkstra(graph, new SpeedWeighting(speedEnc, new TurnCostProvider() { + // | | + // 5 4 + int e0_1 = edge(0, 1); + int e5_1 = edge(5, 1); + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e2_4 = edge(2, 4); + setRestrictions( + // Here we get artificial edges between nodes 1 and 2, and if we did not pay attention the u-turn + // restrictions 1-2-1 and 2-1-2 would cancel out each other, so the path 0-1-2-1-5 would become + // possible. + createViaNodeRestriction(e0_1, 1, e5_1), + createViaEdgeRestriction(e0_1, e1_2, e2_4), + createViaEdgeRestriction(e2_4, e1_2, e0_1), + createViaNodeRestriction(e1_2, 2, e1_2), + createViaNodeRestriction(e1_2, 1, e1_2) + ); + assertPath(0, 5, null); + assertEquals(25, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void restrictTurnsBetweenArtificialEdges() { + // 3->| |<-8 + // 0->1-2->4 + // | + // 5 + int e3_1 = edge(3, 1, false); + int e0_1 = edge(0, 1, false); + int e1_2 = edge(1, 2); + int e2_4 = edge(2, 4, false); + int e1_5 = edge(1, 5); + int e8_2 = edge(8, 2, false); + assertPath(3, 4, nodes(3, 1, 2, 4)); + assertPath(0, 4, nodes(0, 1, 2, 4)); + assertPath(5, 4, nodes(5, 1, 2, 4)); + setRestrictions( + // This yields three artificial edges 1-2. + createViaEdgeRestriction(e0_1, e1_2, e2_4), + createViaEdgeRestriction(e3_1, e1_2, e2_4), + createViaEdgeRestriction(e8_2, e1_2, e1_5) + ); + // If we did not make sure turning between different artificial edges is forbidden we would get routes like 3-1-2-1-2-4 + assertPath(3, 4, null); + assertPath(0, 4, null); + assertPath(5, 4, nodes(5, 1, 2, 4)); + } + + + @Test + void twoProfiles() { + // Note: There are many more combinations of turn restrictions with multiple profiles that + // we could test, + // 0-1-2 + // | + // 3-4-5 + int e0_1 = edge(0, 1); + int e1_2 = edge(1, 2); + int e3_4 = edge(3, 4); + int e4_5 = edge(4, 5); + int e1_4 = edge(1, 4); + for (BooleanEncodedValue t : List.of(turnRestrictionEnc, turnRestrictionEnc2)) { + assertPath(2, 5, t, nodes(2, 1, 4, 5)); + assertPath(3, 0, t, nodes(3, 4, 1, 0)); + } + List restrictions = List.of( + createViaEdgeRestriction(e1_2, e1_4, e4_5), + createViaEdgeRestriction(e3_4, e1_4, e0_1) + ); + List encBits = List.of( + encBits(1, 1), + encBits(1, 1) + ); + setRestrictions(restrictions, encBits); + for (BooleanEncodedValue t : List.of(turnRestrictionEnc, turnRestrictionEnc2)) { + assertPath(2, 5, t, null); + assertPath(3, 0, t, null); + } + } + + @Test + void artificialEdgeSnapping() { + // 6 0 + // | | + // 1-2-x-3-4 + // | | + // 5 7 + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e3_4 = edge(3, 4); + int e0_3 = edge(0, 3); + int e2_5 = edge(2, 5); + int e2_6 = edge(2, 6); + int e3_7 = edge(3, 7); + updateDistancesFor(graph, 0, 40.03, 5.03); + updateDistancesFor(graph, 1, 40.02, 5.01); + updateDistancesFor(graph, 2, 40.02, 5.02); + updateDistancesFor(graph, 3, 40.02, 5.03); + updateDistancesFor(graph, 4, 40.02, 5.04); + updateDistancesFor(graph, 5, 40.01, 5.02); + updateDistancesFor(graph, 6, 40.03, 5.02); + updateDistancesFor(graph, 7, 40.01, 5.03); + assertPath(1, 0, nodes(1, 2, 3, 0)); + assertPath(1, 4, nodes(1, 2, 3, 4)); + assertPath(5, 0, nodes(5, 2, 3, 0)); + assertPath(6, 3, nodes(6, 2, 3)); + assertPath(2, 7, nodes(2, 3, 7)); + setRestrictions( + createViaEdgeRestriction(e1_2, e2_3, e0_3), + createViaNodeRestriction(e2_6, 2, e2_3), + createViaNodeRestriction(e2_3, 3, e3_7) + ); + assertPath(1, 0, null); + assertPath(1, 4, nodes(1, 2, 3, 4)); + assertPath(5, 0, nodes(5, 2, 3, 0)); + assertPath(6, 3, null); + assertPath(2, 7, null); + + // Now we try to route to and from a virtual node x. The problem here is that the 1-2-3-0 + // restriction forces paths coming from 1 onto an artificial edge (2-3)' (denying turns onto + // 2-3 coming from 1), so if we just snapped to the original edge 2-3 we wouldn't find a path! + // But if we snapped to the artificial edge we wouldn't find a path if we came from node 5. + // If x was our starting point we wouldn't be able to go to 0 either. + LocationIndex locationIndex = new LocationIndexTree(graph, graph.getDirectory()).prepareIndex(); + Snap snap = locationIndex.findClosest(40.02, 5.025, EdgeFilter.ALL_EDGES); + QueryGraph queryGraph = QueryGraph.create(graph, snap); + final int x = 8; + // due to the virtual node the 1-2-3-0 path is now possible + assertPath(queryGraph, 1, 0, nodes(1, 2, x, 3, 0)); + assertPath(queryGraph, 1, 4, nodes(1, 2, 3, 4)); + assertPath(queryGraph, 1, x, nodes(1, 2, x)); + assertPath(queryGraph, 5, x, nodes(5, 2, x)); + assertPath(queryGraph, x, 0, nodes(x, 3, 0)); + assertPath(queryGraph, x, 4, nodes(x, 3, 4)); + // the 6-2-3 and 2-3-7 restrictions are still enforced, despite the virtual node + assertPath(queryGraph, 6, 3, null); + assertPath(queryGraph, 2, 7, null); + + assertEquals(10, graph.getTurnCostStorage().getTurnCostsCount()); + } + + @Test + void artificialEdgeSnapping_twoVirtualNodes() { + // 1-2-x-3-y-4-z-5-6 + int e1_2 = edge(1, 2); + int e2_3 = edge(2, 3); + int e3_4 = edge(3, 4); + int e4_5 = edge(4, 5); + int e5_6 = edge(5, 6); + updateDistancesFor(graph, 1, 40.02, 5.01); + updateDistancesFor(graph, 2, 40.02, 5.02); + updateDistancesFor(graph, 3, 40.02, 5.03); + updateDistancesFor(graph, 4, 40.02, 5.04); + updateDistancesFor(graph, 5, 40.02, 5.05); + updateDistancesFor(graph, 6, 40.02, 5.06); + assertPath(1, 4, nodes(1, 2, 3, 4)); + assertPath(2, 4, nodes(2, 3, 4)); + assertPath(2, 5, nodes(2, 3, 4, 5)); + assertPath(3, 5, nodes(3, 4, 5)); + assertPath(3, 6, nodes(3, 4, 5, 6)); + assertPath(4, 6, nodes(4, 5, 6)); + setRestrictions( + createViaEdgeRestriction(e1_2, e2_3, e3_4), + createViaEdgeRestriction(e2_3, e3_4, e4_5), + createViaEdgeRestriction(e3_4, e4_5, e5_6) + ); + assertPath(1, 4, null); + assertPath(2, 4, nodes(2, 3, 4)); + assertPath(2, 5, null); + assertPath(3, 5, nodes(3, 4, 5)); + assertPath(3, 6, null); + assertPath(4, 6, nodes(4, 5, 6)); + + // three virtual notes + LocationIndex locationIndex = new LocationIndexTree(graph, graph.getDirectory()).prepareIndex(); + Snap snapX = locationIndex.findClosest(40.02, 5.025, EdgeFilter.ALL_EDGES); + Snap snapY = locationIndex.findClosest(40.02, 5.035, EdgeFilter.ALL_EDGES); + Snap snapZ = locationIndex.findClosest(40.02, 5.045, EdgeFilter.ALL_EDGES); + QueryGraph queryGraph = QueryGraph.create(graph, List.of(snapX, snapY, snapZ)); + final int x = 8; + final int y = 7; + final int z = 9; + assertEquals(x, snapX.getClosestNode()); + assertEquals(y, snapY.getClosestNode()); + assertEquals(z, snapZ.getClosestNode()); + assertPath(queryGraph, 1, 4, nodes(1, 2, x, 3, 4)); + assertPath(queryGraph, 2, 4, nodes(2, x, 3, 4)); + assertPath(queryGraph, 2, 5, nodes(2, x, 3, y, 4, 5)); + // interestingly this used to go 4-5 instead of 4-z-5 bc we use dist_earth in osmreader and dist_plane in query overlay + assertPath(queryGraph, 3, 5, nodes(3, y, 4, z, 5)); + assertPath(queryGraph, 3, 6, nodes(3, y, 4, z, 5, 6)); + assertPath(queryGraph, 4, 6, nodes(4, z, 5, 6)); + // turning between the virtual nodes is still possible + assertPath(queryGraph, x, y, nodes(x, 3, y)); + assertPath(queryGraph, y, x, nodes(y, 3, x)); + assertPath(queryGraph, y, z, nodes(y, 4, z)); + assertPath(queryGraph, z, y, nodes(z, 4, y)); + + assertEquals(20, graph.getTurnCostStorage().getTurnCostsCount()); + } + + private RestrictionSetter.Restriction createViaNodeRestriction(int fromEdge, int viaNode, int toEdge) { + return RestrictionSetter.createViaNodeRestriction(fromEdge, viaNode, toEdge); + } + + private RestrictionSetter.Restriction createViaEdgeRestriction(int... edges) { + return RestrictionSetter.createViaEdgeRestriction(IntArrayList.from(edges)); + } + + /** + * Shorthand version that only sets restriction for the first turn restriction encoded value + */ + private void setRestrictions(RestrictionSetter.Restriction... restrictions) { + setRestrictions(List.of(restrictions), Stream.of(restrictions).map(r -> encBits(1, 0)).toList()); + } + + private void setRestrictions(List restrictions, List encBits) { + r.setRestrictions(restrictions, encBits); + } + + /** + * Shorthand version that asserts the path for the first turn restriction encoded value + */ + private void assertPath(int from, int to, IntArrayList expectedNodes) { + assertPath(graph, from, to, turnRestrictionEnc, expectedNodes); + } + + private void assertPath(int from, int to, BooleanEncodedValue turnRestrictionEnc, IntArrayList expectedNodes) { + assertPath(graph, from, to, turnRestrictionEnc, expectedNodes); + } + + /** + * Shorthand version that asserts the path for the first turn restriction encoded value + */ + private void assertPath(Graph graph, int from, int to, IntArrayList expectedNodes) { + assertPath(graph, from, to, turnRestrictionEnc, expectedNodes); + } + + private void assertPath(Graph graph, int from, int to, BooleanEncodedValue turnRestrictionEnc, IntArrayList expectedNodes) { + Path path = calcPath(graph, from, to, turnRestrictionEnc); + if (expectedNodes == null) + assertFalse(path.isFound(), "Did not expect to find a path, but found: " + path.calcNodes() + ", edges: " + path.calcEdges()); + else { + assertTrue(path.isFound(), "Expected path: " + expectedNodes + ", but did not find it"); + IntIndexedContainer nodes = path.calcNodes(); + assertEquals(expectedNodes, nodes); + } + } + + private Path calcPath(Graph graph, int from, int to, BooleanEncodedValue turnRestrictionEnc) { + Dijkstra dijkstra = new Dijkstra(graph, graph.wrapWeighting(new SpeedWeighting(speedEnc, new TurnCostProvider() { @Override public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { if (inEdge == outEdge) return Double.POSITIVE_INFINITY; @@ -348,14 +944,29 @@ public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { return Double.isInfinite(calcTurnWeight(inEdge, viaNode, outEdge)) ? Long.MAX_VALUE : 0L; } - }), TraversalMode.EDGE_BASED).calcPath(from, to).calcNodes()); + })), TraversalMode.EDGE_BASED); + return dijkstra.calcPath(from, to); } private IntArrayList nodes(int... nodes) { return IntArrayList.from(nodes); } + private BitSet encBits(int... bits) { + BitSet b = new BitSet(bits.length); + for (int i = 0; i < bits.length; i++) { + if (bits[i] != 0 && bits[i] != 1) + throw new IllegalArgumentException("bits must be 0 or 1"); + if (bits[i] > 0) b.set(i); + } + return b; + } + private int edge(int from, int to) { - return graph.edge(from, to).setDistance(100).set(speedEnc, 10, 10).getEdge(); + return edge(from, to, true); + } + + private int edge(int from, int to, boolean bothDir) { + return graph.edge(from, to).setDistance(100).set(speedEnc, 10, bothDir ? 10 : 0).getEdge(); } } diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java deleted file mode 100644 index da394ece971..00000000000 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/RoadsTagParserTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.graphhopper.routing.util.parsers; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.ArrayEdgeIntAccess; -import com.graphhopper.routing.ev.EdgeIntAccess; -import com.graphhopper.routing.ev.VehicleSpeed; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.VehicleEncodedValues; -import com.graphhopper.util.PMap; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -class RoadsTagParserTest { - - private final EncodingManager encodingManager = new EncodingManager.Builder().add(VehicleEncodedValues.roads(new PMap())).build(); - private final RoadsAverageSpeedParser parser; - - public RoadsTagParserTest() { - parser = new RoadsAverageSpeedParser(encodingManager); - } - - @Test - public void testSpeed() { - ReaderWay way = new ReaderWay(1); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(encodingManager.getIntsForFlags()); - int edgeId = 0; - parser.handleWayTags(edgeId, edgeIntAccess, way, null); - assertTrue(encodingManager.getDecimalEncodedValue(VehicleSpeed.key("roads")).getDecimal(false, edgeId, edgeIntAccess) > 200); - } - -} diff --git a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java index 45b7087ea84..8c6453a6ae4 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/TagParsingTest.java @@ -20,7 +20,6 @@ import com.graphhopper.reader.ReaderRelation; import com.graphhopper.reader.ReaderWay; -import com.graphhopper.reader.osm.conditional.DateRangeParser; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.OSMParsers; @@ -36,93 +35,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; class TagParsingTest { - @Test - public void testCombineRelations() { - ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "track"); - ReaderRelation osmRel = new ReaderRelation(1); - - BooleanEncodedValue bike1AccessEnc = VehicleAccess.create("bike1"); - DecimalEncodedValue bike1SpeedEnc = VehicleSpeed.create("bike1", 4, 2, false); - DecimalEncodedValue bike1PriorityEnc = VehiclePriority.create("bike1", 4, PriorityCode.getFactor(1), false); - BooleanEncodedValue bike2AccessEnc = VehicleAccess.create("bike2"); - DecimalEncodedValue bike2SpeedEnc = VehicleSpeed.create("bike2", 4, 2, false); - DecimalEncodedValue bike2PriorityEnc = VehiclePriority.create("bike2", 4, PriorityCode.getFactor(1), false); - EnumEncodedValue bikeNetworkEnc = RouteNetwork.create(BikeNetwork.KEY); - EncodingManager em = EncodingManager.start() - .add(bike1AccessEnc).add(bike1SpeedEnc).add(bike1PriorityEnc) - .add(bike2AccessEnc).add(bike2SpeedEnc).add(bike2PriorityEnc) - .add(bikeNetworkEnc) - .add(Smoothness.create()) - .build(); - BikePriorityParser bike1Parser = new BikePriorityParser(bike1PriorityEnc, bike1SpeedEnc, bikeNetworkEnc); - BikePriorityParser bike2Parser = new BikePriorityParser(bike2PriorityEnc, bike2SpeedEnc, bikeNetworkEnc) { - @Override - public void handleWayTags(int edgeId, EdgeIntAccess intAccess, ReaderWay way, IntsRef relTags) { - // accept less relations - if (bikeRouteEnc.getEnum(false, edgeId, intAccess) != RouteNetwork.MISSING) - priorityEnc.setDecimal(false, edgeId, intAccess, PriorityCode.getFactor(2)); - } - }; - OSMParsers osmParsers = new OSMParsers() - .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(bikeNetworkEnc, relConfig)) - .addWayTagParser(new OSMRoadClassParser(em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class))) - .addWayTagParser(bike1Parser) - .addWayTagParser(bike2Parser); - - // relation code is PREFER - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "lcn"); - IntsRef relFlags = osmParsers.createRelationFlags(); - relFlags = osmParsers.handleRelationTags(osmRel, relFlags); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - int edgeId = 0; - osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); - assertEquals(RouteNetwork.LOCAL, bikeNetworkEnc.getEnum(false, edgeId, edgeIntAccess)); - assertTrue(bike1PriorityEnc.getDecimal(false, edgeId, edgeIntAccess) > bike2PriorityEnc.getDecimal(false, edgeId, edgeIntAccess)); - } - - @Test - public void testMixBikeTypesAndRelationCombination() { - ReaderWay osmWay = new ReaderWay(1); - osmWay.setTag("highway", "track"); - osmWay.setTag("tracktype", "grade1"); - - ReaderRelation osmRel = new ReaderRelation(1); - - BooleanEncodedValue bikeAccessEnc = VehicleAccess.create("bike"); - DecimalEncodedValue bikeSpeedEnc = VehicleSpeed.create("bike", 4, 2, false); - DecimalEncodedValue bikePriorityEnc = VehiclePriority.create("bike", 4, PriorityCode.getFactor(1), false); - BooleanEncodedValue mtbAccessEnc = VehicleAccess.create("mtb"); - DecimalEncodedValue mtbSpeedEnc = VehicleSpeed.create("mtb", 4, 2, false); - DecimalEncodedValue mtbPriorityEnc = VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false); - EnumEncodedValue bikeNetworkEnc = RouteNetwork.create(BikeNetwork.KEY); - EncodingManager em = EncodingManager.start() - .add(bikeAccessEnc).add(bikeSpeedEnc).add(bikePriorityEnc) - .add(mtbAccessEnc).add(mtbSpeedEnc).add(mtbPriorityEnc) - .add(bikeNetworkEnc) - .add(Smoothness.create()) - .build(); - BikePriorityParser bikeTagParser = new BikePriorityParser(em); - MountainBikePriorityParser mtbTagParser = new MountainBikePriorityParser(em); - OSMParsers osmParsers = new OSMParsers() - .addRelationTagParser(relConfig -> new OSMBikeNetworkTagParser(bikeNetworkEnc, relConfig)) - .addWayTagParser(new OSMRoadClassParser(em.getEnumEncodedValue(RoadClass.KEY, RoadClass.class))) - .addWayTagParser(bikeTagParser) - .addWayTagParser(mtbTagParser); - - // relation code for network rcn is NICE for bike and PREFER for mountainbike - osmRel.setTag("route", "bicycle"); - osmRel.setTag("network", "rcn"); - IntsRef relFlags = osmParsers.createRelationFlags(); - relFlags = osmParsers.handleRelationTags(osmRel, relFlags); - EdgeIntAccess edgeIntAccess = new ArrayEdgeIntAccess(em.getIntsForFlags()); - int edgeId = 0; - osmParsers.handleWayTags(edgeId, edgeIntAccess, osmWay, relFlags); - // bike: uninfluenced speed for grade but via network => NICE - // mtb: uninfluenced speed only PREFER - assertTrue(bikePriorityEnc.getDecimal(false, edgeId, edgeIntAccess) > mtbPriorityEnc.getDecimal(false, edgeId, edgeIntAccess)); - } @Test public void testSharedEncodedValues() { @@ -138,6 +50,7 @@ public void testSharedEncodedValues() { .add(mtbAccessEnc).add(VehicleSpeed.create("mtb", 4, 2, false)).add(VehiclePriority.create("mtb", 4, PriorityCode.getFactor(1), false)) .add(RouteNetwork.create(FootNetwork.KEY)) .add(RouteNetwork.create(BikeNetwork.KEY)) + .add(Roundabout.create()) .add(Smoothness.create()) .build(); @@ -149,11 +62,8 @@ public void testSharedEncodedValues() { new BikeAccessParser(manager, new PMap()), new MountainBikeAccessParser(manager, new PMap()) ); - for (TagParser tagParser : tagParsers) - if (tagParser instanceof AbstractAccessParser) - ((AbstractAccessParser) tagParser).init(new DateRangeParser()); - final ArrayEdgeIntAccess intAccess = new ArrayEdgeIntAccess(manager.getIntsForFlags()); + final ArrayEdgeIntAccess intAccess = ArrayEdgeIntAccess.createFromBytes(manager.getBytesForFlags()); int edgeId = 0; IntsRef relFlags = manager.createRelationFlags(); ReaderWay way = new ReaderWay(1); @@ -165,7 +75,6 @@ public void testSharedEncodedValues() { for (BooleanEncodedValue accessEnc : accessEncs) assertTrue(accessEnc.getBool(false, edgeId, intAccess)); - final IntsRef edgeFlags2 = manager.createEdgeFlags(); way.clearTags(); way.setTag("highway", "tertiary"); way.setTag("junction", "circular"); diff --git a/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java deleted file mode 100644 index 7a0b72a3f68..00000000000 --- a/core/src/test/java/com/graphhopper/routing/weighting/FastestWeightingTest.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.routing.weighting; - -import com.graphhopper.reader.ReaderWay; -import com.graphhopper.routing.ev.*; -import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Graph; -import com.graphhopper.util.*; -import com.graphhopper.util.Parameters.Routing; -import org.junit.jupiter.api.Test; - -import static com.graphhopper.routing.weighting.FastestWeighting.DESTINATION_FACTOR; -import static com.graphhopper.routing.weighting.FastestWeighting.PRIVATE_FACTOR; -import static com.graphhopper.util.GHUtility.getEdge; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Peter Karich - */ -public class FastestWeightingTest { - private final BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); - private final DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - private final BooleanEncodedValue turnRestrictionEnc = TurnRestriction.create("car"); - private final EncodingManager encodingManager = EncodingManager.start().add(accessEnc).add(speedEnc).addTurnCostEncodedValue(turnRestrictionEnc).build(); - private final BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); - - @Test - public void testMinWeightHasSameUnitAs_getWeight() { - EdgeIteratorState edge = graph.edge(0, 1).setDistance(10); - GHUtility.setSpeed(140, 0, accessEnc, speedEnc, edge); - Weighting instance = new FastestWeighting(accessEnc, speedEnc); - assertEquals(instance.calcMinWeightPerDistance() * 10, instance.calcEdgeWeight(edge, false), 1e-8); - } - - @Test - public void testWeightWrongHeading() { - final double penalty = 100; - Weighting instance = new FastestWeighting(accessEnc, speedEnc, null, new PMap().putObject(Parameters.Routing.HEADING_PENALTY, penalty), TurnCostProvider.NO_TURN_COST_PROVIDER); - EdgeIteratorState edge = graph.edge(1, 2).setDistance(10).setWayGeometry(Helper.createPointList(51, 0, 51, 1)); - GHUtility.setSpeed(10, 10, accessEnc, speedEnc, edge); - VirtualEdgeIteratorState virtEdge = new VirtualEdgeIteratorState(edge.getEdgeKey(), 99, 5, 6, edge.getDistance(), edge.getFlags(), - edge.getKeyValues(), edge.fetchWayGeometry(FetchMode.PILLAR_ONLY), false); - double time = instance.calcEdgeWeight(virtEdge, false); - - // no penalty on edge - assertEquals(time, instance.calcEdgeWeight(virtEdge, false), 1e-8); - assertEquals(time, instance.calcEdgeWeight(virtEdge, true), 1e-8); - // ... unless setting it to unfavored (in both directions) - virtEdge.setUnfavored(true); - assertEquals(time + penalty, instance.calcEdgeWeight(virtEdge, false), 1e-8); - assertEquals(time + penalty, instance.calcEdgeWeight(virtEdge, true), 1e-8); - // but not after releasing it - virtEdge.setUnfavored(false); - assertEquals(time, instance.calcEdgeWeight(virtEdge, false), 1e-8); - assertEquals(time, instance.calcEdgeWeight(virtEdge, true), 1e-8); - - // test default penalty - virtEdge.setUnfavored(true); - instance = new FastestWeighting(accessEnc, speedEnc); - assertEquals(time + Routing.DEFAULT_HEADING_PENALTY, instance.calcEdgeWeight(virtEdge, false), 1e-8); - assertEquals(time + Routing.DEFAULT_HEADING_PENALTY, instance.calcEdgeWeight(virtEdge, true), 1e-8); - } - - @Test - public void testSpeed0() { - EdgeIteratorState edge = graph.edge(0, 1).setDistance(10); - Weighting instance = new FastestWeighting(accessEnc, speedEnc); - edge.set(speedEnc, 0); - assertTrue(Double.isInfinite(instance.calcEdgeWeight(edge, false))); - - // 0 / 0 returns NaN but calcWeight should not return NaN! - edge.setDistance(0); - assertTrue(Double.isInfinite(instance.calcEdgeWeight(edge, false))); - } - - @Test - public void testTime() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); - DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); - BaseGraph g = new BaseGraph.Builder(em).create(); - Weighting w = new FastestWeighting(accessEnc, speedEnc); - EdgeIteratorState edge = g.edge(0, 1).setDistance(100_000); - GHUtility.setSpeed(15, 10, accessEnc, speedEnc, edge); - assertEquals(375 * 60 * 1000, w.calcEdgeMillis(edge, false)); - assertEquals(600 * 60 * 1000, w.calcEdgeMillis(edge, true)); - } - - @Test - public void calcWeightAndTime_withTurnCosts() { - BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = new FastestWeighting(accessEnc, speedEnc, new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage())); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(100)); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 2).setDistance(100)); - setTurnRestriction(graph, 0, 1, 2); - assertTrue(Double.isInfinite(GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0))); - assertEquals(Long.MAX_VALUE, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0)); - } - - @Test - public void calcWeightAndTime_uTurnCosts() { - BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = new FastestWeighting(accessEnc, speedEnc, new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage(), 40)); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(100)); - assertEquals(6 + 40, GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0), 1.e-6); - assertEquals((6 + 40) * 1000, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0), 1.e-6); - } - - @Test - public void calcWeightAndTime_withTurnCosts_shortest() { - BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = new ShortestWeighting(accessEnc, speedEnc, - new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage())); - GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(0, 1).setDistance(100)); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, speedEnc, graph.edge(1, 2).setDistance(100)); - setTurnRestriction(graph, 0, 1, 2); - assertTrue(Double.isInfinite(GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0))); - assertEquals(Long.MAX_VALUE, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0)); - } - - @Test - public void testDestinationTag() { - BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("car_access", true); - DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false); - BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true); - DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false); - EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build(); - BaseGraph graph = new BaseGraph.Builder(em).create(); - EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000); - edge.set(carAccessEnc, true, true); - edge.set(bikeAccessEnc, true, true); - edge.set(carSpeedEnc, 60); - edge.set(bikeSpeedEnc, 18); - EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); - - FastestWeighting weighting = new FastestWeighting(carAccessEnc, carSpeedEnc, roadAccessEnc, - new PMap().putObject(DESTINATION_FACTOR, 10), TurnCostProvider.NO_TURN_COST_PROVIDER); - FastestWeighting bikeWeighting = new FastestWeighting(bikeAccessEnc, bikeSpeedEnc, roadAccessEnc, - new PMap().putObject(DESTINATION_FACTOR, 1), TurnCostProvider.NO_TURN_COST_PROVIDER); - - edge.set(roadAccessEnc, RoadAccess.YES); - assertEquals(60, weighting.calcEdgeWeight(edge, false), 1.e-6); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), 1.e-6); - - // the destination tag does not change the weight for the bike weighting - edge.set(roadAccessEnc, RoadAccess.DESTINATION); - assertEquals(600, weighting.calcEdgeWeight(edge, false), 0.1); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), 0.1); - } - - @Test - public void testPrivateTag() { - BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("car_access", true); - DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false); - BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true); - DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false); - EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build(); - BaseGraph graph = new BaseGraph.Builder(em).create(); - EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000); - edge.set(carAccessEnc, true, true); - edge.set(bikeAccessEnc, true, true); - edge.set(carSpeedEnc, 60); - edge.set(bikeSpeedEnc, 18); - EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); - - FastestWeighting weighting = new FastestWeighting(carAccessEnc, carSpeedEnc, roadAccessEnc, - new PMap().putObject(PRIVATE_FACTOR, 10), TurnCostProvider.NO_TURN_COST_PROVIDER); - FastestWeighting bikeWeighting = new FastestWeighting(bikeAccessEnc, bikeSpeedEnc, roadAccessEnc, - new PMap().putObject(PRIVATE_FACTOR, 1.2), TurnCostProvider.NO_TURN_COST_PROVIDER); - - ReaderWay way = new ReaderWay(1); - way.setTag("highway", "secondary"); - - edge.set(roadAccessEnc, RoadAccess.YES); - assertEquals(60, weighting.calcEdgeWeight(edge, false), 1.e-6); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), 1.e-6); - - edge.set(roadAccessEnc, RoadAccess.PRIVATE); - assertEquals(600, weighting.calcEdgeWeight(edge, false), 1.e-6); - // private should influence bike only slightly - assertEquals(240, bikeWeighting.calcEdgeWeight(edge, false), 1.e-6); - } - - private void setTurnRestriction(Graph graph, int from, int via, int to) { - graph.getTurnCostStorage().set(turnRestrictionEnc, getEdge(graph, from, via).getEdge(), via, getEdge(graph, via, to).getEdge(), true); - } - -} diff --git a/core/src/test/java/com/graphhopper/routing/weighting/AbstractWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/WeightingTest.java similarity index 76% rename from core/src/test/java/com/graphhopper/routing/weighting/AbstractWeightingTest.java rename to core/src/test/java/com/graphhopper/routing/weighting/WeightingTest.java index 045d717fa6e..de3bb190ce3 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/AbstractWeightingTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/WeightingTest.java @@ -25,13 +25,13 @@ /** * @author Peter Karich */ -public class AbstractWeightingTest { +public class WeightingTest { @Test public void testToString() { - assertTrue(AbstractWeighting.isValidName("blup")); - assertTrue(AbstractWeighting.isValidName("blup_a")); - assertTrue(AbstractWeighting.isValidName("blup|a")); - assertFalse(AbstractWeighting.isValidName("Blup")); - assertFalse(AbstractWeighting.isValidName("Blup!")); + assertTrue(Weighting.isValidName("blup")); + assertTrue(Weighting.isValidName("blup_a")); + assertTrue(Weighting.isValidName("blup|a")); + assertFalse(Weighting.isValidName("Blup")); + assertFalse(Weighting.isValidName("Blup!")); } } diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java index 8b65fc4ca11..cab19e50036 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/ConditionalExpressionVisitorTest.java @@ -120,6 +120,13 @@ public void isValidAndSimpleCondition() { assertEquals("[backward_my_speed]", result.guessedVariables.toString()); } + @Test + public void testAbs() { + ParseResult result = parse("Math.abs(average_slope) < -0.5", "average_slope"::equals, k -> ""); + assertTrue(result.ok); + assertEquals("[average_slope]", result.guessedVariables.toString()); + } + @Test public void testNegativeConstant() { ParseResult result = parse("average_slope < -0.5", "average_slope"::equals, k -> ""); diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java index 0b927fa7d0f..f7a81f02aa2 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java @@ -20,6 +20,7 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.search.KVStorage; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.CustomModel; import com.graphhopper.util.EdgeIteratorState; @@ -30,14 +31,14 @@ import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.GeometryFactory; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; +import java.util.*; + +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static com.graphhopper.json.Statement.*; -import static com.graphhopper.json.Statement.Op.LIMIT; -import static com.graphhopper.json.Statement.Op.MULTIPLY; +import static com.graphhopper.json.Statement.Op.*; import static com.graphhopper.routing.ev.RoadClass.*; +import static com.graphhopper.routing.weighting.custom.CustomModelParser.findVariablesForEncodedValuesString; import static com.graphhopper.routing.weighting.custom.CustomModelParser.parseExpressions; import static org.junit.jupiter.api.Assertions.*; @@ -62,7 +63,7 @@ void setup() { countryEnc = Country.create(); stateEnc = State.create(); encodingManager = new EncodingManager.Builder().add(accessEnc).add(avgSpeedEnc).add(new EnumEncodedValue<>("bus", MyBus.class)) - .add(stateEnc).add(countryEnc).add(MaxSpeed.create()).add(Surface.create()).build(); + .add(stateEnc).add(countryEnc).add(MaxSpeed.create()).add(Surface.create()).add(RoadClass.create()).add(RoadEnvironment.create()).build(); graph = new BaseGraph.Builder(encodingManager).create(); roadClassEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); maxSpeed = 140; @@ -72,8 +73,8 @@ void setup() { void setPriorityForRoadClass() { CustomModel customModel = new CustomModel(); customModel.addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.5")); - CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager).getEdgeToPriorityMapping(); BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(roadClassEnc, RoadClass.PRIMARY); @@ -97,9 +98,8 @@ void testPriority() { customModel.addToPriority(ElseIf("road_class == SECONDARY", MULTIPLY, "0.7")); customModel.addToPriority(Else(MULTIPLY, "0.9")); customModel.addToPriority(If("road_environment != FERRY", MULTIPLY, "0.8")); - - CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager).getEdgeToPriorityMapping(); assertEquals(0.5 * 0.8, priorityMapping.get(primary, false), 0.01); assertEquals(0.7 * 0.8, priorityMapping.get(secondary, false), 0.01); @@ -109,8 +109,8 @@ void testPriority() { customModel = new CustomModel(); customModel.addToPriority(If("road_class == PRIMARY", MULTIPLY, "1")); customModel.addToPriority(If("road_class == SECONDARY", MULTIPLY, "0.9")); - priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager).getEdgeToPriorityMapping(); assertEquals(1, priorityMapping.get(primary, false), 0.01); assertEquals(0.9, priorityMapping.get(secondary, false), 0.01); } @@ -131,9 +131,8 @@ public void testCountry() { customModel.addToPriority(If("country == USA", MULTIPLY, "0.5")); customModel.addToPriority(If("country == USA && state == US_AK", MULTIPLY, "0.6")); customModel.addToPriority(If("country == DEU", MULTIPLY, "0.8")); - - CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager).getEdgeToPriorityMapping(); assertEquals(0.6 * 0.5, priorityMapping.get(usRoad, false), 0.01); assertEquals(0.5, priorityMapping.get(us2Road, false), 0.01); @@ -149,8 +148,8 @@ public void testBrackets() { CustomModel customModel = new CustomModel(); customModel.addToPriority(If("(road_class == PRIMARY || car_access == true) && car_average_speed > 50", MULTIPLY, "0.9")); - CustomWeighting.Parameters parameters = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null); + customModel.addToSpeed(If("true", LIMIT, "100")); + CustomWeighting.Parameters parameters = CustomModelParser.createWeightingParameters(customModel, encodingManager); assertEquals(0.9, parameters.getEdgeToPriorityMapping().get(primary, false), 0.01); assertEquals(1, parameters.getEdgeToPriorityMapping().get(secondary, false), 0.01); } @@ -164,9 +163,9 @@ public void testSpeedFactorAndPriorityAndMaxSpeed() { CustomModel customModel = new CustomModel(); customModel.addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.9")); + customModel.addToSpeed(If("true", LIMIT, avgSpeedEnc.getName())); customModel.addToSpeed(If("road_class == PRIMARY", MULTIPLY, "0.8")); - CustomWeighting.Parameters parameters = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null); + CustomWeighting.Parameters parameters = CustomModelParser.createWeightingParameters(customModel, encodingManager); assertEquals(0.9, parameters.getEdgeToPriorityMapping().get(primary, false), 0.01); assertEquals(64, parameters.getEdgeToSpeedMapping().get(primary, false), 0.01); @@ -174,8 +173,7 @@ public void testSpeedFactorAndPriorityAndMaxSpeed() { assertEquals(70, parameters.getEdgeToSpeedMapping().get(secondary, false), 0.01); customModel.addToSpeed(If("road_class != PRIMARY", LIMIT, "50")); - CustomWeighting.EdgeToDoubleMapping speedMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToSpeedMapping(); + CustomWeighting.EdgeToDoubleMapping speedMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager).getEdgeToSpeedMapping(); assertEquals(64, speedMapping.get(primary, false), 0.01); assertEquals(50, speedMapping.get(secondary, false), 0.01); } @@ -185,14 +183,12 @@ void testIllegalOrder() { CustomModel customModel = new CustomModel(); customModel.addToPriority(Else(MULTIPLY, "0.9")); customModel.addToPriority(If("road_environment != FERRY", MULTIPLY, "0.8")); - assertThrows(IllegalArgumentException.class, () -> CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null)); + assertThrows(IllegalArgumentException.class, () -> CustomModelParser.createWeightingParameters(customModel, encodingManager)); CustomModel customModel2 = new CustomModel(); customModel2.addToPriority(ElseIf("road_environment != FERRY", MULTIPLY, "0.9")); customModel2.addToPriority(If("road_class != PRIMARY", MULTIPLY, "0.8")); - assertThrows(IllegalArgumentException.class, () -> CustomModelParser.createWeightingParameters(customModel2, encodingManager, - avgSpeedEnc, null)); + assertThrows(IllegalArgumentException.class, () -> CustomModelParser.createWeightingParameters(customModel2, encodingManager)); } @Test @@ -225,26 +221,24 @@ public void multipleAreas() { new HashMap<>())); customModel.setAreas(areas); + customModel.addToSpeed(If("true", LIMIT, avgSpeedEnc.getName())); customModel.addToSpeed(If("in_area_1", LIMIT, "100")); customModel.addToSpeed(If("!in_area_2", LIMIT, "25")); customModel.addToSpeed(Else(LIMIT, "15")); // No exception is thrown during createWeightingParameters - assertAll(() -> - CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null)); + assertAll(() -> CustomModelParser.createWeightingParameters(customModel, encodingManager)); CustomModel customModel2 = new CustomModel(); customModel2.setAreas(areas); + customModel2.addToSpeed(If("true", LIMIT, avgSpeedEnc.getName())); customModel2.addToSpeed(If("in_area_1", LIMIT, "100")); customModel2.addToSpeed(If("in_area_2", LIMIT, "25")); customModel2.addToSpeed(If("in_area_3", LIMIT, "150")); customModel2.addToSpeed(Else(LIMIT, "15")); - assertThrows(IllegalArgumentException.class, () -> - CustomModelParser.createWeightingParameters(customModel2, encodingManager, - avgSpeedEnc, null)); + assertThrows(IllegalArgumentException.class, () -> CustomModelParser.createWeightingParameters(customModel2, encodingManager)); } @Test @@ -256,29 +250,46 @@ public void parseValue() { set(maxSpeedEnc, 70).set(avgSpeedEnc, 70).set(accessEnc, true, true); CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, avgSpeedEnc.getName())); customModel.addToSpeed(If("true", LIMIT, "max_speed * 1.1")); - CustomWeighting.EdgeToDoubleMapping speedMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToSpeedMapping(); + CustomWeighting.EdgeToDoubleMapping speedMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager). + getEdgeToSpeedMapping(); assertEquals(70.0, speedMapping.get(maxSame, false), 0.01); assertEquals(66.0, speedMapping.get(maxLower, false), 0.01); } + @Test + public void parseBlock() { + DecimalEncodedValue maxSpeedEnc = encodingManager.getDecimalEncodedValue(MaxSpeed.KEY); + EdgeIteratorState edge60 = graph.edge(0, 1).setDistance(10). + set(maxSpeedEnc, 60).set(avgSpeedEnc, 70).set(accessEnc, true, true); + EdgeIteratorState edge70 = graph.edge(1, 2).setDistance(10). + set(maxSpeedEnc, 70).set(avgSpeedEnc, 70).set(accessEnc, true, true); + + CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, "200")); + customModel.addToSpeed(If("max_speed > 65", List.of(If("true", LIMIT, "65")))); + CustomWeighting.EdgeToDoubleMapping speedMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager). + getEdgeToSpeedMapping(); + assertEquals(65.0, speedMapping.get(edge70, false), 0.01); + assertEquals(200.0, speedMapping.get(edge60, false), 0.01); + } + @Test public void parseValueWithError() { CustomModel customModel1 = new CustomModel(); customModel1.addToSpeed(If("true", LIMIT, "unknown")); IllegalArgumentException ret = assertThrows(IllegalArgumentException.class, - () -> CustomModelParser.createWeightingParameters(customModel1, encodingManager, - avgSpeedEnc, null)); - assertTrue(ret.getMessage().startsWith("Cannot compile expression: 'unknown' not available"), ret.getMessage()); + () -> CustomModelParser.createWeightingParameters(customModel1, encodingManager)); + assertEquals("Cannot compile expression: 'unknown' not available", ret.getMessage()); CustomModel customModel3 = new CustomModel(); + customModel3.addToSpeed(If("true", LIMIT, avgSpeedEnc.getName())); customModel3.addToSpeed(If("road_class == PRIMARY", MULTIPLY, "0.5")); customModel3.addToSpeed(Else(MULTIPLY, "road_class")); ret = assertThrows(IllegalArgumentException.class, - () -> CustomModelParser.createWeightingParameters(customModel3, encodingManager, - avgSpeedEnc, null)); + () -> CustomModelParser.createWeightingParameters(customModel3, encodingManager)); assertTrue(ret.getMessage().contains("Binary numeric promotion not possible on types \"double\" and \"com.graphhopper.routing.ev.RoadClass\""), ret.getMessage()); } @@ -290,33 +301,43 @@ public void parseConditionWithError() { IllegalArgumentException ret = assertThrows(IllegalArgumentException.class, () -> parseExpressions(new StringBuilder(), validVariable, "[HERE]", new HashSet<>(), - Arrays.asList(If("max_weight > 10", MULTIPLY, "0")), - key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName())); + Arrays.asList(If("max_weight > 10", MULTIPLY, "0")), s -> "", "") + ); assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"max_weight > 10\": 'max_weight' not available"), ret.getMessage()); // invalid variable or constant (NameValidator returns false) ret = assertThrows(IllegalArgumentException.class, () -> parseExpressions(new StringBuilder(), validVariable, "[HERE]", new HashSet<>(), - Arrays.asList(If("country == GERMANY", MULTIPLY, "0")), - key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName())); + Arrays.asList(If("country == GERMANY", MULTIPLY, "0")), s -> "", "")); assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"country == GERMANY\": 'GERMANY' not available"), ret.getMessage()); // not whitelisted method ret = assertThrows(IllegalArgumentException.class, () -> parseExpressions(new StringBuilder(), validVariable, "[HERE]", new HashSet<>(), - Arrays.asList(If("edge.fetchWayGeometry().size() > 2", MULTIPLY, "0")), - key -> encodingManager.getEncodedValue(key, EncodedValue.class).getName())); + Arrays.asList(If("edge.fetchWayGeometry().size() > 2", MULTIPLY, "0")), s -> "", "")); assertTrue(ret.getMessage().startsWith("[HERE] invalid condition \"edge.fetchWayGeometry().size() > 2\": size is an illegal method"), ret.getMessage()); } + @Test + public void testStatements() { + CustomModel customModel = new CustomModel(); + customModel.addToPriority(If("true", MULTIPLY, "0.5")); + customModel.addToPriority(Else(LIMIT, "0.7")); + customModel.addToSpeed(If("true", LIMIT, "100")); + Exception ret = assertThrows(IllegalArgumentException.class, + () -> CustomModelParser.createWeightingParameters(customModel, encodingManager)); + assertTrue(ret.getMessage().contains("Only one statement allowed for an unconditional statement"), ret.getMessage()); + } + @Test void testBackwardFunction() { CustomModel customModel = new CustomModel(); customModel.addToPriority(If("backward_car_access != car_access", MULTIPLY, "0.5")); - CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager). + getEdgeToPriorityMapping(); BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(accessEnc, true, false); @@ -327,17 +348,45 @@ void testBackwardFunction() { } @Test - void testExternalEV() { + void testTurnPenalty() { CustomModel customModel = new CustomModel(); - customModel.addToPriority(If("bus == NO", MULTIPLY, "0.5")); - CustomWeighting.EdgeToDoubleMapping priorityMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager, - avgSpeedEnc, null).getEdgeToPriorityMapping(); + customModel.addToSpeed(If("true", LIMIT, "100")); + customModel.addToTurnPenalty(If("prev_road_class != PRIMARY && road_class == PRIMARY", ADD, "100")); + CustomWeighting.TurnPenaltyMapping turnPenaltyMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager). + getTurnPenaltyMapping(); BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); - EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(encodingManager.getEnumEncodedValue("bus", MyBus.class), MyBus.NO); - EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(100); + EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).set(roadClassEnc, SECONDARY); + EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(100).set(roadClassEnc, PRIMARY); + EdgeIteratorState edge3 = graph.edge(2, 3).setDistance(100).set(roadClassEnc, PRIMARY); - assertEquals(0.5, priorityMapping.get(edge1, false), 1.e-6); - assertEquals(1.0, priorityMapping.get(edge2, false), 1.e-6); + assertEquals(100, turnPenaltyMapping.get(graph, graph.getEdgeAccess(), edge1.getEdge(), 1, edge2.getEdge())); + assertEquals(0, turnPenaltyMapping.get(graph, graph.getEdgeAccess(), edge2.getEdge(), 2, edge3.getEdge())); + } + + @Test + void testStreetNameTurnPenalty() { + CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, "100")); + customModel.addToTurnPenalty(If("prev_street_name.equals(street_name)", ADD, "100")); + CustomWeighting.TurnPenaltyMapping turnPenaltyMapping = CustomModelParser.createWeightingParameters(customModel, encodingManager). + getTurnPenaltyMapping(); + + BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); + EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(100).setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue("Main St"))); + EdgeIteratorState edge2 = graph.edge(1, 2).setDistance(100).setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue("Main St"))); + EdgeIteratorState edge3 = graph.edge(2, 3).setDistance(100).setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue("Oak Ave"))); + + assertEquals(100, turnPenaltyMapping.get(graph, graph.getEdgeAccess(), edge1.getEdge(), 1, edge2.getEdge())); + assertEquals(0, turnPenaltyMapping.get(graph, graph.getEdgeAccess(), edge2.getEdge(), 2, edge3.getEdge())); + } + + @Test + public void findVariablesForEncodedValueString() { + CustomModel customModel = new CustomModel(); + customModel.addToPriority(If("!foot_access && (hike_rating < 4 || road_access == PRIVATE)", MULTIPLY, "0")); + //, {"if": "true", "multiply_by": foot_priority}, {"if": "foot_network == INTERNATIONAL || foot_network == NATIONAL", "multiply_by": 1.7}, {"else_if": "foot_network == REGIONAL || foot_network == LOCAL", "multiply_by": 1.5}]|areas=[]|turnCostsConfig=transportationMode=null, restrictions=false, uTurnCosts=-1 + List variables = findVariablesForEncodedValuesString(customModel, s -> new DefaultImportRegistry().createImportUnit(s) != null, s -> ""); + assertEquals(List.of("foot_access", "hike_rating", "road_access"), variables); } } diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java index e26edc6141b..9f421280609 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingHelperTest.java @@ -1,17 +1,22 @@ package com.graphhopper.routing.weighting.custom; -import com.graphhopper.json.MinMax; -import com.graphhopper.routing.ev.VehicleSpeed; +import com.graphhopper.config.Profile; +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.DefaultWeightingFactory; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.VehicleEncodedValues; +import com.graphhopper.routing.util.parsers.OrientationCalculator; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.*; import com.graphhopper.util.shapes.Polygon; import org.junit.jupiter.api.Test; -import static com.graphhopper.json.Statement.Else; -import static com.graphhopper.json.Statement.If; -import static com.graphhopper.json.Statement.Op.MULTIPLY; +import java.util.Arrays; +import java.util.List; + +import static com.graphhopper.json.Statement.*; +import static com.graphhopper.json.Statement.Op.*; import static org.junit.jupiter.api.Assertions.*; class CustomWeightingHelperTest { @@ -55,14 +60,134 @@ public void testInRectangle() { @Test public void testNegativeMax() { CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, VehicleSpeed.key("car"))); customModel.addToSpeed(If("road_class == PRIMARY", MULTIPLY, "0.5")); customModel.addToSpeed(Else(MULTIPLY, "-0.5")); CustomWeightingHelper helper = new CustomWeightingHelper(); - EncodingManager lookup = new EncodingManager.Builder().add(VehicleEncodedValues.car(new PMap())).build(); - helper.init(customModel, lookup, lookup.getDecimalEncodedValue(VehicleSpeed.key("car")), null, null); - IllegalArgumentException ret = assertThrows(IllegalArgumentException.class, - helper::calcMaxSpeed); - assertEquals("speed has to be >=0 but can be negative (-0.5)" ,ret.getMessage()); + EncodingManager lookup = new EncodingManager.Builder().add(VehicleSpeed.create("car", 5, 5, true)).build(); + helper.init(customModel, lookup, null); + IllegalArgumentException ret = assertThrows(IllegalArgumentException.class, helper::calcMaxSpeed); + assertTrue(ret.getMessage().startsWith("statement resulted in negative value")); + } + + @Test + public void testCalcChangeAngle() { + EncodingManager encodingManager = new EncodingManager.Builder().add(Orientation.create()).build(); + DecimalEncodedValue orientationEnc = encodingManager.getDecimalEncodedValue(Orientation.KEY); + OrientationCalculator calc = new OrientationCalculator(orientationEnc); + BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); + graph.getNodeAccess().setNode(1, 0.030, 0.011); + graph.getNodeAccess().setNode(2, 0.020, 0.009); + graph.getNodeAccess().setNode(3, 0.010, 0.000); + graph.getNodeAccess().setNode(4, 0.000, 0.008); + + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); + // 1 + // | + // /--2 + // 3-/| + // 4 + EdgeIteratorState edge12 = handleWayTags(edgeIntAccess, calc, graph.edge(1, 2), List.of()); + EdgeIteratorState edge24 = handleWayTags(edgeIntAccess, calc, graph.edge(2, 4), List.of()); + EdgeIteratorState edge23 = handleWayTags(edgeIntAccess, calc, graph.edge(2, 3), Arrays.asList(0.020, 0.002)); + EdgeIteratorState edge23down = handleWayTags(edgeIntAccess, calc, graph.edge(2, 3), Arrays.asList(0.010, 0.005)); + + assertEquals(-12, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge12.getEdge(), 2, edge24.getEdge()), 1); + assertEquals(-12, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge23down.getEdge(), 2, edge12.getEdge()), 1); + + // left + assertEquals(-84, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge24.getEdge(), 2, edge23.getEdge()), 1); + assertEquals(-84, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge23.getEdge(), 2, edge12.getEdge()), 1); + + // right + assertEquals(96, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge23down.getEdge(), 3, edge23.getEdge()), 1); + assertEquals(84, calcChangeAngle(graph, edgeIntAccess, orientationEnc, edge12.getEdge(), 2, edge23.getEdge()), 1); + } + + public static double calcChangeAngle(BaseGraph graph, EdgeIntAccess edgeIntAccess, DecimalEncodedValue orientationEnc, + int inEdge, int viaNode, int outEdge) { + boolean inEdgeReverse = !graph.isAdjNode(inEdge, viaNode); + boolean outEdgeReverse = !graph.isAdjNode(outEdge, viaNode); + return CustomWeightingHelper.calcChangeAngle(edgeIntAccess, orientationEnc, inEdge, inEdgeReverse, outEdge, outEdgeReverse); + } + + @Test + public void testCalcTurnWeight() { + BooleanEncodedValue accessEnc = VehicleAccess.create("car"); + DecimalEncodedValue avgSpeedEnc = VehicleSpeed.create("car", 5, 5, true); + DecimalEncodedValue orientEnc = Orientation.create(); + EncodingManager em = new EncodingManager.Builder().add(accessEnc).add(avgSpeedEnc). + add(orientEnc).addTurnCostEncodedValue(TurnRestriction.create("car")).build(); + BaseGraph graph = new BaseGraph.Builder(em).withTurnCosts(true).create(); + + // 4 5 + // 0 - 1 - 2 + // 3 6 + + graph.getNodeAccess().setNode(0, 51.0362, 13.714); + graph.getNodeAccess().setNode(1, 51.0362, 13.720); + graph.getNodeAccess().setNode(2, 51.0362, 13.726); + graph.getNodeAccess().setNode(3, 51.0358, 13.7205); + graph.getNodeAccess().setNode(4, 51.0366, 13.720); + graph.getNodeAccess().setNode(5, 51.0366, 13.726); + graph.getNodeAccess().setNode(6, 51.0358, 13.726); + + CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, "100")); + customModel.addToTurnPenalty(If("change_angle > -25 && change_angle < 25", ADD, "0")); // straight + customModel.addToTurnPenalty(ElseIf("change_angle >= 25 && change_angle < 80", ADD, "0.5")); // right + customModel.addToTurnPenalty(ElseIf("change_angle >= 80 && change_angle <= 180", ADD, "1")); // sharp right + customModel.addToTurnPenalty(ElseIf("change_angle <= -25 && change_angle > -80", ADD, "6")); // left + customModel.addToTurnPenalty(ElseIf("change_angle <= -80 && change_angle >= -180", ADD, "12")); // sharp left + customModel.addToTurnPenalty(Else(ADD, "Infinity")); // uTurn + + Profile profile = new Profile("car"); + profile.setTurnCostsConfig(new TurnCostsConfig()); + profile.setCustomModel(customModel); + + Weighting weighting = new DefaultWeightingFactory(graph, em).createWeighting(profile, new PMap(), false); + OrientationCalculator calc = new OrientationCalculator(orientEnc); + EdgeIntAccess edgeIntAccess = graph.getEdgeAccess(); + EdgeIteratorState edge01 = handleWayTags(edgeIntAccess, calc, graph.edge(0, 1).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + EdgeIteratorState edge13 = handleWayTags(edgeIntAccess, calc, graph.edge(1, 3).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + EdgeIteratorState edge14 = handleWayTags(edgeIntAccess, calc, graph.edge(1, 4).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + EdgeIteratorState edge26 = handleWayTags(edgeIntAccess, calc, graph.edge(2, 6).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + EdgeIteratorState edge25 = handleWayTags(edgeIntAccess, calc, graph.edge(2, 5).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + EdgeIteratorState edge12 = handleWayTags(edgeIntAccess, calc, graph.edge(1, 2).setDistance(500).set(avgSpeedEnc, 15).set(accessEnc, true, true), List.of()); + + // from top to left => sharp right turn + assertEquals(10, weighting.calcTurnWeight(edge14.getEdge(), 1, edge01.getEdge())); + // left to right => straight + assertEquals(0.0, weighting.calcTurnWeight(edge01.getEdge(), 1, edge12.getEdge())); + // top to right => sharp left turn + assertEquals(120, weighting.calcTurnWeight(edge14.getEdge(), 1, edge12.getEdge())); + // left to down => right turn + assertEquals(5, weighting.calcTurnWeight(edge01.getEdge(), 1, edge13.getEdge())); + // bottom to left => left turn + assertEquals(60, weighting.calcTurnWeight(edge13.getEdge(), 1, edge01.getEdge())); + + // left to top => sharp left turn => here like 'straight' + assertEquals(120, weighting.calcTurnWeight(edge12.getEdge(), 2, edge25.getEdge())); + // down to left => sharp left turn => here again like 'straight' + assertEquals(120, weighting.calcTurnWeight(edge26.getEdge(), 2, edge12.getEdge())); + // top to left => sharp right turn + assertEquals(10, weighting.calcTurnWeight(edge25.getEdge(), 2, edge12.getEdge())); + } + + EdgeIteratorState handleWayTags(EdgeIntAccess edgeIntAccess, OrientationCalculator calc, EdgeIteratorState edge, List rawPointList) { + if (rawPointList.size() % 2 != 0) throw new IllegalArgumentException(); + if (!rawPointList.isEmpty()) { + PointList list = new PointList(); + for (int i = 0; i < rawPointList.size(); i += 2) { + list.add(rawPointList.get(0), rawPointList.get(1)); + } + edge.setWayGeometry(list); + } + + ReaderWay way = new ReaderWay(1); + way.setTag("point_list", edge.fetchWayGeometry(FetchMode.ALL)); + calc.handleWayTags(edge.getEdge(), edgeIntAccess, way, null); + return edge; } } diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java index 981d902ff5b..68722f17977 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java @@ -7,20 +7,20 @@ import com.graphhopper.routing.ev.*; import com.graphhopper.routing.querygraph.VirtualEdgeIteratorState; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.weighting.*; +import com.graphhopper.routing.weighting.DefaultTurnCostProvider; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.Graph; import com.graphhopper.util.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.Random; - import static com.graphhopper.json.Statement.*; import static com.graphhopper.json.Statement.Op.LIMIT; import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.routing.ev.RoadClass.*; import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; +import static com.graphhopper.routing.weighting.Weighting.roundWeight; import static com.graphhopper.util.GHUtility.getEdge; import static org.junit.jupiter.api.Assertions.*; @@ -41,6 +41,9 @@ public void setup() { .add(Toll.create()) .add(Hazmat.create()) .add(RouteNetwork.create(BikeNetwork.KEY)) + .add(MaxSpeed.create()) + .add(RoadClass.create()) + .add(RoadClassLink.create()) .addTurnCostEncodedValue(turnRestrictionEnc) .build(); maxSpeedEnc = encodingManager.getDecimalEncodedValue(MaxSpeed.KEY); @@ -48,218 +51,240 @@ public void setup() { graph = new BaseGraph.Builder(encodingManager).create(); } + private void setTurnRestriction(Graph graph, int from, int via, int to) { + graph.getTurnCostStorage().set(turnRestrictionEnc, getEdge(graph, from, via).getEdge(), via, getEdge(graph, via, to).getEdge(), true); + } + + private CustomModel createSpeedCustomModel(DecimalEncodedValue speedEnc) { + CustomModel customModel = new CustomModel(); + customModel.addToSpeed(If("true", LIMIT, speedEnc.getName())); + return customModel; + } + + private Weighting createWeighting(CustomModel vehicleModel) { + return CustomModelParser.createWeighting(encodingManager, NO_TURN_COST_PROVIDER, vehicleModel); + } + @Test public void speedOnly() { // 50km/h -> 72s per km, 100km/h -> 36s per km - EdgeIteratorState edge; - GHUtility.setSpeed(50, 100, accessEnc, avSpeedEnc, edge = graph.edge(0, 1).setDistance(1000)); - assertEquals(72, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeWeight(edge, false), 1.e-6); - assertEquals(36, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeWeight(edge, true), 1.e-6); + EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000).set(avSpeedEnc, 50, 100); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc) + .setDistanceInfluence(0d); + Weighting weighting = createWeighting(customModel); + + assertEquals(720, weighting.calcEdgeWeight(edge, false)); + assertEquals(360, weighting.calcEdgeWeight(edge, true)); } @Test public void withPriority() { // 25km/h -> 144s per km, 50km/h -> 72s per km, 100km/h -> 36s per km - EdgeIteratorState slow = GHUtility.setSpeed(25, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(1000)). + EdgeIteratorState slow = graph.edge(0, 1).set(avSpeedEnc, 25, 25).setDistance(1000). set(roadClassEnc, SECONDARY); - EdgeIteratorState medium = GHUtility.setSpeed(50, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(1000)). + EdgeIteratorState medium = graph.edge(0, 1).set(avSpeedEnc, 50, 50).setDistance(1000). set(roadClassEnc, SECONDARY); - EdgeIteratorState fast = GHUtility.setSpeed(100, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(1000)). + EdgeIteratorState fast = graph.edge(0, 1).set(avSpeedEnc, 100).setDistance(1000). set(roadClassEnc, SECONDARY); - // without priority costs fastest weighting is the same as custom weighting - assertEquals(144, CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager).calcEdgeWeight(slow, false), .1); - assertEquals(72, CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager).calcEdgeWeight(medium, false), .1); - assertEquals(36, CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager).calcEdgeWeight(fast, false), .1); - - CustomModel model = new CustomModel().setDistanceInfluence(0d); - assertEquals(144, createWeighting(model).calcEdgeWeight(slow, false), .1); - assertEquals(72, createWeighting(model).calcEdgeWeight(medium, false), .1); - assertEquals(36, createWeighting(model).calcEdgeWeight(fast, false), .1); + Weighting weighting = createWeighting(createSpeedCustomModel(avSpeedEnc)); + assertEquals(1440, weighting.calcEdgeWeight(slow, false)); + assertEquals(720, weighting.calcEdgeWeight(medium, false)); + assertEquals(360, weighting.calcEdgeWeight(fast, false)); // if we reduce the priority we get higher edge weights - model.addToPriority(If("road_class == SECONDARY", MULTIPLY, "0.5")); - // the absolute priority costs depend on the speed, so setting priority=0.5 means a lower absolute weight - // weight increase for fast edges and a higher absolute increase for slower edges - assertEquals(2 * 144, createWeighting(model).calcEdgeWeight(slow, false), .1); - assertEquals(2 * 72, createWeighting(model).calcEdgeWeight(medium, false), .1); - assertEquals(2 * 36, createWeighting(model).calcEdgeWeight(fast, false), .1); + weighting = CustomModelParser.createWeighting(encodingManager, NO_TURN_COST_PROVIDER, + createSpeedCustomModel(avSpeedEnc) + .addToPriority(If("road_class == SECONDARY", MULTIPLY, "0.5")) + ); + assertEquals(2 * 1440, weighting.calcEdgeWeight(slow, false)); + assertEquals(2 * 720, weighting.calcEdgeWeight(medium, false)); + assertEquals(2 * 360, weighting.calcEdgeWeight(fast, false)); } @Test public void withDistanceInfluence() { - EdgeIteratorState edge = graph.edge(0, 1).setDistance(10_000).set(avSpeedEnc, 50).set(accessEnc, true, true); - assertEquals(720, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeWeight(edge, false), .1); - assertEquals(720_000, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeMillis(edge, false), .1); - // distance_influence=30 means that for every kilometer we get additional costs of 30s, so +300s here - assertEquals(1020, createWeighting(new CustomModel().setDistanceInfluence(30d)).calcEdgeWeight(edge, false), .1); - // ... but the travelling time stays the same - assertEquals(720_000, createWeighting(new CustomModel().setDistanceInfluence(30d)).calcEdgeMillis(edge, false), .1); - + EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(10_000).set(avSpeedEnc, 50); + EdgeIteratorState edge2 = graph.edge(0, 1).setDistance(5_000).set(avSpeedEnc, 25); + Weighting weighting = createWeighting(createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(0d)); + assertEquals(7200, weighting.calcEdgeWeight(edge1, false)); + assertEquals(720_000, weighting.calcEdgeMillis(edge1, false)); // we can also imagine a shorter but slower road that takes the same time - edge = graph.edge(0, 1).setDistance(5_000).set(avSpeedEnc, 25).set(accessEnc, true, true); - assertEquals(720, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeWeight(edge, false), .1); - assertEquals(720_000, createWeighting(new CustomModel().setDistanceInfluence(0d)).calcEdgeMillis(edge, false), .1); - // and if we include the distance influence the weight will be bigger but still smaller than what we got for - // the longer and faster edge - assertEquals(870, createWeighting(new CustomModel().setDistanceInfluence(30d)).calcEdgeWeight(edge, false), .1); + assertEquals(7200, weighting.calcEdgeWeight(edge2, false)); + assertEquals(720_000, weighting.calcEdgeMillis(edge2, false)); + + // distance_influence=30 means that for every kilometer we get additional costs of 30s, so +300s here + weighting = createWeighting(createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(30d)); + assertEquals(10200, weighting.calcEdgeWeight(edge1, false)); + // for the shorter but slower edge the distance influence also increases the weight, but not as much because it is shorter + assertEquals(8700, weighting.calcEdgeWeight(edge2, false)); + // ... the travelling times stay the same + assertEquals(720_000, weighting.calcEdgeMillis(edge1, false)); + assertEquals(720_000, weighting.calcEdgeMillis(edge2, false)); } @Test public void testSpeedFactorBooleanEV() { - EdgeIteratorState edge = GHUtility.setSpeed(15, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(10)); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d); - assertEquals(3.1, createWeighting(vehicleModel).calcEdgeWeight(edge, false), 0.01); + EdgeIteratorState edge = graph.edge(0, 1).set(avSpeedEnc, 15, 15).setDistance(10); + Weighting weighting = createWeighting(createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d)); + assertEquals(31, weighting.calcEdgeWeight(edge, false)); // here we increase weight for edges that are road class links - vehicleModel.addToPriority(If(RoadClassLink.KEY, MULTIPLY, "0.5")); - Weighting weighting = createWeighting(vehicleModel); + weighting = createWeighting(createSpeedCustomModel(avSpeedEnc) + .setDistanceInfluence(70d) + .addToPriority(If(RoadClassLink.KEY, MULTIPLY, "0.5"))); BooleanEncodedValue rcLinkEnc = encodingManager.getBooleanEncodedValue(RoadClassLink.KEY); - assertEquals(3.1, weighting.calcEdgeWeight(edge.set(rcLinkEnc, false), false), 0.01); - assertEquals(5.5, weighting.calcEdgeWeight(edge.set(rcLinkEnc, true), false), 0.01); + assertEquals(31, weighting.calcEdgeWeight(edge.set(rcLinkEnc, false), false)); + assertEquals(55, weighting.calcEdgeWeight(edge.set(rcLinkEnc, true), false)); } @Test public void testBoolean() { - BooleanEncodedValue accessEnc = VehicleAccess.create("car"); - DecimalEncodedValue avSpeedEnc = VehicleSpeed.create("car", 5, 5, false); BooleanEncodedValue specialEnc = new SimpleBooleanEncodedValue("special", true); - encodingManager = new EncodingManager.Builder().add(accessEnc).add(avSpeedEnc).add(specialEnc).build(); + DecimalEncodedValue avSpeedEnc = VehicleSpeed.create("car", 5, 5, false); + encodingManager = new EncodingManager.Builder().add(specialEnc).add(avSpeedEnc).build(); graph = new BaseGraph.Builder(encodingManager).create(); - EdgeIteratorState edge = graph.edge(0, 1).set(accessEnc, true).setReverse(accessEnc, true). - set(avSpeedEnc, 15).set(specialEnc, false).setReverse(specialEnc, true).setDistance(10); - - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel); - assertEquals(3.1, weighting.calcEdgeWeight(edge, false), 0.01); - vehicleModel.addToPriority(If("special == true", MULTIPLY, "0.8")); - vehicleModel.addToPriority(If("special == false", MULTIPLY, "0.4")); - weighting = CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel); - assertEquals(6.7, weighting.calcEdgeWeight(edge, false), 0.01); - assertEquals(3.7, weighting.calcEdgeWeight(edge, true), 0.01); + EdgeIteratorState edge = graph.edge(0, 1).set(specialEnc, false, true).set(avSpeedEnc, 15).setDistance(10); + + Weighting weighting = createWeighting(createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d)); + assertEquals(31, weighting.calcEdgeWeight(edge, false)); + weighting = createWeighting(createSpeedCustomModel(avSpeedEnc) + .setDistanceInfluence(70d) + .addToPriority(If("special == true", MULTIPLY, "0.8")) + .addToPriority(If("special == false", MULTIPLY, "0.4"))); + assertEquals(67, weighting.calcEdgeWeight(edge, false)); + assertEquals(37, weighting.calcEdgeWeight(edge, true)); } @Test public void testSpeedFactorAndPriority() { EdgeIteratorState primary = graph.edge(0, 1).setDistance(10). - set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80).set(accessEnc, true, true); + set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80); EdgeIteratorState secondary = graph.edge(1, 2).setDistance(10). - set(roadClassEnc, SECONDARY).set(avSpeedEnc, 70).set(accessEnc, true, true); + set(roadClassEnc, SECONDARY).set(avSpeedEnc, 70); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d). + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToPriority(If("road_class != PRIMARY", MULTIPLY, "0.5")). addToSpeed(If("road_class != PRIMARY", MULTIPLY, "0.9")); - assertEquals(1.15, createWeighting(vehicleModel).calcEdgeWeight(primary, false), 0.01); - assertEquals(1.84, createWeighting(vehicleModel).calcEdgeWeight(secondary, false), 0.01); + Weighting weighting = createWeighting(customModel); + assertEquals(12, weighting.calcEdgeWeight(primary, false)); + assertEquals(18, weighting.calcEdgeWeight(secondary, false)); - vehicleModel = new CustomModel().setDistanceInfluence(70d). + customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToPriority(If("road_class == PRIMARY", MULTIPLY, "1.0")). addToPriority(Else(MULTIPLY, "0.5")). addToSpeed(If("road_class != PRIMARY", MULTIPLY, "0.9")); - assertEquals(1.15, createWeighting(vehicleModel).calcEdgeWeight(primary, false), 0.01); - assertEquals(1.84, createWeighting(vehicleModel).calcEdgeWeight(secondary, false), 0.01); + weighting = createWeighting(customModel); + assertEquals(12, weighting.calcEdgeWeight(primary, false)); + assertEquals(18, weighting.calcEdgeWeight(secondary, false)); } @Test public void testIssueSameKey() { - EdgeIteratorState withToll = graph.edge(0, 1).setDistance(10). - set(avSpeedEnc, 80).set(accessEnc, true, true); - EdgeIteratorState noToll = graph.edge(1, 2).setDistance(10). - set(avSpeedEnc, 80).set(accessEnc, true, true); + EdgeIteratorState withToll = graph.edge(0, 1).setDistance(10).set(avSpeedEnc, 80); + EdgeIteratorState noToll = graph.edge(1, 2).setDistance(10).set(avSpeedEnc, 80); - CustomModel vehicleModel = new CustomModel(); - vehicleModel.setDistanceInfluence(70d). + CustomModel customModel = createSpeedCustomModel(avSpeedEnc); + customModel.setDistanceInfluence(70d). addToSpeed(If("toll == HGV || toll == ALL", MULTIPLY, "0.8")). addToSpeed(If("hazmat != NO", MULTIPLY, "0.8")); - assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(withToll, false), 0.01); - assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(noToll, false), 0.01); + Weighting weighting = createWeighting(customModel); + assertEquals(13, weighting.calcEdgeWeight(withToll, false)); + assertEquals(13, weighting.calcEdgeWeight(noToll, false)); - vehicleModel = new CustomModel().setDistanceInfluence(70d). + customModel = createSpeedCustomModel(avSpeedEnc); + customModel.setDistanceInfluence(70d). addToSpeed(If("bike_network != OTHER", MULTIPLY, "0.8")); - assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(withToll, false), 0.01); - assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(noToll, false), 0.01); + weighting = createWeighting(customModel); + assertEquals(13, weighting.calcEdgeWeight(withToll, false)); + assertEquals(13, weighting.calcEdgeWeight(noToll, false)); } @Test public void testFirstMatch() { EdgeIteratorState primary = graph.edge(0, 1).setDistance(10). - set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80).set(accessEnc, true, true); + set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80); EdgeIteratorState secondary = graph.edge(1, 2).setDistance(10). - set(roadClassEnc, SECONDARY).set(avSpeedEnc, 70).set(accessEnc, true, true); + set(roadClassEnc, SECONDARY).set(avSpeedEnc, 70); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d). + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToSpeed(If("road_class == PRIMARY", MULTIPLY, "0.8")); - assertEquals(1.26, createWeighting(vehicleModel).calcEdgeWeight(primary, false), 0.01); - assertEquals(1.21, createWeighting(vehicleModel).calcEdgeWeight(secondary, false), 0.01); + Weighting weighting = createWeighting(customModel); + assertEquals(13, weighting.calcEdgeWeight(primary, false)); + assertEquals(12, weighting.calcEdgeWeight(secondary, false)); - vehicleModel.addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.9")); - vehicleModel.addToPriority(ElseIf("road_class == SECONDARY", MULTIPLY, "0.8")); + customModel.addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.9")); + customModel.addToPriority(ElseIf("road_class == SECONDARY", MULTIPLY, "0.8")); - assertEquals(1.33, createWeighting(vehicleModel).calcEdgeWeight(primary, false), 0.01); - assertEquals(1.34, createWeighting(vehicleModel).calcEdgeWeight(secondary, false), 0.01); + weighting = createWeighting(customModel); + assertEquals(13, weighting.calcEdgeWeight(primary, false)); + assertEquals(13, weighting.calcEdgeWeight(secondary, false)); } @Test - public void testCarAccess() { - EdgeIteratorState edge40 = graph.edge(0, 1).setDistance(10).set(avSpeedEnc, 40).set(accessEnc, true, true); - EdgeIteratorState edge50 = graph.edge(1, 2).setDistance(10).set(avSpeedEnc, 50).set(accessEnc, true, true); + public void testSpeedBiggerThan() { + EdgeIteratorState edge40 = graph.edge(0, 1).setDistance(10).set(avSpeedEnc, 40); + EdgeIteratorState edge50 = graph.edge(1, 2).setDistance(10).set(avSpeedEnc, 50); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d). + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToPriority(If("car_average_speed > 40", MULTIPLY, "0.5")); + Weighting weighting = createWeighting(customModel); - assertEquals(1.60, createWeighting(vehicleModel).calcEdgeWeight(edge40, false), 0.01); - assertEquals(2.14, createWeighting(vehicleModel).calcEdgeWeight(edge50, false), 0.01); + assertEquals(16, weighting.calcEdgeWeight(edge40, false)); + assertEquals(21, weighting.calcEdgeWeight(edge50, false)); } @Test public void testRoadClass() { EdgeIteratorState primary = graph.edge(0, 1).setDistance(10). - set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80).set(accessEnc, true, true); + set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80); EdgeIteratorState secondary = graph.edge(1, 2).setDistance(10). - set(roadClassEnc, SECONDARY).set(avSpeedEnc, 80).set(accessEnc, true, true); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d). + set(roadClassEnc, SECONDARY).set(avSpeedEnc, 80); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.5")); - assertEquals(1.6, createWeighting(vehicleModel).calcEdgeWeight(primary, false), 0.01); - assertEquals(1.15, createWeighting(vehicleModel).calcEdgeWeight(secondary, false), 0.01); + Weighting weighting = createWeighting(customModel); + assertEquals(16, weighting.calcEdgeWeight(primary, false)); + assertEquals(12, weighting.calcEdgeWeight(secondary, false)); } @Test public void testArea() throws Exception { EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(10). - set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80).set(accessEnc, true, true); + set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80); EdgeIteratorState edge2 = graph.edge(2, 3).setDistance(10). - set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80).set(accessEnc, true, true); + set(roadClassEnc, PRIMARY).set(avSpeedEnc, 80); graph.getNodeAccess().setNode(0, 50.0120, 11.582); graph.getNodeAccess().setNode(1, 50.0125, 11.585); graph.getNodeAccess().setNode(2, 40.0, 8.0); graph.getNodeAccess().setNode(3, 40.1, 8.1); - CustomModel vehicleModel = new CustomModel().setDistanceInfluence(70d). + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setDistanceInfluence(70d). addToPriority(If("in_custom1", MULTIPLY, "0.5")); ObjectMapper om = new ObjectMapper().registerModule(new JtsModule()); JsonFeature json = om.readValue("{ \"geometry\":{ \"type\": \"Polygon\", \"coordinates\": " + "[[[11.5818,50.0126], [11.5818,50.0119], [11.5861,50.0119], [11.5861,50.0126], [11.5818,50.0126]]] }}", JsonFeature.class); json.setId("custom1"); - vehicleModel.getAreas().getFeatures().add(json); + customModel.getAreas().getFeatures().add(json); + Weighting weighting = createWeighting(customModel); // edge1 is located within the area custom1, edge2 is not - assertEquals(1.6, createWeighting(vehicleModel).calcEdgeWeight(edge1, false), 0.01); - assertEquals(1.15, createWeighting(vehicleModel).calcEdgeWeight(edge2, false), 0.01); + assertEquals(16, weighting.calcEdgeWeight(edge1, false)); + assertEquals(12, weighting.calcEdgeWeight(edge2, false)); } @Test public void testMaxSpeed() { assertEquals(155, avSpeedEnc.getMaxOrMaxStorableDecimal(), 0.1); - assertEquals(1d / 72 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / 72 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToSpeed(If("true", LIMIT, "72"))).calcMinWeightPerDistance(), .001); // ignore too big limit to let custom model compatibility not break when max speed of encoded value later decreases - assertEquals(1d / 155 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / 155 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToSpeed(If("true", LIMIT, "180"))).calcMinWeightPerDistance(), .001); // reduce speed only a bit - assertEquals(1d / 150 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / 150 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToSpeed(If("road_class == SERVICE", MULTIPLY, "1.5")). addToSpeed(If("true", LIMIT, "150"))).calcMinWeightPerDistance(), .001); } @@ -268,139 +293,110 @@ public void testMaxSpeed() { public void testMaxPriority() { double maxSpeed = 155; assertEquals(maxSpeed, avSpeedEnc.getMaxOrMaxStorableDecimal(), 0.1); - assertEquals(1d / maxSpeed / 0.5 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 0.5 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("true", MULTIPLY, "0.5"))).calcMinWeightPerDistance(), 1.e-6); // ignore too big limit - assertEquals(1d / maxSpeed / 1.0 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 1.0 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("true", LIMIT, "2.0"))).calcMinWeightPerDistance(), 1.e-6); // priority bigger 1 is fine (if CustomModel not in query) - assertEquals(1d / maxSpeed / 2.0 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 2.0 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("true", MULTIPLY, "3.0")). addToPriority(If("true", LIMIT, "2.0"))).calcMinWeightPerDistance(), 1.e-6); - assertEquals(1d / maxSpeed / 1.5 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 1.5 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("true", MULTIPLY, "1.5"))).calcMinWeightPerDistance(), 1.e-6); // pick maximum priority from value even if this is for a special case - assertEquals(1d / maxSpeed / 3.0 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 3.0 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("road_class == SERVICE", MULTIPLY, "3.0"))).calcMinWeightPerDistance(), 1.e-6); // do NOT pick maximum priority when it is for a special case - assertEquals(1d / maxSpeed / 1.0 * 3.6, createWeighting(new CustomModel(). + assertEquals(10d / maxSpeed / 1.0 * 3.6, createWeighting(createSpeedCustomModel(avSpeedEnc). addToPriority(If("road_class == SERVICE", MULTIPLY, "0.5"))).calcMinWeightPerDistance(), 1.e-6); } - @Test - public void tooManyStatements() { - CustomModel customModel = new CustomModel(); - for (int i = 0; i < 1050; i++) { - customModel.addToPriority(If("road_class == MOTORWAY || road_class == SECONDARY || road_class == PRIMARY", MULTIPLY, "0.1")); - } - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> createWeighting(customModel)); - assertTrue(ex.getMessage().startsWith("Custom Model too big"), ex.getMessage()); - } - @Test public void maxSpeedViolated_bug_2307() { EdgeIteratorState motorway = graph.edge(0, 1).setDistance(10). - set(roadClassEnc, MOTORWAY).set(avSpeedEnc, 80).set(accessEnc, true, true); - CustomModel customModel = new CustomModel() + set(roadClassEnc, MOTORWAY).set(avSpeedEnc, 80); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc) .setDistanceInfluence(70d) - .addToSpeed(Statement.If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "0.7")) - .addToSpeed(Statement.Else(LIMIT, "30")); + .addToSpeed(If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "0.7")) + .addToSpeed(Else(LIMIT, "30")); Weighting weighting = createWeighting(customModel); - assertEquals(1.3429, weighting.calcEdgeWeight(motorway, false), 1e-4); + assertEquals(13, weighting.calcEdgeWeight(motorway, false)); assertEquals(10 / (80 * 0.7 / 3.6) * 1000, weighting.calcEdgeMillis(motorway, false), 1); } @Test public void bugWithNaNForBarrierEdges() { EdgeIteratorState motorway = graph.edge(0, 1).setDistance(0). - set(roadClassEnc, MOTORWAY).set(avSpeedEnc, 80).set(accessEnc, true, true); - CustomModel customModel = new CustomModel() - .addToPriority(Statement.If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "0")); + set(roadClassEnc, MOTORWAY).set(avSpeedEnc, 80); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc) + .addToPriority(If("road_class == MOTORWAY", Statement.Op.MULTIPLY, "0")); Weighting weighting = createWeighting(customModel); assertFalse(Double.isNaN(weighting.calcEdgeWeight(motorway, false))); assertTrue(Double.isInfinite(weighting.calcEdgeWeight(motorway, false))); } - @Test - void sameTimeAsFastestWeighting() { - // we make sure the returned times are the same, so we can check for regressions more easily when we migrate from fastest to custom - FastestWeighting fastestWeighting = new FastestWeighting(accessEnc, avSpeedEnc); - Weighting customWeighting = createWeighting(new CustomModel().setDistanceInfluence(0d)); - Random rnd = new Random(); - for (int i = 0; i < 100; i++) { - double speed = 5 + rnd.nextDouble() * 100; - double distance = rnd.nextDouble() * 1000; - EdgeIteratorState edge = graph.edge(0, 1).setDistance(distance); - GHUtility.setSpeed(speed, speed, accessEnc, avSpeedEnc, edge); - long fastestMillis = fastestWeighting.calcEdgeMillis(edge, false); - long customMillis = customWeighting.calcEdgeMillis(edge, false); - assertEquals(fastestMillis, customMillis); - } - } - - private Weighting createWeighting(CustomModel vehicleModel) { - return CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, encodingManager, NO_TURN_COST_PROVIDER, vehicleModel); - } - @Test public void testMinWeightHasSameUnitAs_getWeight() { - EdgeIteratorState edge = graph.edge(0, 1).setDistance(10); - GHUtility.setSpeed(140, 0, accessEnc, avSpeedEnc, edge); - Weighting instance = CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager); - assertEquals(instance.calcMinWeightPerDistance() * 10, instance.calcEdgeWeight(edge, false), 1e-8); + EdgeIteratorState edge = graph.edge(0, 1).set(avSpeedEnc, 140, 0).setDistance(1000); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc); + Weighting weighting = createWeighting(customModel); + assertEquals(roundWeight(weighting.calcMinWeightPerDistance() * 1000), weighting.calcEdgeWeight(edge, false)); } @Test public void testWeightWrongHeading() { - Weighting instance = CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, encodingManager, - TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel().setHeadingPenalty(100)); - EdgeIteratorState edge = graph.edge(1, 2).setDistance(10).setWayGeometry(Helper.createPointList(51, 0, 51, 1)); - GHUtility.setSpeed(10, 10, accessEnc, avSpeedEnc, edge); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc).setHeadingPenalty(100); + Weighting weighting = createWeighting(customModel); + EdgeIteratorState edge = graph.edge(1, 2) + .set(avSpeedEnc, 10, 10) + .setDistance(10).setWayGeometry(Helper.createPointList(51, 0, 51, 1)); VirtualEdgeIteratorState virtEdge = new VirtualEdgeIteratorState(edge.getEdgeKey(), 99, 5, 6, edge.getDistance(), edge.getFlags(), edge.getKeyValues(), edge.fetchWayGeometry(FetchMode.PILLAR_ONLY), false); - double time = instance.calcEdgeWeight(virtEdge, false); + double time = weighting.calcEdgeWeight(virtEdge, false); virtEdge.setUnfavored(true); // heading penalty on edge - assertEquals(time + 100, instance.calcEdgeWeight(virtEdge, false), 1e-8); + assertEquals(time + 1000, weighting.calcEdgeWeight(virtEdge, false)); // only after setting it virtEdge.setUnfavored(true); - assertEquals(time + 100, instance.calcEdgeWeight(virtEdge, true), 1e-8); + assertEquals(time + 1000, weighting.calcEdgeWeight(virtEdge, true)); // but not after releasing it virtEdge.setUnfavored(false); - assertEquals(time, instance.calcEdgeWeight(virtEdge, true), 1e-8); + assertEquals(time, weighting.calcEdgeWeight(virtEdge, true)); // test default penalty virtEdge.setUnfavored(true); - instance = CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager); - assertEquals(time + Parameters.Routing.DEFAULT_HEADING_PENALTY, instance.calcEdgeWeight(virtEdge, false), 1e-8); + customModel = createSpeedCustomModel(avSpeedEnc); + weighting = createWeighting(customModel); + assertEquals(time + 10 * Parameters.Routing.DEFAULT_HEADING_PENALTY, weighting.calcEdgeWeight(virtEdge, false)); } @Test public void testSpeed0() { EdgeIteratorState edge = graph.edge(0, 1).setDistance(10); - Weighting instance = CustomModelParser.createFastestWeighting(accessEnc, avSpeedEnc, encodingManager); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc); + Weighting weighting = createWeighting(customModel); edge.set(avSpeedEnc, 0); - assertEquals(1.0 / 0, instance.calcEdgeWeight(edge, false), 1e-8); + assertEquals(1.0 / 0, weighting.calcEdgeWeight(edge, false), 1e-8); // 0 / 0 returns NaN but calcWeight should not return NaN! edge.setDistance(0); - assertEquals(1.0 / 0, instance.calcEdgeWeight(edge, false), 1e-8); + assertEquals(1.0 / 0, weighting.calcEdgeWeight(edge, false), 1e-8); } @Test public void testTime() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true); - EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager em = EncodingManager.start().add(speedEnc).build(); BaseGraph g = new BaseGraph.Builder(em).create(); - Weighting w = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); - EdgeIteratorState edge = g.edge(0, 1).setDistance(100_000); - GHUtility.setSpeed(15, 10, accessEnc, speedEnc, edge); + EdgeIteratorState edge = g.edge(0, 1).set(speedEnc, 15, 10).setDistance(100_000); + CustomModel customModel = createSpeedCustomModel(speedEnc); + Weighting w = CustomModelParser.createWeighting(em, NO_TURN_COST_PROVIDER, customModel); assertEquals(375 * 60 * 1000, w.calcEdgeMillis(edge, false)); assertEquals(600 * 60 * 1000, w.calcEdgeMillis(edge, true)); } @@ -408,103 +404,85 @@ public void testTime() { @Test public void calcWeightAndTime_withTurnCosts() { BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, encodingManager, - new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage()), new CustomModel()); - GHUtility.setSpeed(60, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(100)); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, avSpeedEnc, graph.edge(1, 2).setDistance(100)); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc); + Weighting weighting = CustomModelParser.createWeighting(encodingManager, + new DefaultTurnCostProvider(turnRestrictionEnc, graph, new TurnCostsConfig(), null), customModel); + graph.edge(0, 1).set(avSpeedEnc, 60, 60).setDistance(100); + EdgeIteratorState edge = graph.edge(1, 2).set(avSpeedEnc, 60, 60).setDistance(100); setTurnRestriction(graph, 0, 1, 2); assertTrue(Double.isInfinite(GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0))); - assertEquals(Long.MAX_VALUE, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0)); + // the time only reflects the time for the edge, the turn time is 0 + assertEquals(6000, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0)); } @Test public void calcWeightAndTime_uTurnCosts() { BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, avSpeedEnc, null, - encodingManager, new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage(), 40), new CustomModel()); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(100)); - assertEquals(6 + 40, GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0), 1.e-6); - assertEquals((6 + 40) * 1000, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0), 1.e-6); - } - - @Test - public void calcWeightAndTime_withTurnCosts_shortest() { - BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); - Weighting weighting = new ShortestWeighting(accessEnc, avSpeedEnc, - new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage())); - GHUtility.setSpeed(60, true, true, accessEnc, avSpeedEnc, graph.edge(0, 1).setDistance(100)); - EdgeIteratorState edge = GHUtility.setSpeed(60, true, true, accessEnc, avSpeedEnc, graph.edge(1, 2).setDistance(100)); - setTurnRestriction(graph, 0, 1, 2); - assertTrue(Double.isInfinite(GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0))); - assertEquals(Long.MAX_VALUE, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0)); + CustomModel customModel = createSpeedCustomModel(avSpeedEnc); + Weighting weighting = CustomModelParser.createWeighting(encodingManager, + new DefaultTurnCostProvider(turnRestrictionEnc, graph, new TurnCostsConfig().setUTurnCosts(40), null), customModel); + EdgeIteratorState edge = graph.edge(0, 1).set(avSpeedEnc, 60, 60).setDistance(100); + assertEquals(60 + 400, GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0), 1.e-6); + assertEquals(6 * 1000, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0), 1.e-6); } @Test public void testDestinationTag() { - BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("car_access", true); DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false); - BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true); DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false); - EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build(); + EncodingManager em = EncodingManager.start().add(carSpeedEnc).add(bikeSpeedEnc).add(RoadAccess.create()).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000); - edge.set(carAccessEnc, true, true); - edge.set(bikeAccessEnc, true, true); edge.set(carSpeedEnc, 60); edge.set(bikeSpeedEnc, 18); EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); - Weighting weighting = CustomModelParser.createWeighting(carAccessEnc, carSpeedEnc, null, em, - TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel().addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1"))); + CustomModel customModel = createSpeedCustomModel(carSpeedEnc) + .addToPriority(If("road_access == DESTINATION", MULTIPLY, ".1")); + Weighting weighting = CustomModelParser.createWeighting(em, NO_TURN_COST_PROVIDER, customModel); - Weighting bikeWeighting = CustomModelParser.createWeighting(bikeAccessEnc, bikeSpeedEnc, null, em, - TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel()); + CustomModel bikeCustomModel = createSpeedCustomModel(bikeSpeedEnc); + Weighting bikeWeighting = CustomModelParser.createWeighting(em, NO_TURN_COST_PROVIDER, bikeCustomModel); edge.set(roadAccessEnc, RoadAccess.YES); - assertEquals(60, weighting.calcEdgeWeight(edge, false), 1.e-6); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), 1.e-6); + assertEquals(600, weighting.calcEdgeWeight(edge, false)); + assertEquals(2000, bikeWeighting.calcEdgeWeight(edge, false)); // the destination tag does not change the weight for the bike weighting edge.set(roadAccessEnc, RoadAccess.DESTINATION); - assertEquals(600, weighting.calcEdgeWeight(edge, false), 0.1); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), 0.1); + assertEquals(6000, weighting.calcEdgeWeight(edge, false)); + assertEquals(2000, bikeWeighting.calcEdgeWeight(edge, false)); } @Test public void testPrivateTag() { - BooleanEncodedValue carAccessEnc = new SimpleBooleanEncodedValue("car_access", true); DecimalEncodedValue carSpeedEnc = new DecimalEncodedValueImpl("car_speed", 5, 5, false); - BooleanEncodedValue bikeAccessEnc = new SimpleBooleanEncodedValue("bike_access", true); DecimalEncodedValue bikeSpeedEnc = new DecimalEncodedValueImpl("bike_speed", 4, 2, false); - EncodingManager em = EncodingManager.start().add(carAccessEnc).add(carSpeedEnc).add(bikeAccessEnc).add(bikeSpeedEnc).build(); + EncodingManager em = EncodingManager.start().add(carSpeedEnc).add(bikeSpeedEnc).add(RoadAccess.create()).build(); BaseGraph graph = new BaseGraph.Builder(em).create(); EdgeIteratorState edge = graph.edge(0, 1).setDistance(1000); - edge.set(carAccessEnc, true, true); - edge.set(bikeAccessEnc, true, true); edge.set(carSpeedEnc, 60); edge.set(bikeSpeedEnc, 18); EnumEncodedValue roadAccessEnc = em.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class); - Weighting weighting = CustomModelParser.createWeighting(carAccessEnc, carSpeedEnc, null, em, - TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel().addToPriority(If("road_access == PRIVATE", MULTIPLY, ".1"))); + CustomModel customModel = createSpeedCustomModel(carSpeedEnc) + .addToPriority(If("road_access == PRIVATE", MULTIPLY, ".1")); + Weighting weighting = CustomModelParser.createWeighting(em, NO_TURN_COST_PROVIDER, customModel); - Weighting bikeWeighting = CustomModelParser.createWeighting(bikeAccessEnc, bikeSpeedEnc, null, em, - TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel().addToPriority(If("road_access == PRIVATE", MULTIPLY, "0.8333"))); + customModel = createSpeedCustomModel(bikeSpeedEnc) + .addToPriority(If("road_access == PRIVATE", MULTIPLY, "0.8333")); + Weighting bikeWeighting = CustomModelParser.createWeighting(em, NO_TURN_COST_PROVIDER, customModel); ReaderWay way = new ReaderWay(1); way.setTag("highway", "secondary"); edge.set(roadAccessEnc, RoadAccess.YES); - assertEquals(60, weighting.calcEdgeWeight(edge, false), .01); - assertEquals(200, bikeWeighting.calcEdgeWeight(edge, false), .01); + assertEquals(600, weighting.calcEdgeWeight(edge, false)); + assertEquals(2000, bikeWeighting.calcEdgeWeight(edge, false)); edge.set(roadAccessEnc, RoadAccess.PRIVATE); - assertEquals(600, weighting.calcEdgeWeight(edge, false), .01); + assertEquals(6000, weighting.calcEdgeWeight(edge, false)); // private should influence bike only slightly - assertEquals(240, bikeWeighting.calcEdgeWeight(edge, false), .01); - } - - private void setTurnRestriction(Graph graph, int from, int via, int to) { - graph.getTurnCostStorage().set(turnRestrictionEnc, getEdge(graph, from, via).getEdge(), via, getEdge(graph, via, to).getEdge(), true); + assertEquals(2400, bikeWeighting.calcEdgeWeight(edge, false)); } } diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java index b8105528695..4bcdc1fe50b 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/FindMinMaxTest.java @@ -3,6 +3,7 @@ import com.graphhopper.json.MinMax; import com.graphhopper.json.Statement; import com.graphhopper.routing.ev.EncodedValueLookup; +import com.graphhopper.routing.ev.RoadEnvironment; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.util.CustomModel; import org.junit.jupiter.api.BeforeEach; @@ -10,15 +11,13 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.HashSet; import java.util.List; import static com.graphhopper.json.Statement.*; import static com.graphhopper.json.Statement.Op.LIMIT; import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.routing.weighting.custom.FindMinMax.findMinMax; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.*; class FindMinMaxTest { @@ -26,7 +25,7 @@ class FindMinMaxTest { @BeforeEach void setup() { - lookup = new EncodingManager.Builder().build(); + lookup = new EncodingManager.Builder().add(RoadEnvironment.create()).build(); } @Test @@ -76,16 +75,15 @@ public void testFindMaxPriority() { statements.add(If("true", MULTIPLY, "2")); assertEquals(2, findMinMax(new MinMax(0, 1), statements, lookup).max); - statements = new ArrayList<>(); - statements.add(If("true", MULTIPLY, "0.5")); - assertEquals(0.5, findMinMax(new MinMax(0, 1), statements, lookup).max); + List statements2 = new ArrayList<>(); + statements2.add(If("true", MULTIPLY, "0.5")); + assertEquals(0.5, findMinMax(new MinMax(0, 1), statements2, lookup).max); - statements = new ArrayList<>(); - statements.add(If("road_class == MOTORWAY", MULTIPLY, "0.5")); - statements.add(Else(MULTIPLY, "-0.5")); - MinMax minMax = findMinMax(new MinMax(1, 1), statements, lookup); - assertEquals(-0.5, minMax.min); - assertEquals(0.5, minMax.max); + List statements3 = new ArrayList<>(); + statements3.add(If("road_class == MOTORWAY", MULTIPLY, "0.5")); + statements3.add(Else(MULTIPLY, "-0.5")); + IllegalArgumentException m = assertThrows(IllegalArgumentException.class, () -> findMinMax(new MinMax(1, 1), statements3, lookup)); + assertTrue(m.getMessage().startsWith("statement resulted in negative value")); } @Test @@ -101,6 +99,13 @@ public void findMax_multipleBlocks() { assertEquals(80, findMinMax(new MinMax(0, 100), statements, lookup).max); assertEquals(60, findMinMax(new MinMax(0, 60), statements, lookup).max); + statements = Arrays.asList( + If("road_environment == TUNNEL", LIMIT, "130"), + ElseIf("road_environment == BRIDGE", LIMIT, "50"), + Else(MULTIPLY, "0.8") + ); + assertEquals(130, findMinMax(new MinMax(0, 150), statements, lookup).max); + statements = Arrays.asList( If("road_class == TERTIARY", MULTIPLY, "0.2"), ElseIf("road_class == SECONDARY", LIMIT, "25"), @@ -111,4 +116,25 @@ public void findMax_multipleBlocks() { assertEquals(40, findMinMax(new MinMax(0, 150), statements, lookup).max); assertEquals(40, findMinMax(new MinMax(0, 40), statements, lookup).max); } + + @Test + public void testBlock() { + List statements = Arrays.asList( + If("road_class == TERTIARY", + List.of(If("max_speed > 100", LIMIT, "100"), + Else(LIMIT, "30"))), + ElseIf("road_class == SECONDARY", LIMIT, "25"), + Else(MULTIPLY, "0.8") + ); + assertEquals(100, findMinMax(new MinMax(0, 120), statements, lookup).max); + + statements = Arrays.asList( + If("road_class == TERTIARY", + List.of(If("max_speed > 100", LIMIT, "90"), + Else(LIMIT, "30"))), + ElseIf("road_class == SECONDARY", LIMIT, "25"), + Else(MULTIPLY, "0.8") + ); + assertEquals(96, findMinMax(new MinMax(0, 120), statements, lookup).max); + } } diff --git a/core/src/test/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitorTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitorTest.java index b6eccc199a0..ce7440699bc 100644 --- a/core/src/test/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitorTest.java +++ b/core/src/test/java/com/graphhopper/routing/weighting/custom/ValueExpressionVisitorTest.java @@ -102,6 +102,12 @@ public void testErrors() { msg = assertThrows(IllegalArgumentException.class, () -> findVariables("my_prio*my_priority2 * 3", lookup)).getMessage(); assertEquals("'my_prio' not available", msg); + + msg = assertThrows(IllegalArgumentException.class, () -> findVariables("-0.5", lookup)).getMessage(); + assertEquals("illegal expression as it can result in a negative weight: -0.5", msg); + + msg = assertThrows(IllegalArgumentException.class, () -> findVariables("-my_priority", lookup)).getMessage(); + assertEquals("illegal expression as it can result in a negative weight: -my_priority", msg); } @Test @@ -124,7 +130,10 @@ public void runVariables() { EncodedValueLookup lookup = new EncodingManager.Builder().add(prio1).add(prio2).build(); assertEquals(Set.of(), findVariables("2", lookup)); - assertEquals(Set.of("my_priority"), findVariables("-2*my_priority", lookup)); + assertEquals(Set.of("my_priority"), findVariables("2*my_priority", lookup)); + + Exception ex = assertThrows(IllegalArgumentException.class, () -> findVariables("-2*my_priority", lookup)); + assertTrue(ex.getMessage().contains("illegal expression as it can result in a negative weight")); } void assertInterval(double min, double max, String expression, EncodedValueLookup lookup) { diff --git a/core/src/test/java/com/graphhopper/search/KVStorageTest.java b/core/src/test/java/com/graphhopper/search/KVStorageTest.java index 8c3820ac013..e7415f76e47 100644 --- a/core/src/test/java/com/graphhopper/search/KVStorageTest.java +++ b/core/src/test/java/com/graphhopper/search/KVStorageTest.java @@ -1,9 +1,9 @@ package com.graphhopper.search; import com.carrotsearch.hppc.LongArrayList; -import com.graphhopper.search.KVStorage.KeyValue; -import com.graphhopper.storage.RAMDirectory; -import com.graphhopper.util.BitUtil; +import com.graphhopper.search.KVStorage.KValue; +import com.graphhopper.storage.DAType; +import com.graphhopper.storage.GHDirectory; import com.graphhopper.util.Helper; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; @@ -11,7 +11,6 @@ import java.io.File; import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; import static com.graphhopper.search.KVStorage.MAX_UNIQUE_KEYS; import static com.graphhopper.search.KVStorage.cutString; import static com.graphhopper.util.Helper.UTF_CS; @@ -22,15 +21,15 @@ public class KVStorageTest { private final static String location = "./target/edge-kv-storage"; private KVStorage create() { - return new KVStorage(new RAMDirectory(), true).create(1000); + return new KVStorage(new GHDirectory("", DAType.RAM), true).create(1000); } - List createList(Object... keyValues) { + Map createMap(Object... keyValues) { if (keyValues.length % 2 != 0) throw new IllegalArgumentException("Cannot create list from " + Arrays.toString(keyValues)); - List map = new ArrayList<>(); + Map map = new LinkedHashMap<>(); for (int i = 0; i < keyValues.length; i += 2) { - map.add(new KeyValue((String) keyValues[i], keyValues[i + 1])); + map.put((String) keyValues[i], new KValue(keyValues[i + 1])); } return map; } @@ -38,7 +37,7 @@ List createList(Object... keyValues) { @Test public void putSame() { KVStorage index = create(); - long aPointer = index.add(createList("a", "same name", "b", "same name")); + long aPointer = index.add(createMap("a", "same name", "b", "same name")); assertNull(index.get(aPointer, "", false)); assertEquals("same name", index.get(aPointer, "a", false)); @@ -46,14 +45,14 @@ public void putSame() { assertNull(index.get(aPointer, "c", false)); index = create(); - aPointer = index.add(createList("a", "a name", "b", "same name")); + aPointer = index.add(createMap("a", "a name", "b", "same name")); assertEquals("a name", index.get(aPointer, "a", false)); } @Test public void putAB() { KVStorage index = create(); - long aPointer = index.add(createList("a", "a name", "b", "b name")); + long aPointer = index.add(createMap("a", "a name", "b", "b name")); assertNull(index.get(aPointer, "", false)); assertEquals("a name", index.get(aPointer, "a", false)); @@ -63,15 +62,16 @@ public void putAB() { @Test public void getForwardBackward() { KVStorage index = create(); - List list = new ArrayList<>(); - list.add(new KeyValue("keyA", "FORWARD", true, false)); - list.add(new KeyValue("keyB", "BACKWARD", false, true)); - list.add(new KeyValue("keyC", "BOTH", true, true)); - long aPointer = index.add(list); + Map map = new LinkedHashMap<>(); + map.put("keyA", new KValue("FORWARD", null)); + map.put("keyB", new KValue(null, "BACKWARD")); + map.put("keyC", new KValue("BOTH")); + map.put("keyD", new KValue("BOTH1", "BOTH2")); + long aPointer = index.add(map); assertNull(index.get(aPointer, "", false)); - List deserializedList = index.getAll(aPointer); - assertEquals(list, deserializedList); + Map deserializedList = index.getAll(aPointer); + assertEquals(map, deserializedList); assertEquals("FORWARD", index.get(aPointer, "keyA", false)); assertNull(index.get(aPointer, "keyA", true)); @@ -81,20 +81,27 @@ public void getForwardBackward() { assertEquals("BOTH", index.get(aPointer, "keyC", false)); assertEquals("BOTH", index.get(aPointer, "keyC", true)); + + assertEquals("BOTH1", index.get(aPointer, "keyD", false)); + assertEquals("BOTH2", index.get(aPointer, "keyD", true)); } @Test public void putEmpty() { KVStorage index = create(); - assertEquals(1, index.add(createList("", ""))); + long emptyKeyPointer = index.add(createMap("", "")); + // First pointer should be at START_POINTER (aligned to 4) + assertEquals(4, emptyKeyPointer); // cannot store null (in its first version we accepted null once it was clear which type the value has, but this is inconsequential) - assertThrows(IllegalArgumentException.class, () -> assertEquals(5, index.add(createList("", null)))); - assertThrows(IllegalArgumentException.class, () -> index.add(createList("blup", null))); - assertThrows(IllegalArgumentException.class, () -> index.add(createList(null, null))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("", null))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("blup", null))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap(null, null))); assertNull(index.get(0, "", false)); - assertEquals(5, index.add(createList("else", "else"))); + long elsePointer = index.add(createMap("else", "else")); + assertTrue(elsePointer > emptyKeyPointer, "second pointer should be larger than first"); + assertEquals("else", index.get(elsePointer, "else", false)); } @Test @@ -103,7 +110,7 @@ public void putMany() { long aPointer = 0, tmpPointer = 0; for (int i = 0; i < 10000; i++) { - aPointer = index.add(createList("a", "a name " + i, "b", "b name " + i, "c", "c name " + i)); + aPointer = index.add(createMap("a", "a name " + i, "b", "b name " + i, "c", "c name " + i)); if (i == 567) tmpPointer = aPointer; } @@ -121,15 +128,53 @@ public void putManyKeys() { KVStorage index = create(); // one key is already stored => empty key for (int i = 1; i < MAX_UNIQUE_KEYS; i++) { - index.add(createList("a" + i, "a name")); + index.add(createMap("a" + i, "a name")); } try { - index.add(createList("new", "a name")); + index.add(createMap("new", "a name")); fail(); } catch (IllegalArgumentException ex) { } } + @Test + public void testHighKeyIndicesUpToDesignLimit() { + // This test verifies that key indices >= 8192 work correctly. + // Previously there was a sign extension bug when reading shorts for key indices >= 8192 + // because (keyIndex << 2) exceeds 32767 and becomes negative when stored as a signed short. + KVStorage index = create(); + + // Create MAX_UNIQUE_KEYS - 1 unique keys (index 0 is reserved for empty key) + // This gives us key indices from 1 to MAX_UNIQUE_KEYS - 1 (i.e., 1 to 16383) + List pointers = new ArrayList<>(); + for (int i = 1; i < MAX_UNIQUE_KEYS; i++) { + long pointer = index.add(createMap("key" + i, "value" + i)); + pointers.add(pointer); + } + + // Verify we can read back entries that use high key indices (>= 8192) + // Key index 8192 is the first one that triggers the sign extension issue + for (int i = 8192; i < MAX_UNIQUE_KEYS; i++) { + long pointer = pointers.get(i - 1); // pointers list is 0-indexed, keys start at 1 + String expectedKey = "key" + i; + String expectedValue = "value" + i; + + // Test get() method + assertEquals(expectedValue, index.get(pointer, expectedKey, false), + "get() failed for key index " + i); + + // Test getMap() method + Map map = index.getMap(pointer); + assertEquals(expectedValue, map.get(expectedKey), + "getMap() failed for key index " + i); + + // Test getAll() method + Map allMap = index.getAll(pointer); + assertEquals(expectedValue, allMap.get(expectedKey).getFwd(), + "getAll() failed for key index " + i); + } + } + @Test public void testNoErrorOnLargeStringValue() { KVStorage index = create(); @@ -138,14 +183,14 @@ public void testNoErrorOnLargeStringValue() { str += "ß"; } assertEquals(254, str.getBytes(Helper.UTF_CS).length); - long result = index.add(createList("", str)); + long result = index.add(createMap("", str)); assertEquals(127, ((String) index.get(result, "", false)).length()); } @Test public void testTooLongStringValueError() { KVStorage index = create(); - assertThrows(IllegalArgumentException.class, () -> index.add(createList("", "Бухарестская улица (http://ru.wikipedia.org/wiki" + + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("", "Бухарестская улица (http://ru.wikipedia.org/wiki" + "/%D0%91%D1%83%D1%85%D0%B0%D1%80%D0%B5%D1%81%D1%82%D1%81%D0%BA%D0%B0%D1%8F_%D1%83%D0%BB%D0%B8%D1%86%D0%B0_(%D0%A1%D0%B0%D0%BD%D0%BA%D1%82-%D0%9F%D0%B5%D1%82%D0%B5%D1%80%D0%B1%D1%83%D1%80%D0%B3))"))); String str = "sdfsdfds"; @@ -153,7 +198,7 @@ public void testTooLongStringValueError() { str += "Б"; } final String finalStr = str; - assertThrows(IllegalArgumentException.class, () -> index.add(createList("", finalStr))); + assertThrows(IllegalArgumentException.class, () -> index.add(createMap("", finalStr))); } @Test @@ -165,26 +210,35 @@ public void testNoErrorOnLargestByteArray() { bytes[i] = (byte) (i % 255); copy[i] = bytes[i]; } - long result = index.add(createKV("myval", bytes)); + long result = index.add(Map.of("myval", new KValue(bytes))); bytes = (byte[]) index.get(result, "myval", false); assertArrayEquals(copy, bytes); final byte[] biggerByteArray = Arrays.copyOf(bytes, 256); - IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> index.add(createKV("myval2", biggerByteArray))); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> index.add(Map.of("myval2", new KValue(biggerByteArray)))); assertTrue(e.getMessage().contains("bytes.length cannot be > 255")); } @Test public void testIntLongDoubleFloat() { KVStorage index = create(); - long intres = index.add(createKV("intres", 4)); - long doubleres = index.add(createKV("doubleres", 4d)); - long floatres = index.add(createKV("floatres", 4f)); - long longres = index.add(createKV("longres", 4L)); - long after4Inserts = index.add(createKV("somenext", 0)); - - // initial point is 1, then twice plus 1 + (2+4) and twice plus 1 + (2+8) - assertEquals(1 + 36, after4Inserts); + long intres = index.add(Map.of("intres", new KValue(4))); + long doubleres = index.add(Map.of("doubleres", new KValue(4d))); + long floatres = index.add(Map.of("floatres", new KValue(4f))); + long longres = index.add(Map.of("longres", new KValue(4L))); + long after4Inserts = index.add(Map.of("somenext", new KValue(0))); + + // Pointers should be sequential and increasing, aligned to 4-byte boundaries + assertTrue(intres < doubleres); + assertTrue(doubleres < floatres); + assertTrue(floatres < longres); + assertTrue(longres < after4Inserts); + // Verify all pointers are 4-byte aligned + assertEquals(0, intres % 4); + assertEquals(0, doubleres % 4); + assertEquals(0, floatres % 4); + assertEquals(0, longres % 4); + assertEquals(0, after4Inserts % 4); assertEquals(4f, index.get(floatres, "floatres", false)); assertEquals(4L, index.get(longres, "longres", false)); @@ -195,40 +249,44 @@ public void testIntLongDoubleFloat() { @Test public void testIntLongDoubleFloat2() { KVStorage index = create(); - List list = new ArrayList<>(); - list.add(new KeyValue("int", 4)); - list.add(new KeyValue("long", 4L)); - list.add(new KeyValue("double", 4d)); - list.add(new KeyValue("float", 4f)); - long allInOne = index.add(list); - - long afterMapInsert = index.add(createKV("somenext", 0)); - - // 1 + 1 + (2+4) + (2+8) + (2+8) + (2+4) - assertEquals(1 + 1 + 32, afterMapInsert); - - List resMap = index.getAll(allInOne); - assertEquals(4, resMap.get(0).value); - assertEquals(4L, resMap.get(1).value); - assertEquals(4d, resMap.get(2).value); - assertEquals(4f, resMap.get(3).value); + Map map = new LinkedHashMap<>(); + map.put("int", new KValue(4)); + map.put("long", new KValue(4L)); + map.put("double", new KValue(4d)); + map.put("float", new KValue(4f)); + long allInOne = index.add(map); + + long afterMapInsert = index.add(Map.of("somenext", new KValue(0))); + + // Pointer should increase after adding more data, and both should be aligned + assertTrue(afterMapInsert > allInOne); + assertEquals(0, allInOne % 4); + assertEquals(0, afterMapInsert % 4); + + Map resMap = index.getAll(allInOne); + assertEquals(4, resMap.get("int").getFwd()); + assertEquals(4L, resMap.get("long").getFwd()); + assertEquals(4d, resMap.get("double").getFwd()); + assertEquals(4f, resMap.get("float").getFwd()); } @Test public void testFlush() { Helper.removeDir(new File(location)); - KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true); - long pointer = index.add(createList("", "test")); + KVStorage index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE).create(), true); + long pointer = index.add(createMap("", "test")); index.flush(); index.close(); - index = new KVStorage(new RAMDirectory(location, true), true); + index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE), true); assertTrue(index.loadExisting()); assertEquals("test", index.get(pointer, "", false)); // make sure bytePointer is correctly set after loadExisting - long newPointer = index.add(createList("", "testing")); - assertEquals(pointer + 1 + 3 + "test".getBytes().length, newPointer, newPointer + ">" + pointer); + long newPointer = index.add(createMap("", "testing")); + assertTrue(newPointer > pointer, "newPointer " + newPointer + " should be > pointer " + pointer); + assertEquals(0, newPointer % 4, "newPointer should be 4-byte aligned"); + assertEquals("testing", index.get(newPointer, "", false)); index.close(); Helper.removeDir(new File(location)); @@ -238,16 +296,16 @@ public void testFlush() { public void testLoadKeys() { Helper.removeDir(new File(location)); - KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true).create(1000); - long pointerA = index.add(createList("c", "test value")); + KVStorage index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE).create(), true).create(1000); + long pointerA = index.add(createMap("c", "test value")); assertEquals(2, index.getKeys().size()); - long pointerB = index.add(createList("a", "value", "b", "another value")); + long pointerB = index.add(createMap("a", "value", "b", "another value")); // empty string is always the first key assertEquals("[, c, a, b]", index.getKeys().toString()); index.flush(); index.close(); - index = new KVStorage(new RAMDirectory(location, true), true); + index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE), true); assertTrue(index.loadExisting()); assertEquals("[, c, a, b]", index.getKeys().toString()); assertEquals("test value", index.get(pointerA, "c", false)); @@ -256,7 +314,7 @@ public void testLoadKeys() { assertNull(index.get(pointerB, "", false)); assertEquals("value", index.get(pointerB, "a", false)); assertEquals("another value", index.get(pointerB, "b", false)); - assertEquals("[a=value (true|true), b=another value (true|true)]", index.getAll(pointerB).toString()); + assertEquals("{a=value, b=another value}", index.getAll(pointerB).toString()); index.close(); Helper.removeDir(new File(location)); @@ -265,8 +323,8 @@ public void testLoadKeys() { @Test public void testEmptyKey() { KVStorage index = create(); - long pointerA = index.add(createList("", "test value")); - long pointerB = index.add(createList("a", "value", "b", "another value")); + long pointerA = index.add(createMap("", "test value")); + long pointerB = index.add(createMap("a", "value", "b", "another value")); assertEquals("test value", index.get(pointerA, "", false)); assertNull(index.get(pointerA, "a", false)); @@ -275,24 +333,36 @@ public void testEmptyKey() { assertNull(index.get(pointerB, "", false)); } + @Test + public void testDifferentValuePerDirection() { + Map map = new LinkedHashMap<>(); + map.put("test", new KValue("forw", "back")); + + KVStorage index = create(); + long pointerA = index.add(map); + + assertEquals("forw", index.get(pointerA, "test", false)); + assertEquals("back", index.get(pointerA, "test", true)); + } + @Test public void testSameByteArray() { KVStorage index = create(); - long pointerA = index.add(createList("mykey", new byte[]{1, 2, 3, 4})); - long pointerB = index.add(createList("mykey", new byte[]{1, 2, 3, 4})); + long pointerA = index.add(createMap("mykey", new byte[]{1, 2, 3, 4})); + long pointerB = index.add(createMap("mykey", new byte[]{1, 2, 3, 4})); assertEquals(pointerA, pointerB); byte[] sameRef = new byte[]{1, 2, 3, 4}; - pointerA = index.add(createList("mykey", sameRef)); - pointerB = index.add(createList("mykey", sameRef)); + pointerA = index.add(createMap("mykey", sameRef)); + pointerB = index.add(createMap("mykey", sameRef)); assertEquals(pointerA, pointerB); } @Test public void testUnknownValueClass() { KVStorage index = create(); - IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> index.add(createList("mykey", new Object()))); + IllegalArgumentException ex = assertThrows(IllegalArgumentException.class, () -> index.add(createMap("mykey", new Object()))); assertTrue(ex.getMessage().contains("The Class of a value was Object, currently supported"), ex.getMessage()); } @@ -300,15 +370,15 @@ public void testUnknownValueClass() { public void testRandom() { final long seed = new Random().nextLong(); try { - KVStorage index = new KVStorage(new RAMDirectory(location, true).create(), true).create(1000); + KVStorage index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE).create(), true).create(1000); Random random = new Random(seed); List keys = createRandomStringList(random, "_key", 100); - List values = createRandomList(random, 500); + List values = createRandomMap(random, 500); int size = 10000; LongArrayList pointers = new LongArrayList(size); for (int i = 0; i < size; i++) { - List list = createRandomList(random, keys, values); + Map list = createRandomMap(random, keys, values); long pointer = index.add(list); try { assertEquals(list.size(), index.getAll(pointer).size(), "" + i); @@ -319,24 +389,24 @@ public void testRandom() { } for (int i = 0; i < size; i++) { - List list = index.getAll(pointers.get(i)); - assertTrue(list.size() > 0, i + " " + list); - for (KeyValue entry : list) { - Object value = index.get(pointers.get(i), entry.key, false); - assertEquals(entry.value, value, i + " " + list); + Map map = index.getAll(pointers.get(i)); + assertFalse(map.isEmpty(), i + " " + map); + for (Map.Entry entry : map.entrySet()) { + Object value = index.get(pointers.get(i), entry.getKey(), false); + assertEquals(entry.getValue().getFwd(), value, i + " " + map); } } index.flush(); index.close(); - index = new KVStorage(new RAMDirectory(location, true).create(), true); + index = new KVStorage(new GHDirectory(location, DAType.RAM_STORE).create(), true); assertTrue(index.loadExisting()); for (int i = 0; i < size; i++) { - List list = index.getAll(pointers.get(i)); - assertTrue(list.size() > 0, i + " " + list); - for (KeyValue entry : list) { - Object value = index.get(pointers.get(i), entry.key, false); - assertEquals(entry.value, value, i + " " + list); + Map map = index.getAll(pointers.get(i)); + assertFalse(map.isEmpty(), i + " " + map); + for (Map.Entry entry : map.entrySet()) { + Object value = index.get(pointers.get(i), entry.getKey(), false); + assertEquals(entry.getValue().getFwd(), value, i + " " + map); } } index.close(); @@ -345,7 +415,7 @@ public void testRandom() { } } - private List createRandomList(Random random, int size) { + private List createRandomMap(Random random, int size) { List list = new ArrayList<>(); for (int i = 0; i < size; i++) { list.add(random.nextInt(size * 5)); @@ -361,16 +431,16 @@ private List createRandomStringList(Random random, String postfix, int s return list; } - private List createRandomList(Random random, List keys, List values) { + private Map createRandomMap(Random random, List keys, List values) { int count = random.nextInt(10) + 2; Set avoidDuplicates = new HashSet<>(); // otherwise index.get returns potentially wrong value - List list = new ArrayList<>(); + Map list = new LinkedHashMap<>(); for (int i = 0; i < count; i++) { String key = keys.get(random.nextInt(keys.size())); if (!avoidDuplicates.add(key)) continue; Object o = values.get(random.nextInt(values.size())); - list.add(new KeyValue(key, key.endsWith("_s") ? o + "_s" : o)); + list.put(key, new KValue(key.endsWith("_s") ? o + "_s" : o)); } return list; } diff --git a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java index c0edccbbcbf..dfeb4b28c7d 100644 --- a/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java +++ b/core/src/test/java/com/graphhopper/storage/AbstractGraphStorageTester.java @@ -17,10 +17,7 @@ */ package com.graphhopper.storage; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.AccessFilter; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.routing.util.EncodingManager; @@ -31,9 +28,10 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -57,6 +55,7 @@ protected EncodingManager createEncodingManager() { return new EncodingManager.Builder() .add(carAccessEnc).add(carSpeedEnc) .add(footAccessEnc).add(footSpeedEnc) + .add(RoadClass.create()) .build(); } @@ -70,8 +69,8 @@ protected EncodingManager createEncodingManager() { public static void assertPList(PointList expected, PointList list) { assertEquals(expected.size(), list.size(), "size of point lists is not equal"); for (int i = 0; i < expected.size(); i++) { - assertEquals(expected.getLat(i), list.getLat(i), 1e-4); - assertEquals(expected.getLon(i), list.getLon(i), 1e-4); + assertEquals(expected.getLat(i), list.getLat(i), 1e-5); + assertEquals(expected.getLon(i), list.getLon(i), 1e-5); } } @@ -79,7 +78,7 @@ public static int getIdOf(Graph g, double latitude) { int s = g.getNodes(); NodeAccess na = g.getNodeAccess(); for (int i = 0; i < s; i++) { - if (Math.abs(na.getLat(i) - latitude) < 1e-4) { + if (Math.abs(na.getLat(i) - latitude) < 1e-5) { return i; } } @@ -90,7 +89,7 @@ public static int getIdOf(Graph g, double latitude, double longitude) { int s = g.getNodes(); NodeAccess na = g.getNodeAccess(); for (int i = 0; i < s; i++) { - if (Math.abs(na.getLat(i) - latitude) < 1e-4 && Math.abs(na.getLon(i) - longitude) < 1e-4) { + if (Math.abs(na.getLat(i) - latitude) < 1e-5 && Math.abs(na.getLon(i) - longitude) < 1e-5) { return i; } } @@ -122,7 +121,7 @@ public void tearDown() { public void testSetTooBigDistance_435() { graph = createGHStorage(); - double maxDist = BaseGraphNodesAndEdges.MAX_DIST; + double maxDist = BaseGraph.MAX_DIST_METERS; EdgeIteratorState edge1 = graph.edge(0, 1).setDistance(maxDist); assertEquals(maxDist, edge1.getDistance(), 1); @@ -131,6 +130,65 @@ public void testSetTooBigDistance_435() { assertEquals(maxDist, edge2.getDistance(), 1); } + @Test + public void testGetSetDistance_mm() { + graph = createGHStorage(); + + // basic round-trip: setDistance_mm -> getDistance_mm is lossless + EdgeIteratorState edge = graph.edge(0, 1).setDistance_mm(123456); + assertEquals(123456, edge.getDistance_mm()); + // getDistance returns meters + assertEquals(123.456, edge.getDistance(), 1e-9); + + // zero distance + edge.setDistance_mm(0); + assertEquals(0, edge.getDistance_mm()); + assertEquals(0, edge.getDistance(), 1e-9); + + // single millimeter + edge.setDistance_mm(1); + assertEquals(1, edge.getDistance_mm()); + assertEquals(0.001, edge.getDistance(), 1e-9); + + // setDistance -> getDistance_mm should give the rounded mm value + edge.setDistance(1.2345); + // 1.2345 * 1000 = 1234.5 -> rounds to 1235 + assertEquals(1235, edge.getDistance_mm()); + assertEquals(1.235, edge.getDistance(), 1e-9); + + // setDistance_mm -> setDistance_mm copy is lossless (the whole point of the API) + EdgeIteratorState edge2 = graph.edge(0, 2); + edge2.setDistance_mm(edge.getDistance_mm()); + assertEquals(edge.getDistance_mm(), edge2.getDistance_mm()); + assertEquals(edge.getDistance(), edge2.getDistance(), 1e-9); + } + + @Test + public void testDistance_mmCapping() { + graph = createGHStorage(); + + // distances at MAX_DIST_MM are stored exactly + EdgeIteratorState edge = graph.edge(0, 1).setDistance_mm(BaseGraphNodesAndEdges.MAX_DIST_MM); + assertEquals(BaseGraphNodesAndEdges.MAX_DIST_MM, edge.getDistance_mm()); + + // values larger than MAX_DIST_MM are capped + EdgeIteratorState edge2 = graph.edge(0, 2).setDistance_mm(BaseGraphNodesAndEdges.MAX_DIST_MM + 1); + assertEquals(BaseGraphNodesAndEdges.MAX_DIST_MM, edge2.getDistance_mm()); + } + + @Test + public void testDistance_mmArguments() { + graph = createGHStorage(); + EdgeIteratorState edge = graph.edge(0, 1); + assertThrows(IllegalArgumentException.class, () -> edge.setDistance_mm(-1)); + // if the distance exceeds the limit it will be capped silently! debatable, but this is what + // we've been doing for a long time in setDistance. + edge.setDistance_mm(BaseGraphNodesAndEdges.MAX_DIST_MM + 1L); + assertEquals(BaseGraphNodesAndEdges.MAX_DIST_MM, edge.getDistance_mm()); + edge.setDistance(BaseGraph.MAX_DIST_METERS + 1L); + assertEquals(BaseGraph.MAX_DIST_METERS, edge.getDistance()); + } + @Test public void testSetNodes() { graph = createGHStorage(); @@ -267,7 +325,7 @@ public void testUpdateUnidirectional() { public void testCopyProperties() { graph = createGHStorage(); EdgeIteratorState edge = graph.edge(1, 3).setDistance(10).set(carAccessEnc, true, false). - setKeyValues(createKV(STREET_NAME, "testing")).setWayGeometry(Helper.createPointList(1, 2)); + setKeyValues(Map.of(STREET_NAME, new KValue("testing"))).setWayGeometry(Helper.createPointList(1, 2)); EdgeIteratorState newEdge = graph.edge(1, 3).setDistance(10).set(carAccessEnc, true, false); newEdge.copyPropertiesFrom(edge); @@ -640,10 +698,10 @@ public void testGetAllEdges() { public void testKVStorage() { graph = createGHStorage(); EdgeIteratorState iter1 = graph.edge(0, 1).setDistance(10).set(carAccessEnc, true, true); - iter1.setKeyValues(createKV(STREET_NAME, "named street1")); + iter1.setKeyValues(Map.of(STREET_NAME, new KValue("named street1"))); EdgeIteratorState iter2 = graph.edge(0, 1).setDistance(10).set(carAccessEnc, true, true); - iter2.setKeyValues(createKV(STREET_NAME, "named street2")); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("named street2"))); assertEquals(graph.getEdgeIteratorState(iter1.getEdge(), iter1.getAdjNode()).getName(), "named street1"); assertEquals(graph.getEdgeIteratorState(iter2.getEdge(), iter2.getAdjNode()).getName(), "named street2"); @@ -666,8 +724,7 @@ public void test8AndMoreBytesForEdgeFlags() { IntsRef intsRef = manager.createEdgeFlags(); intsRef.ints[0] = Integer.MAX_VALUE / 3; edge.setFlags(intsRef); - // System.out.println(BitUtil.LITTLE.toBitString(Long.MAX_VALUE / 3) + "\n" + BitUtil.LITTLE.toBitString(edge.getFlags())); - assertEquals(Integer.MAX_VALUE / 3, edge.getFlags().ints[0]); + assertEquals(Integer.MAX_VALUE / 3, intsRef.ints[0]); graph.close(); graph = new BaseGraph.Builder(manager).create(); @@ -727,21 +784,21 @@ public void testDontGrowOnUpdate() { na.setNode(2, 12, 12, 0.4); EdgeIteratorState iter2 = graph.edge(0, 1).setDistance(100).set(carAccessEnc, true, true); - final BaseGraph baseGraph = (BaseGraph) graph.getBaseGraph(); - assertEquals(4, baseGraph.getMaxGeoRef()); + final BaseGraph baseGraph = graph.getBaseGraph(); + assertEquals(1, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 9)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 2 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5, 5, 6, 7)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 2 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3, 3, 4, 5)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); + assertEquals(1 + 2 + 4 * 11, baseGraph.getMaxGeoRef()); iter2.setWayGeometry(Helper.createPointList3D(1, 2, 3)); - assertEquals(4 + (1 + 12), baseGraph.getMaxGeoRef()); - iter2.setWayGeometry(Helper.createPointList3D(1.5, 1, 0, 2, 3, 0)); - assertEquals(4 + (1 + 12) + (1 + 6), baseGraph.getMaxGeoRef()); + assertEquals(1 + 2 + 4 * 11, baseGraph.getMaxGeoRef()); + assertThrows(IllegalStateException.class, () -> iter2.setWayGeometry(Helper.createPointList3D(1.5, 1, 0, 2, 3, 0))); + assertEquals(1 + 2 + 4 * 11, baseGraph.getMaxGeoRef()); EdgeIteratorState iter1 = graph.edge(0, 2).setDistance(200).set(carAccessEnc, true, true); iter1.setWayGeometry(Helper.createPointList3D(3.5, 4.5, 0, 5, 6, 0)); - assertEquals(4 + (1 + 12) + (1 + 6) + (1 + 6), baseGraph.getMaxGeoRef()); + assertEquals(1 + 2 + 4 * 11 + (2 + 2 * 11), baseGraph.getMaxGeoRef()); } @Test diff --git a/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java b/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java index 06b71845e97..819e3850d72 100644 --- a/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java +++ b/core/src/test/java/com/graphhopper/storage/BaseGraphTest.java @@ -17,19 +17,22 @@ */ package com.graphhopper.storage; +import com.carrotsearch.hppc.IntArrayList; import com.graphhopper.routing.ev.EnumEncodedValue; import com.graphhopper.routing.ev.RoadClass; -import com.graphhopper.search.KVStorage.KeyValue; +import com.graphhopper.search.KVStorage.KValue; import com.graphhopper.util.*; import com.graphhopper.util.shapes.BBox; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; -import java.util.ArrayList; -import java.util.List; +import java.util.LinkedHashMap; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; import static com.graphhopper.util.EdgeIteratorState.REVERSE_STATE; import static com.graphhopper.util.FetchMode.*; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -39,22 +42,18 @@ public class BaseGraphTest extends AbstractGraphStorageTester { @Override public BaseGraph createGHStorage(String location, boolean enabled3D) { // reduce segment size in order to test the case where multiple segments come into the game - BaseGraph gs = newGHStorage(new RAMDirectory(location), enabled3D, defaultSize / 2); + BaseGraph gs = newGHStorage(new GHDirectory(location, DAType.RAM), enabled3D); gs.create(defaultSize); return gs; } protected BaseGraph newGHStorage(Directory dir, boolean enabled3D) { - return newGHStorage(dir, enabled3D, -1); - } - - protected BaseGraph newGHStorage(Directory dir, boolean enabled3D, int segmentSize) { - return new BaseGraph.Builder(encodingManager).setDir(dir).set3D(enabled3D).setSegmentSize(segmentSize).build(); + return new BaseGraph.Builder(encodingManager).setDir(dir).set3D(enabled3D).build(); } @Test public void testSave_and_fileFormat() { - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), true).create(defaultSize); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM_STORE), true).create(defaultSize); NodeAccess na = graph.getNodeAccess(); assertTrue(na.is3D()); na.setNode(0, 10, 10, 0); @@ -70,20 +69,21 @@ public void testSave_and_fileFormat() { graph.edge(9, 11).setDistance(200).set(carAccessEnc, true, true); graph.edge(1, 2).setDistance(120).set(carAccessEnc, true, false); - iter1.setKeyValues(KeyValue.createKV(STREET_NAME, "named street1")); - iter2.setKeyValues(KeyValue.createKV(STREET_NAME, "named street2")); + iter1.setKeyValues(Map.of(STREET_NAME, new KValue("named street1"))); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("named street2"))); - List list = new ArrayList<>(); - list.add(new KeyValue("keyA", "FORWARD", true, false)); - list.add(new KeyValue("keyB", "BACKWARD", false, true)); - list.add(new KeyValue("keyC", "BOTH", true, true)); - iter3.setKeyValues(list); + Map map = new LinkedHashMap<>(); + map.put("keyA", new KValue("FORWARD", null)); + map.put("keyB", new KValue(null, "BACKWARD")); + map.put("keyC", new KValue("BOTH")); + map.put("keyD", new KValue("BOTH2", "BOTH2")); + iter3.setKeyValues(map); checkGraph(graph); graph.flush(); graph.close(); - graph = newGHStorage(new MMapDirectory(defaultGraphLoc), true); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.MMAP), true); graph.loadExisting(); assertEquals(12, graph.getNodes()); @@ -92,8 +92,8 @@ public void testSave_and_fileFormat() { assertEquals("named street1", graph.getEdgeIteratorState(iter1.getEdge(), iter1.getAdjNode()).getName()); assertEquals("named street2", graph.getEdgeIteratorState(iter2.getEdge(), iter2.getAdjNode()).getName()); iter3 = graph.getEdgeIteratorState(iter3.getEdge(), iter3.getAdjNode()); - assertEquals(list, iter3.getKeyValues()); - assertEquals(list, iter3.detach(true).getKeyValues()); + assertEquals(map, iter3.getKeyValues()); + assertEquals(map, iter3.detach(true).getKeyValues()); assertEquals("FORWARD", iter3.getValue("keyA")); assertNull(iter3.getValue("keyB")); @@ -101,6 +101,7 @@ public void testSave_and_fileFormat() { assertNull(iter3.detach(true).getValue("keyA")); assertEquals("BACKWARD", iter3.detach(true).getValue("keyB")); assertEquals("BOTH", iter3.detach(true).getValue("keyC")); + assertEquals("BOTH2", iter3.getValue("keyD")); GHUtility.setSpeed(60, true, true, carAccessEnc, carSpeedEnc, graph.edge(3, 4).setDistance(123)). setWayGeometry(Helper.createPointList3D(4.4, 5.5, 0, 6.6, 7.7, 0)); @@ -109,14 +110,14 @@ public void testSave_and_fileFormat() { @Test public void testSave_and_Freeze() { - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), true).create(defaultSize); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM_STORE), true).create(defaultSize); graph.edge(1, 0); graph.freeze(); graph.flush(); graph.close(); - graph = newGHStorage(new MMapDirectory(defaultGraphLoc), true); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.MMAP), true); graph.loadExisting(); assertEquals(2, graph.getNodes()); assertTrue(graph.isFrozen()); @@ -165,12 +166,12 @@ protected void checkGraph(Graph g) { @Test public void testDoThrowExceptionIfDimDoesNotMatch() { - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), false); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM_STORE), false); graph.create(1000); graph.flush(); graph.close(); - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), true); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM_STORE), true); assertThrows(Exception.class, () -> graph.loadExisting()); } @@ -271,7 +272,7 @@ public void outOfBounds() { public void setGetFlagsRaw() { BaseGraph graph = new BaseGraph.Builder(1).create(); EdgeIteratorState edge = graph.edge(0, 1); - IntsRef flags = new IntsRef(graph.getIntsForFlags()); + IntsRef flags = encodingManager.createEdgeFlags(); flags.ints[0] = 10; edge.setFlags(flags); assertEquals(10, edge.getFlags().ints[0]); @@ -289,4 +290,158 @@ public void setGetFlags() { edge.set(rcEnc, RoadClass.CORRIDOR); assertEquals(RoadClass.CORRIDOR, edge.get(rcEnc)); } + + @Test + public void copyEdge() { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(3, 5).set(rcEnc, RoadClass.LIVING_STREET); + EdgeIteratorState edge2 = graph.edge(3, 5).set(rcEnc, RoadClass.MOTORWAY); + EdgeIteratorState edge3 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), false); + assertEquals(RoadClass.LIVING_STREET, edge1.get(rcEnc)); + assertEquals(RoadClass.MOTORWAY, edge2.get(rcEnc)); + assertEquals(edge1.get(rcEnc), edge3.get(rcEnc)); + assertEquals(edge1.get(rcEnc), edge4.get(rcEnc)); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge1, e -> e.set(rcEnc, RoadClass.FOOTWAY)); + assertEquals(RoadClass.FOOTWAY, edge1.get(rcEnc)); + assertEquals(RoadClass.FOOTWAY, edge3.get(rcEnc)); + // edge4 was not changed because it was copied with reuseGeometry=false + assertEquals(RoadClass.LIVING_STREET, edge4.get(rcEnc)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void copyEdge_multiple(boolean withGeometries) { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(1, 2).set(rcEnc, RoadClass.FOOTWAY); + EdgeIteratorState edge2 = graph.edge(1, 3).set(rcEnc, RoadClass.MOTORWAY); + EdgeIteratorState edge3 = graph.edge(1, 4).set(rcEnc, RoadClass.CYCLEWAY); + if (withGeometries) { + edge1.setWayGeometry(Helper.createPointList(1.5, 1, 0, 2, 3, 0)); + edge2.setWayGeometry(Helper.createPointList(1.5, 1, 1, 2, 3, 5)); + edge3.setWayGeometry(Helper.createPointList(1.5, 1, 2, 2, 3, 6)); + } + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge5 = graph.copyEdge(edge3.getEdge(), true); + EdgeIteratorState edge6 = graph.copyEdge(edge3.getEdge(), true); + EdgeExplorer explorer = graph.createEdgeExplorer(); + graph.forEdgeAndCopiesOfEdge(explorer, edge1, e -> e.set(rcEnc, RoadClass.PATH)); + assertEquals(RoadClass.PATH, edge1.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge3.get(rcEnc)); + assertEquals(RoadClass.PATH, edge4.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge5.get(rcEnc)); + assertEquals(RoadClass.CYCLEWAY, edge6.get(rcEnc)); + graph.forEdgeAndCopiesOfEdge(explorer, edge6, e -> e.set(rcEnc, RoadClass.OTHER)); + assertEquals(RoadClass.PATH, edge1.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge3.get(rcEnc)); + assertEquals(RoadClass.PATH, edge4.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge5.get(rcEnc)); + assertEquals(RoadClass.OTHER, edge6.get(rcEnc)); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + public void forEdgeAndCopiesOfEdge_noCopyNoGeo(boolean withGeometries) { + BaseGraph graph = createGHStorage(); + EdgeIteratorState edge1 = graph.edge(0, 1); + EdgeIteratorState edge2 = graph.edge(1, 2); + if (withGeometries) { + edge1.setWayGeometry(Helper.createPointList(1.5, 1, 0, 2, 3, 0)); + edge2.setWayGeometry(Helper.createPointList(3, 0, 4, 5, 4.5, 5.5)); + } + IntArrayList edges = new IntArrayList(); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge2, e -> { + edges.add(e.getEdge()); + }); + assertEquals(IntArrayList.from(edge2.getEdge()), edges); + + edges.clear(); + EdgeIteratorState edge3 = graph.copyEdge(edge2.getEdge(), true); + graph.forEdgeAndCopiesOfEdge(graph.createEdgeExplorer(), edge2, e -> { + edges.add(e.getEdge()); + }); + assertEquals(IntArrayList.from(edge3.getEdge(), edge2.getEdge()), edges); + } + + @Test + public void copyEdge_changeGeometry() { + BaseGraph graph = createGHStorage(); + EnumEncodedValue rcEnc = encodingManager.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); + EdgeIteratorState edge1 = graph.edge(1, 2).set(rcEnc, RoadClass.FOOTWAY); + EdgeIteratorState edge2 = graph.edge(1, 3).set(rcEnc, RoadClass.FOOTWAY).setWayGeometry(Helper.createPointList(0, 1, 2, 3)); + EdgeIteratorState edge3 = graph.edge(1, 4).set(rcEnc, RoadClass.FOOTWAY).setWayGeometry(Helper.createPointList(4, 5, 6, 7)); + EdgeIteratorState edge4 = graph.copyEdge(edge1.getEdge(), true); + EdgeIteratorState edge5 = graph.copyEdge(edge3.getEdge(), true); + + // after copying an edge we can no longer change the geometry + assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge1.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(1.5, 1, 5, 4))); + // after setting the geometry once we can change it again + graph.getEdgeIteratorState(edge2.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(2, 3, 4, 5)); + // ... but not if it is longer than before + IllegalStateException e = assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge2.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(2, 3, 4, 5, 6, 7))); + assertTrue(e.getMessage().contains("This edge already has a way geometry so it cannot be changed to a bigger geometry"), e.getMessage()); + // it's the same for edges with geometry that were copied: + graph.getEdgeIteratorState(edge3.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(6, 7, 8, 9)); + e = assertThrows(IllegalStateException.class, () -> graph.getEdgeIteratorState(edge3.getEdge(), Integer.MIN_VALUE).setWayGeometry(Helper.createPointList(0, 1, 6, 7, 8, 9))); + assertTrue(e.getMessage().contains("This edge already has a way geometry so it cannot be changed to a bigger geometry"), e.getMessage()); + } + + @Test + public void testGeoRef() { + BaseGraph graph = createGHStorage(); + BaseGraphNodesAndEdges ne = graph.getStore(); + ne.setGeoRef(0, 123); + assertEquals(123, ne.getGeoRef(0)); + ne.setGeoRef(0, -123); + assertEquals(-123, ne.getGeoRef(0)); + ne.setGeoRef(0, 1L << 38); + assertEquals(1L << 38, ne.getGeoRef(0)); + + // 1000_0000 0000_0000 0000_0000 0000_0000 0000_0000 + assertThrows(IllegalArgumentException.class, () -> ne.setGeoRef(0, 1L << 39)); + graph.close(); + } + + @Test + public void testMaxPillarNodes() { + // 1. Use -1 to use the default segment size (1MB). + BaseGraph graph = newGHStorage(new GHDirectory("", DAType.RAM), false); + graph.create(defaultSize); + + // 2. Create the massive point list (65535 nodes) + int maxNodes = 65535; + PointList list = new PointList(maxNodes, false); + for (int i = 0; i < maxNodes; i++) { + list.add(1.0 + i * 0.0001, 2.0 + i * 0.0001); + } + + // 3. Store and Fetch + EdgeIteratorState edge = graph.edge(0, 1).setDistance(100); + edge.setWayGeometry(list); + + PointList fetched = edge.fetchWayGeometry(FetchMode.PILLAR_ONLY); + assertEquals(maxNodes, fetched.size()); + assertEquals(1.0, fetched.getLat(0), 1e-6); + assertEquals(1.0 + (maxNodes - 1) * 0.0001, fetched.getLat(maxNodes - 1), 1e-6); + + graph.close(); + } + + @Test + public void testPillarNodesOverflow() { + BaseGraph graph = createGHStorage(); + int tooManyNodes = 65536; + PointList list = new PointList(tooManyNodes, false); + for (int i = 0; i < tooManyNodes; i++) { + list.add(1.0, 2.0); + } + + EdgeIteratorState edge = graph.edge(0, 1); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, () -> { + edge.setWayGeometry(list); + }); + assertTrue(e.getMessage().contains("Too many pillar nodes")); + } } diff --git a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java index addeff1bf63..4ebbefc9bbe 100644 --- a/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java +++ b/core/src/test/java/com/graphhopper/storage/BaseGraphWithTurnCostsTest.java @@ -18,16 +18,18 @@ package com.graphhopper.storage; import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.RoadClass; import com.graphhopper.routing.ev.TurnCost; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.search.KVStorage.KeyValue; +import com.graphhopper.search.KVStorage; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.Helper; import org.junit.jupiter.api.Test; +import java.util.Map; import java.util.Random; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -44,23 +46,19 @@ protected EncodingManager createEncodingManager() { return EncodingManager.start() .add(carAccessEnc).add(carSpeedEnc).addTurnCostEncodedValue(turnCostEnc) .add(footAccessEnc).add(footSpeedEnc) + .add(RoadClass.create()) .build(); } @Override - protected BaseGraph newGHStorage(Directory dir, boolean is3D) { - return newGHStorage(dir, is3D, -1); - } - - @Override - protected BaseGraph newGHStorage(Directory dir, boolean enabled3D, int segmentSize) { - return new BaseGraph.Builder(encodingManager).setDir(dir).set3D(enabled3D).withTurnCosts(true).setSegmentSize(segmentSize).build(); + protected BaseGraph newGHStorage(Directory dir, boolean enabled3D) { + return new BaseGraph.Builder(encodingManager).setDir(dir).set3D(enabled3D).withTurnCosts(true).build(); } @Override @Test public void testSave_and_fileFormat() { - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, true), true).create(defaultSize); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM_STORE), true).create(defaultSize); NodeAccess na = graph.getNodeAccess(); assertTrue(na.is3D()); na.setNode(0, 10, 10, 0); @@ -79,14 +77,14 @@ public void testSave_and_fileFormat() { setTurnCost(iter2.getEdge(), 0, iter1.getEdge(), 666); setTurnCost(iter1.getEdge(), 1, iter2.getEdge(), 815); - iter1.setKeyValues(KeyValue.createKV(STREET_NAME, "named street1")); - iter2.setKeyValues(KeyValue.createKV(STREET_NAME, "named street2")); + iter1.setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue( "named street1"))); + iter2.setKeyValues(Map.of(STREET_NAME, new KVStorage.KValue( "named street2"))); checkGraph(graph); graph.flush(); graph.close(); - graph = newGHStorage(new MMapDirectory(defaultGraphLoc), true); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.MMAP), true); graph.loadExisting(); assertEquals(12, graph.getNodes()); @@ -107,12 +105,10 @@ public void testSave_and_fileFormat() { @Test public void testEnsureCapacity() { - graph = newGHStorage(new MMapDirectory(defaultGraphLoc), false, 128); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.MMAP), false); graph.create(100); // 100 is the minimum size TurnCostStorage turnCostStorage = graph.getTurnCostStorage(); - // assert that turnCostStorage can hold 104 turn cost entries at the beginning - assertEquals(128, turnCostStorage.getCapacity()); Random r = new Random(); @@ -132,23 +128,18 @@ public void testEnsureCapacity() { graph.edge(nodeId, 50).setDistance(r.nextDouble()).set(carAccessEnc, true, true); } - // add 100 turn cost entries around node 50 - for (int edgeId = 0; edgeId < 50; edgeId++) { + // add turn cost entries around node 50 + for (int edgeId = 0; edgeId < 52; edgeId++) { setTurnCost(edgeId, 50, edgeId + 50, 1337); setTurnCost(edgeId + 50, 50, edgeId, 1337); } - setTurnCost(0, 50, 1, 1337); - assertEquals(104, turnCostStorage.getCapacity() / 16); // we are still good here - setTurnCost(0, 50, 2, 1337); - // A new segment should be added, which will support 128 / 16 = 8 more entries. - assertEquals(112, turnCostStorage.getCapacity() / 16); } @Test public void testInitializeTurnCost() { - graph = newGHStorage(new RAMDirectory(defaultGraphLoc, false), true).create(defaultSize); + graph = newGHStorage(new GHDirectory(defaultGraphLoc, DAType.RAM), true).create(defaultSize); NodeAccess na = graph.getNodeAccess(); // turn cost index is initialized in BaseGraph.initNodeRefs diff --git a/core/src/test/java/com/graphhopper/storage/CHStorageTest.java b/core/src/test/java/com/graphhopper/storage/CHStorageTest.java index 4524f8b7f3c..b0542bf880c 100644 --- a/core/src/test/java/com/graphhopper/storage/CHStorageTest.java +++ b/core/src/test/java/com/graphhopper/storage/CHStorageTest.java @@ -6,15 +6,14 @@ import java.nio.file.Path; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class CHStorageTest { @Test void setAndGetLevels() { - RAMDirectory dir = new RAMDirectory(); - CHStorage store = new CHStorage(dir, "ch1", -1, false); + GHDirectory dir = new GHDirectory("", DAType.RAM); + CHStorage store = new CHStorage(dir, "ch1", false); store.create(30, 5); assertEquals(0, store.getLevel(store.toNodePointer(10))); store.setLevel(store.toNodePointer(10), 100); @@ -27,7 +26,7 @@ void setAndGetLevels() { void createAndLoad(@TempDir Path path) { { GHDirectory dir = new GHDirectory(path.toAbsolutePath().toString(), DAType.RAM_INT_STORE); - CHStorage chStorage = new CHStorage(dir, "car", -1, false); + CHStorage chStorage = new CHStorage(dir, "car", false); // we have to call create, because we want to create a new storage not load an existing one chStorage.create(5, 3); assertEquals(0, chStorage.shortcutNodeBased(0, 1, PrepareEncoder.getScFwdDir(), 10, 3, 5)); @@ -42,7 +41,7 @@ void createAndLoad(@TempDir Path path) { } { GHDirectory dir = new GHDirectory(path.toAbsolutePath().toString(), DAType.RAM_INT_STORE); - CHStorage chStorage = new CHStorage(dir, "car", -1, false); + CHStorage chStorage = new CHStorage(dir, "car", false); // this time we load from disk chStorage.loadExisting(); assertEquals(4, chStorage.getShortcuts()); @@ -58,21 +57,28 @@ void createAndLoad(@TempDir Path path) { @Test public void testBigWeight() { - CHStorage g = new CHStorage(new RAMDirectory(), "abc", 1024, false); + CHStorage g = new CHStorage(new GHDirectory("", DAType.RAM), "abc", false); g.shortcutNodeBased(0, 0, 0, 10, 0, 1); - g.setWeight(0, Integer.MAX_VALUE / 1000d + 1000); - assertEquals(Integer.MAX_VALUE / 1000d + 1000, g.getWeight(0)); + g.setWeight(0, (1L << 32) - 3); + assertEquals((1L << 32) - 3, g.getWeight(0)); - g.setWeight(0, ((long) Integer.MAX_VALUE << 1) / 1000d - 0.001); - assertEquals(((long) Integer.MAX_VALUE << 1) / 1000d - 0.001, g.getWeight(0), 0.001); - - g.setWeight(0, ((long) Integer.MAX_VALUE << 1) / 1000d); - assertTrue(Double.isInfinite(g.getWeight(0))); - g.setWeight(0, ((long) Integer.MAX_VALUE << 1) / 1000d + 1); + g.setWeight(0, (1L << 32) - 2); assertTrue(Double.isInfinite(g.getWeight(0))); - g.setWeight(0, ((long) Integer.MAX_VALUE << 1) / 1000d + 100); + + g.setWeight(0, 5.e9); assertTrue(Double.isInfinite(g.getWeight(0))); + + g.setWeight(0, 0); + assertEquals(0, g.getWeight(0)); + + assertThrows(IllegalArgumentException.class, () -> g.setWeight(0, 0.0000001)); + assertThrows(IllegalArgumentException.class, () -> g.setWeight(0, 0.0001)); + assertThrows(IllegalArgumentException.class, () -> g.setWeight(0, 0.1)); + assertThrows(IllegalArgumentException.class, () -> g.setWeight(0, -0.1)); + assertThrows(IllegalArgumentException.class, () -> g.setWeight(0, -1)); + + } @Test @@ -84,4 +90,4 @@ public void testLargeNodeA() { assertTrue(access.getInt(0) < 0); assertEquals(Integer.MAX_VALUE, access.getInt(0) >>> 1); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/storage/DataAccessTest.java b/core/src/test/java/com/graphhopper/storage/DataAccessTest.java index caee43a03b0..e957b707e5a 100644 --- a/core/src/test/java/com/graphhopper/storage/DataAccessTest.java +++ b/core/src/test/java/com/graphhopper/storage/DataAccessTest.java @@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Random; import static org.junit.jupiter.api.Assertions.*; @@ -255,4 +256,122 @@ public void testSet_Get_Short_Long() { } da.close(); } + + @Test + public void testTrimTo() { + DataAccess da = createDataAccess(name, 128); + da.create(128); + // fill 4 segments + da.ensureCapacity(4 * 128); + assertEquals(4, da.getSegments()); + assertEquals(4 * 128, da.getCapacity()); + + // write data in first two segments + da.setInt(0, 111); + da.setInt(100, 222); + da.setInt(200, 333); + + // trim to 2 segments + da.trimTo(2 * 128); + assertEquals(2, da.getSegments()); + assertEquals(2 * 128, da.getCapacity()); + + // data in kept segments survives + assertEquals(111, da.getInt(0)); + assertEquals(222, da.getInt(100)); + + // flush, close, reload — verify data and capacity persist + da.flush(); + da.close(); + + da = createDataAccess(name, 128); + assertTrue(da.loadExisting()); + assertEquals(2, da.getSegments()); + assertEquals(2 * 128, da.getCapacity()); + assertEquals(111, da.getInt(0)); + assertEquals(222, da.getInt(100)); + + // verify file is actually truncated + File file = new File(directory + name); + if (file.exists()) { + long expectedMaxFileSize = AbstractDataAccess.HEADER_OFFSET + 2 * 128; + assertTrue(file.length() <= expectedMaxFileSize, + "file should be truncated but was " + file.length() + " > " + expectedMaxFileSize); + } + da.close(); + } + + @Test + public void testTrimToRoundsUpToSegmentBoundary() { + DataAccess da = createDataAccess(name, 128); + da.create(128); + da.ensureCapacity(4 * 128); + assertEquals(4, da.getSegments()); + + // trimTo with a non-segment-aligned value should round up + da.trimTo(128 + 1); + assertEquals(2, da.getSegments()); + assertEquals(2 * 128, da.getCapacity()); + da.close(); + } + + @Test + public void testTrimToSameCapacityIsNoop() { + DataAccess da = createDataAccess(name, 128); + da.create(128); + da.ensureCapacity(2 * 128); + da.setInt(0, 42); + da.trimTo(2 * 128); + assertEquals(2, da.getSegments()); + assertEquals(42, da.getInt(0)); + da.close(); + } + + @Test + public void testTrimToIllegal() { + DataAccess da = createDataAccess(name, 128); + da.create(128); + assertThrows(IllegalArgumentException.class, () -> da.trimTo(da.getCapacity() + 1)); + assertThrows(IllegalArgumentException.class, () -> da.trimTo(-1)); + da.close(); + } + + @Test + public void testTrimToZero() { + DataAccess da = createDataAccess(name, 128); + da.create(128); + da.ensureCapacity(3 * 128); + assertEquals(3, da.getSegments()); + + da.trimTo(0); + assertEquals(0, da.getSegments()); + assertEquals(0, da.getCapacity()); + da.close(); + } + + @Test + public void testPadding() { + DataAccess da = createDataAccess(name); + da.create(10); + da.ensureCapacity(12_800); + assertEquals(100, da.getSegments()); + int val = Integer.MAX_VALUE / 2; + for (int i = 0; i < 10_000; i++) { + da.setInt(i, val * i); + assertEquals(val * i, da.getInt(i), "idx " + i); + da.setInt(i, -val * i); + assertEquals(-val * i, da.getInt(i), "idx " + i); + } + + Random rand = new Random(0); + for (int i = 0; i < 10_000; i++) { + val = 1 << rand.nextInt(32) + rand.nextInt(); + da.setInt(i, val); + assertEquals(val, da.getInt(i), "idx " + i); + da.setInt(i, -val); + assertEquals(-val, da.getInt(i), "idx " + i); + } + + da.close(); + } } diff --git a/core/src/test/java/com/graphhopper/storage/GraphStorageViaMMapTest.java b/core/src/test/java/com/graphhopper/storage/GraphStorageViaMMapTest.java index c87ae09528e..4d5899a21de 100644 --- a/core/src/test/java/com/graphhopper/storage/GraphStorageViaMMapTest.java +++ b/core/src/test/java/com/graphhopper/storage/GraphStorageViaMMapTest.java @@ -23,7 +23,7 @@ public class GraphStorageViaMMapTest extends AbstractGraphStorageTester { @Override public BaseGraph createGHStorage(String location, boolean is3D) { - BaseGraph gs = new BaseGraph.Builder(encodingManager).set3D(is3D).setDir(new MMapDirectory(location)).setSegmentSize(defaultSize / 2).build(); + BaseGraph gs = new BaseGraph.Builder(encodingManager).set3D(is3D).setDir(new GHDirectory(location, DAType.MMAP)).build(); gs.create(defaultSize); return gs; } diff --git a/core/src/test/java/com/graphhopper/storage/MMapDirectoryTest.java b/core/src/test/java/com/graphhopper/storage/MMapDirectoryTest.java index 7edefa128ff..a3512aa48ce 100644 --- a/core/src/test/java/com/graphhopper/storage/MMapDirectoryTest.java +++ b/core/src/test/java/com/graphhopper/storage/MMapDirectoryTest.java @@ -23,6 +23,6 @@ public class MMapDirectoryTest extends AbstractDirectoryTester { @Override Directory createDir() { - return new MMapDirectory(location).create(); + return new GHDirectory(location, DAType.MMAP).create(); } } diff --git a/core/src/test/java/com/graphhopper/storage/RAMDirectoryTest.java b/core/src/test/java/com/graphhopper/storage/RAMDirectoryTest.java index 6c66ce0e0bc..42bc2e38e59 100644 --- a/core/src/test/java/com/graphhopper/storage/RAMDirectoryTest.java +++ b/core/src/test/java/com/graphhopper/storage/RAMDirectoryTest.java @@ -23,6 +23,6 @@ public class RAMDirectoryTest extends AbstractDirectoryTester { @Override Directory createDir() { - return new RAMDirectory(location, true).create(); + return new GHDirectory(location, DAType.RAM_STORE).create(); } } diff --git a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java index 26aa8a68a23..9156cda44a4 100644 --- a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java +++ b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java @@ -105,12 +105,12 @@ public Stream provideArguments(ExtensionContext context) { @ArgumentsSource(FixtureProvider.class) public void testUnpacking(Fixture f) { // 0-1-2-3-4-5-6 - f.graph.edge(0, 1).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(1, 2).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(2, 3).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(3, 4).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(4, 5).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(5, 6).setDistance(1).set(f.speedEnc, 20, 10); // edge 5 + f.graph.edge(0, 1).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(1, 2).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(2, 3).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(3, 4).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(4, 5).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(5, 6).setDistance(100).set(f.speedEnc, 20, 10); // edge 5 f.freeze(); f.setCHLevels(1, 3, 5, 4, 2, 0, 6); @@ -127,9 +127,9 @@ public void testUnpacking(Fixture f) { assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.baseNodes); assertEquals(IntArrayList.from(1, 2, 3, 4, 5, 6), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); if (f.edgeBased) { assertEquals(IntArrayList.from(PREV_EDGE, 0, 1, 2, 3, 4), visitor.prevOrNextEdgeIds); } @@ -144,9 +144,9 @@ public void testUnpacking(Fixture f) { assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds); assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.baseNodes); assertEquals(IntArrayList.from(6, 5, 4, 3, 2, 1), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); if (f.edgeBased) { assertEquals(IntArrayList.from(4, 3, 2, 1, 0, PREV_EDGE), visitor.prevOrNextEdgeIds); } @@ -159,9 +159,9 @@ public void testUnpacking(Fixture f) { assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds); assertEquals(IntArrayList.from(6, 5, 4, 3, 2, 1), visitor.baseNodes); assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); if (f.edgeBased) { assertEquals(IntArrayList.from(NEXT_EDGE, 5, 4, 3, 2, 1), visitor.prevOrNextEdgeIds); } @@ -174,9 +174,9 @@ public void testUnpacking(Fixture f) { assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds); assertEquals(IntArrayList.from(1, 2, 3, 4, 5, 6), visitor.baseNodes); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); if (f.edgeBased) { assertEquals(IntArrayList.from(1, 2, 3, 4, 5, NEXT_EDGE), visitor.prevOrNextEdgeIds); } @@ -192,12 +192,12 @@ public void loopShortcut(Fixture f) { // 2 4 // \ / // 0 - 1 - 5 - f.graph.edge(0, 1).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(1, 2).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(2, 3).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(3, 4).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(4, 1).setDistance(1).set(f.speedEnc, 20, 10); - f.graph.edge(1, 5).setDistance(1).set(f.speedEnc, 20, 10); + f.graph.edge(0, 1).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(1, 2).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(2, 3).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(3, 4).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(4, 1).setDistance(100).set(f.speedEnc, 20, 10); + f.graph.edge(1, 5).setDistance(100).set(f.speedEnc, 20, 10); f.freeze(); f.setCHLevels(2, 4, 3, 1, 5, 0); @@ -214,9 +214,9 @@ public void loopShortcut(Fixture f) { assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 1), visitor.baseNodes); assertEquals(IntArrayList.from(1, 2, 3, 4, 1, 5), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); assertEquals(IntArrayList.from(PREV_EDGE, 0, 1, 2, 3, 4), visitor.prevOrNextEdgeIds); } @@ -227,9 +227,9 @@ public void loopShortcut(Fixture f) { assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds); assertEquals(IntArrayList.from(1, 4, 3, 2, 1, 0), visitor.baseNodes); assertEquals(IntArrayList.from(5, 1, 4, 3, 2, 1), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); assertEquals(IntArrayList.from(4, 3, 2, 1, 0, PREV_EDGE), visitor.prevOrNextEdgeIds); } @@ -240,9 +240,9 @@ public void loopShortcut(Fixture f) { assertEquals(IntArrayList.from(5, 4, 3, 2, 1, 0), visitor.edgeIds); assertEquals(IntArrayList.from(5, 1, 4, 3, 2, 1), visitor.baseNodes); assertEquals(IntArrayList.from(1, 4, 3, 2, 1, 0), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); assertEquals(IntArrayList.from(NEXT_EDGE, 5, 4, 3, 2, 1), visitor.prevOrNextEdgeIds); } @@ -253,27 +253,27 @@ public void loopShortcut(Fixture f) { assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 5), visitor.edgeIds); assertEquals(IntArrayList.from(1, 2, 3, 4, 1, 5), visitor.baseNodes); assertEquals(IntArrayList.from(0, 1, 2, 3, 4, 1), visitor.adjNodes); - assertEquals(DoubleArrayList.from(0.05, 0.05, 0.05, 0.05, 0.05, 0.05), visitor.weights); - assertEquals(DoubleArrayList.from(1, 1, 1, 1, 1, 1), visitor.distances); - assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.times); + assertEquals(DoubleArrayList.from(50, 50, 50, 50, 50, 50), visitor.weights); + assertEquals(DoubleArrayList.from(100, 100, 100, 100, 100, 100), visitor.distances); + assertEquals(DoubleArrayList.from(5000, 5000, 5000, 5000, 5000, 5000), visitor.times); assertEquals(IntArrayList.from(1, 2, 3, 4, 5, NEXT_EDGE), visitor.prevOrNextEdgeIds); } } @ParameterizedTest @ArgumentsSource(FixtureProvider.class) - public void withTurnWeighting(Fixture f) { + public void withCalcTurnWeight(Fixture f) { assumeTrue(f.edgeBased); // 2 5 3 2 1 4 6 turn costs -> // prev 0-1-2-3-4-5-6 next // 1 0 1 4 2 3 2 turn costs <- EdgeIteratorState edge0, edge1, edge2, edge3, edge4, edge5; - edge0 = f.graph.edge(0, 1).setDistance(1).set(f.speedEnc, 20, 10); - edge1 = f.graph.edge(1, 2).setDistance(1).set(f.speedEnc, 20, 10); - edge2 = f.graph.edge(2, 3).setDistance(1).set(f.speedEnc, 20, 10); - edge3 = f.graph.edge(3, 4).setDistance(1).set(f.speedEnc, 20, 10); - edge4 = f.graph.edge(4, 5).setDistance(1).set(f.speedEnc, 20, 10); - edge5 = f.graph.edge(5, 6).setDistance(1).set(f.speedEnc, 20, 10); + edge0 = f.graph.edge(0, 1).setDistance(100).set(f.speedEnc, 20, 10); + edge1 = f.graph.edge(1, 2).setDistance(100).set(f.speedEnc, 20, 10); + edge2 = f.graph.edge(2, 3).setDistance(100).set(f.speedEnc, 20, 10); + edge3 = f.graph.edge(3, 4).setDistance(100).set(f.speedEnc, 20, 10); + edge4 = f.graph.edge(4, 5).setDistance(100).set(f.speedEnc, 20, 10); + edge5 = f.graph.edge(5, 6).setDistance(100).set(f.speedEnc, 20, 10); f.freeze(); // turn costs -> @@ -304,32 +304,32 @@ public void withTurnWeighting(Fixture f) { // unpack the shortcut 0->6, traverse original edges in 'forward' order (from node 0 to 6) TurnWeightingVisitor visitor = new TurnWeightingVisitor(f.routingCHGraph); f.visitFwd(10, 6, false, visitor); - assertEquals(6 * 0.05 + 17, visitor.weight, 1.e-3, "wrong weight"); - assertEquals((6 * 50 + 17000), visitor.time, "wrong time"); + assertEquals(6 * 50 + 170, visitor.weight, "wrong weight"); + assertEquals((6 * 5000 + 17000), visitor.time, "wrong time"); } { // unpack the shortcut 0->6, traverse original edges in 'backward' order (from node 6 to 0) TurnWeightingVisitor visitor = new TurnWeightingVisitor(f.routingCHGraph); f.visitFwd(10, 6, true, visitor); - assertEquals(6 * 0.05 + 17, visitor.weight, 1.e-3, "wrong weight"); - assertEquals((6 * 50 + 17000), visitor.time, "wrong time"); + assertEquals(6 * 50 + 170, visitor.weight, "wrong weight"); + assertEquals((6 * 5000 + 17000), visitor.time, "wrong time"); } { // unpack the shortcut 6<-0, traverse original edges in 'forward' order (from node 6 to 0) TurnWeightingVisitor visitor = new TurnWeightingVisitor(f.routingCHGraph); f.visitBwd(10, 0, false, visitor); - assertEquals(6 * 0.05 + 21, visitor.weight, 1.e-3, "wrong weight"); - assertEquals((6 * 50 + 21000), visitor.time, "wrong time"); + assertEquals(6 * 50 + 210, visitor.weight, "wrong weight"); + assertEquals((6 * 5000 + 21000), visitor.time, "wrong time"); } { // unpack the shortcut 6<-0, traverse original edges in 'backward' order (from node 0 to 6) TurnWeightingVisitor visitor = new TurnWeightingVisitor(f.routingCHGraph); f.visitBwd(10, 0, true, visitor); - assertEquals(6 * 0.05 + 21, visitor.weight, 1.e-3, "wrong weight"); - assertEquals((6 * 50 + 21000), visitor.time, "wrong time"); + assertEquals(6 * 50 + 210, visitor.weight, "wrong weight"); + assertEquals((6 * 5000 + 21000), visitor.time, "wrong time"); } } diff --git a/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java b/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java index 8df51886b1f..4160cef6dd3 100644 --- a/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java +++ b/core/src/test/java/com/graphhopper/storage/StorablePropertiesTest.java @@ -33,7 +33,7 @@ */ public class StorablePropertiesTest { Directory createDir(String location, boolean store) { - return new RAMDirectory(location, store).create(); + return new GHDirectory(location, store ? DAType.RAM_STORE : DAType.RAM).create(); } @Test @@ -68,6 +68,30 @@ public void testStore() { Helper.removeDir(new File(dir)); } + @Test + public void testStoreLarge() { + String dir = "./target/test"; + Helper.removeDir(new File(dir)); + StorableProperties instance = new StorableProperties(createDir(dir, true)); + instance.create(1000); + for (int i = 0; i <= 100_000; i++) { + instance.put(Integer.toString(i), "test." + i); + } + + instance.flush(); + long bytesWritten = instance.getCapacity(); + instance.close(); + + instance = new StorableProperties(createDir(dir, true)); + assertTrue(instance.loadExisting()); + assertEquals(bytesWritten, instance.getCapacity()); + assertEquals("test.0", instance.get("0")); + assertEquals("test.100000", instance.get("100000")); + instance.close(); + + Helper.removeDir(new File(dir)); + } + @Test public void testLoadProperties() throws IOException { Map map = new HashMap<>(); diff --git a/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java b/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java index 89bea6fe82b..871c10cb142 100644 --- a/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java +++ b/core/src/test/java/com/graphhopper/storage/TurnCostStorageTest.java @@ -28,6 +28,7 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.stream.IntStream; import static com.graphhopper.util.GHUtility.getEdge; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -59,11 +60,11 @@ public void setup() { // 4 public static void initGraph(BaseGraph g, BooleanEncodedValue accessEnc, DecimalEncodedValue speedEnc) { GHUtility.setSpeed(60, 60, accessEnc, speedEnc, - g.edge(0, 1).setDistance(3), - g.edge(0, 2).setDistance(1), - g.edge(1, 3).setDistance(1), - g.edge(2, 3).setDistance(1), - g.edge(2, 4).setDistance(1)); + g.edge(0, 1).setDistance(300), + g.edge(0, 2).setDistance(100), + g.edge(1, 3).setDistance(100), + g.edge(2, 3).setDistance(100), + g.edge(2, 4).setDistance(100)); } /** @@ -92,6 +93,8 @@ public void testMultipleTurnCosts() { turnCostStorage.set(bikeEnc, edge31, 1, edge10, Double.POSITIVE_INFINITY); turnCostStorage.set(bikeEnc, edge02, 2, edge24, Double.POSITIVE_INFINITY); + assertEquals(turnCostStorage.getTurnCostsCount(), IntStream.range(0, g.getNodes()).map(turnCostStorage::getTurnCostsCount).sum()); + assertEquals(Double.POSITIVE_INFINITY, turnCostStorage.get(carEnc, edge42, 2, edge23), 0); assertEquals(Double.POSITIVE_INFINITY, turnCostStorage.get(bikeEnc, edge42, 2, edge23), 0); diff --git a/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java b/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java index 365c5567365..7b02fc01e27 100644 --- a/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java +++ b/core/src/test/java/com/graphhopper/storage/index/LocationIndexTreeTest.java @@ -79,7 +79,7 @@ public static void initSimpleGraph(Graph g) { } private LocationIndexTree createIndexNoPrepare(Graph g, int resolution) { - Directory dir = new RAMDirectory(); + Directory dir = new GHDirectory("", DAType.RAM); LocationIndexTree tmpIDX = new LocationIndexTree(g, dir); tmpIDX.setResolution(resolution); return tmpIDX; diff --git a/core/src/test/java/com/graphhopper/storage/index/SnapTest.java b/core/src/test/java/com/graphhopper/storage/index/SnapTest.java new file mode 100644 index 00000000000..752f3960761 --- /dev/null +++ b/core/src/test/java/com/graphhopper/storage/index/SnapTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.storage.index; + +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.NodeAccess; +import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.shapes.GHPoint; +import org.junit.jupiter.api.Test; + +import static com.graphhopper.util.DistancePlaneProjection.DIST_PLANE; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SnapTest { + + @Test + void snapToCloseTower() { + // see #3009 + BaseGraph graph = new BaseGraph.Builder(1).create(); + EdgeIteratorState edge = graph.edge(0, 1); + NodeAccess na = graph.getNodeAccess(); + na.setNode(0, 40.000_000, 6.000_000); + na.setNode(1, 40.000_000, 6.000_101); + double queryLat = 40.001_000; + double queryLon = 6.000_1009; + Snap snap = new Snap(queryLat, queryLon); + snap.setClosestEdge(edge); + // We set the base node to the closest node, even though the crossing point is closer to + // the adj node. Not sure if LocationIndexTree can really produce this situation. + snap.setClosestNode(edge.getBaseNode()); + snap.setWayIndex(0); + snap.setSnappedPosition(Snap.Position.EDGE); + // the crossing point is very close to the adj node + GHPoint crossingPoint = DIST_PLANE.calcCrossingPointToEdge(queryLat, queryLon, + na.getLat(edge.getBaseNode()), na.getLon(edge.getBaseNode()), na.getLat(edge.getAdjNode()), na.getLon(edge.getAdjNode())); + double distCrossingTo0 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(edge.getBaseNode()), na.getLon(edge.getBaseNode())); + double distCrossingTo1 = DIST_PLANE.calcDist(crossingPoint.lat, crossingPoint.lon, na.getLat(edge.getAdjNode()), na.getLon(edge.getAdjNode())); + assertEquals(8.594, distCrossingTo0, 1.e-3); + assertEquals(0.008, distCrossingTo1, 1.e-3); + // the snapped point snaps to the adj tower node, so the coordinates must the same + snap.calcSnappedPoint(DIST_PLANE); + assertEquals(na.getLat(snap.getClosestNode()), snap.getSnappedPoint().getLat()); + assertEquals(na.getLon(snap.getClosestNode()), snap.getSnappedPoint().getLon()); + assertEquals(edge.getAdjNode(), snap.getClosestNode()); + } + +} diff --git a/core/src/test/java/com/graphhopper/util/ArrayUtilTest.java b/core/src/test/java/com/graphhopper/util/ArrayUtilTest.java index c5d2982625a..f22d8a81580 100644 --- a/core/src/test/java/com/graphhopper/util/ArrayUtilTest.java +++ b/core/src/test/java/com/graphhopper/util/ArrayUtilTest.java @@ -110,6 +110,15 @@ public void removeConsecutiveDuplicates() { assertEquals(IntArrayList.from(4, 3, 3, 5, 3), IntArrayList.from(brr)); } + @Test + public void removeConsecutiveDuplicates_empty() { + int[] arr = new int[]{}; + assertEquals(0, ArrayUtil.removeConsecutiveDuplicates(arr, arr.length)); + arr = new int[]{3}; + assertEquals(1, ArrayUtil.removeConsecutiveDuplicates(arr, arr.length)); + assertEquals(0, ArrayUtil.removeConsecutiveDuplicates(arr, 0)); + } + @Test public void testWithoutConsecutiveDuplicates() { assertEquals(from(), ArrayUtil.withoutConsecutiveDuplicates(from())); @@ -169,4 +178,4 @@ public void testMerge() { int[] b = {3, 7, 9, 10, 11, 12, 15, 20, 21, 26}; assertEquals(from(2, 3, 6, 7, 8, 9, 10, 11, 12, 15, 20, 21, 26), from(ArrayUtil.merge(a, b))); } -} \ No newline at end of file +} diff --git a/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java b/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java deleted file mode 100644 index 71cadf3f09a..00000000000 --- a/core/src/test/java/com/graphhopper/util/BitUtilLittleTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.util; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author Peter Karich - */ -public class BitUtilLittleTest extends AbstractBitUtilTester { - @Override - BitUtil getBitUtil() { - return BitUtil.LITTLE; - } - - @Test - public void testToBitString() { - assertEquals("0010101010101010101010101010101010101010101010101010101010101010", bitUtil.toBitString(Long.MAX_VALUE / 3)); - assertEquals("0111111111111111111111111111111111111111111111111111111111111111", bitUtil.toBitString(Long.MAX_VALUE)); - - assertEquals("00101010101010101010101010101010", bitUtil.toBitString(bitUtil.fromInt(Integer.MAX_VALUE / 3))); - - assertEquals("10000000000000000000000000000000", bitUtil.toBitString(1L << 63, 32)); - assertEquals("00000000000000000000000000000001", bitUtil.toBitString((1L << 32), 32)); - } - - @Test - public void testFromBitString() { - String str = "001110110"; - assertEquals(str + "0000000", bitUtil.toBitString(bitUtil.fromBitString(str))); - - str = "01011110010111000000111111000111"; - assertEquals(str, bitUtil.toBitString(bitUtil.fromBitString(str))); - - str = "0101111001011100000011111100011"; - assertEquals(str + "0", bitUtil.toBitString(bitUtil.fromBitString(str))); - } - - @Test - public void testCountBitValue() { - assertEquals(1, BitUtil.countBitValue(1)); - assertEquals(2, BitUtil.countBitValue(2)); - assertEquals(2, BitUtil.countBitValue(3)); - assertEquals(3, BitUtil.countBitValue(4)); - assertEquals(3, BitUtil.countBitValue(7)); - assertEquals(4, BitUtil.countBitValue(8)); - assertEquals(5, BitUtil.countBitValue(20)); - } - - @Test - public void testUnsignedConversions() { - long l = Integer.toUnsignedLong(-1); - assertEquals(4294967295L, l); - assertEquals(-1, BitUtil.toSignedInt(l)); - - int intVal = Integer.MAX_VALUE; - long maxInt = intVal; - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - intVal++; - maxInt = Integer.toUnsignedLong(intVal); - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - intVal++; - maxInt = Integer.toUnsignedLong(intVal); - assertEquals(intVal, BitUtil.toSignedInt(maxInt)); - - assertEquals(0xFFFFffffL, (1L << 32) - 1); - assertTrue(0xFFFFffffL > 0L); - } -} diff --git a/core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java b/core/src/test/java/com/graphhopper/util/BitUtilTest.java similarity index 55% rename from core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java rename to core/src/test/java/com/graphhopper/util/BitUtilTest.java index 7aa05a41a93..7c32afb952c 100644 --- a/core/src/test/java/com/graphhopper/util/AbstractBitUtilTester.java +++ b/core/src/test/java/com/graphhopper/util/BitUtilTest.java @@ -20,14 +20,69 @@ import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * @author Peter Karich */ -public abstract class AbstractBitUtilTester { - protected BitUtil bitUtil = getBitUtil(); +public class BitUtilTest { + private static final BitUtil bitUtil = BitUtil.LITTLE; - abstract BitUtil getBitUtil(); + @Test + public void testToBitString() { + assertEquals("0010101010101010101010101010101010101010101010101010101010101010", bitUtil.toBitString(Long.MAX_VALUE / 3)); + assertEquals("0111111111111111111111111111111111111111111111111111111111111111", bitUtil.toBitString(Long.MAX_VALUE)); + + assertEquals("00101010101010101010101010101010", bitUtil.toBitString(bitUtil.fromInt(Integer.MAX_VALUE / 3))); + + assertEquals("10000000000000000000000000000000", bitUtil.toBitString(1L << 63, 32)); + assertEquals("00000000000000000000000000000001", bitUtil.toBitString((1L << 32), 32)); + } + + @Test + public void testFromBitString() { + String str = "001110110"; + assertEquals(str + "0000000", bitUtil.toBitString(bitUtil.fromBitString(str))); + + str = "01011110010111000000111111000111"; + assertEquals(str, bitUtil.toBitString(bitUtil.fromBitString(str))); + + str = "0101111001011100000011111100011"; + assertEquals(str + "0", bitUtil.toBitString(bitUtil.fromBitString(str))); + } + + @Test + public void testCountBitValue() { + assertEquals(1, BitUtil.countBitValue(1)); + assertEquals(2, BitUtil.countBitValue(2)); + assertEquals(2, BitUtil.countBitValue(3)); + assertEquals(3, BitUtil.countBitValue(4)); + assertEquals(3, BitUtil.countBitValue(7)); + assertEquals(4, BitUtil.countBitValue(8)); + assertEquals(5, BitUtil.countBitValue(20)); + } + + @Test + public void testUnsignedConversions() { + long l = Integer.toUnsignedLong(-1); + assertEquals(4294967295L, l); + assertEquals(-1, BitUtil.toSignedInt(l)); + + int intVal = Integer.MAX_VALUE; + long maxInt = intVal; + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + intVal++; + maxInt = Integer.toUnsignedLong(intVal); + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + intVal++; + maxInt = Integer.toUnsignedLong(intVal); + assertEquals(intVal, BitUtil.toSignedInt(maxInt)); + + assertEquals(0xFFFFffffL, (1L << 32) - 1); + assertTrue(0xFFFFffffL > 0L); + } @Test public void testToFloat() { @@ -98,4 +153,14 @@ public void testToLastBitString() { assertEquals("011", bitUtil.toLastBitString(3L, 3)); } + @Test + public void testUInt3() { + byte[] bytes = new byte[3]; + bitUtil.fromUInt3(bytes, 12345678, 0); + assertEquals(12345678, bitUtil.toUInt3(bytes, 0)); + + bytes = new byte[3]; + bitUtil.fromUInt3(bytes, -12345678, 0); + assertEquals(-12345678 & 0x00FF_FFFF, bitUtil.toUInt3(bytes, 0)); + } } diff --git a/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java b/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java index de60e0f1610..d90b500aa24 100644 --- a/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java +++ b/core/src/test/java/com/graphhopper/util/DepthFirstSearchTest.java @@ -67,7 +67,7 @@ public boolean goFurther(int v) { BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); accessEnc.init(evConf); - BaseGraph g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + BaseGraph g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); g.edge(1, 2).setDistance(1).set(accessEnc, true, false); g.edge(1, 5).setDistance(1).set(accessEnc, true, false); g.edge(1, 4).setDistance(1).set(accessEnc, true, false); @@ -103,7 +103,7 @@ public boolean goFurther(int v) { BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); EncodedValue.InitializerConfig evConf = new EncodedValue.InitializerConfig(); accessEnc.init(evConf); - BaseGraph g = new BaseGraph.Builder(evConf.getRequiredInts()).create(); + BaseGraph g = new BaseGraph.Builder(evConf.getRequiredBytes()).create(); g.edge(1, 2).setDistance(1).set(accessEnc, true, false); g.edge(1, 4).setDistance(1).set(accessEnc, true, true); g.edge(1, 3).setDistance(1).set(accessEnc, true, false); diff --git a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java index eb2f2d746ff..7d918cf1544 100644 --- a/core/src/test/java/com/graphhopper/util/GHUtilityTest.java +++ b/core/src/test/java/com/graphhopper/util/GHUtilityTest.java @@ -18,15 +18,6 @@ package com.graphhopper.util; import com.graphhopper.coll.GHIntLongHashMap; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; -import com.graphhopper.routing.util.AllEdgesIterator; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.storage.Graph; -import com.graphhopper.storage.NodeAccess; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.*; diff --git a/core/src/test/java/com/graphhopper/util/InstructionListTest.java b/core/src/test/java/com/graphhopper/util/InstructionListTest.java index 26717a1e23e..faeaa02bf88 100644 --- a/core/src/test/java/com/graphhopper/util/InstructionListTest.java +++ b/core/src/test/java/com/graphhopper/util/InstructionListTest.java @@ -24,9 +24,10 @@ import com.graphhopper.routing.Path; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.routing.util.PriorityCode; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.*; +import com.graphhopper.routing.weighting.SpeedWeighting; +import com.graphhopper.routing.weighting.TurnCostProvider; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.Graph; @@ -34,13 +35,11 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Locale; +import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.json.Statement.If; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; /** @@ -56,7 +55,9 @@ public class InstructionListTest { @BeforeEach public void setUp() { speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); - carManager = EncodingManager.start().add(speedEnc).build(); + carManager = EncodingManager.start().add(speedEnc).add(Roundabout.create()).add(VehicleAccess.create("car")). + add(MaxSpeed.create()).add(RoadClass.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()).build(); } private static List getTurnDescriptions(InstructionList instructionList) { @@ -91,30 +92,30 @@ Graph createTestGraph() { na.setNode(6, 1.0, 1.0); na.setNode(7, 1.0, 1.1); na.setNode(8, 1.0, 1.2); - g.edge(0, 1).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "0-1")); - g.edge(1, 2).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-2")); + g.edge(0, 1).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("0-1"))); + g.edge(1, 2).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); g.edge(0, 3).setDistance(11000).set(speedEnc, 60, 60); - g.edge(1, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-4")); - g.edge(2, 5).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "5-2")); + g.edge(1, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-4"))); + g.edge(2, 5).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("5-2"))); - g.edge(3, 6).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-6")); - g.edge(4, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-7")); - g.edge(5, 8).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "5-8")); + g.edge(3, 6).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-6"))); + g.edge(4, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-7"))); + g.edge(5, 8).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("5-8"))); - g.edge(6, 7).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "6-7")); + g.edge(6, 7).setDistance(11000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("6-7"))); EdgeIteratorState iter = g.edge(7, 8).setDistance(10000).set(speedEnc, 60, 60); PointList list = new PointList(); list.add(1.0, 1.15); list.add(1.0, 1.16); iter.setWayGeometry(list); - iter.setKeyValues(createKV(STREET_NAME, "7-8")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("7-8"))); // missing edge name g.edge(9, 10).setDistance(10000).set(speedEnc, 60, 60); EdgeIteratorState iter2 = g.edge(8, 9).setDistance(20000).set(speedEnc, 60, 60); list.clear(); list.add(1.0, 1.3); - iter2.setKeyValues(createKV(STREET_NAME, "8-9")); + iter2.setKeyValues(Map.of(STREET_NAME, new KValue("8-9"))); iter2.setWayGeometry(list); return g; } @@ -183,11 +184,11 @@ public void testWayList2() { na.setNode(3, 10.0, 10.08); na.setNode(4, 10.1, 10.10); na.setNode(5, 10.2, 10.13); - g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-4")); - g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); EdgeIteratorState iter = g.edge(2, 4).setDistance(100).set(speedEnc, 60, 60); - iter.setKeyValues(createKV(STREET_NAME, "2-4")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("2-4"))); PointList list = new PointList(); list.add(10.20, 10.05); iter.setWayGeometry(list); @@ -222,11 +223,11 @@ public void testNoInstructionIfSameStreet() { na.setNode(3, 10.0, 10.05); na.setNode(4, 10.1, 10.10); na.setNode(5, 10.2, 10.15); - g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "street")); - g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(3, 4).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("street"))); + g.edge(4, 5).setDistance(100).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); EdgeIteratorState iter = g.edge(2, 4).setDistance(100).set(speedEnc, 60, 60); - iter.setKeyValues(createKV(STREET_NAME, "street")); + iter.setKeyValues(Map.of(STREET_NAME, new KValue("street"))); PointList list = new PointList(); list.add(10.20, 10.05); iter.setWayGeometry(list); @@ -299,7 +300,9 @@ public void testNoInstructionIfSlightTurnAndAlternativeIsSharp2() { @Test public void testNoInstructionIfSlightTurnAndAlternativeIsSharp3() { DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true); - EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).build(); + EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).add(RoadClass.create()). + add(VehicleAccess.create("car")).add(RoadClassLink.create()).add(Roundabout.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).build(); EnumEncodedValue rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BaseGraph g = new BaseGraph.Builder(tmpEM).create(); // real world example: https://graphhopper.com/maps/?point=48.411549,15.599567&point=48.411663%2C15.600527&profile=bike @@ -321,9 +324,9 @@ public void testNoInstructionIfSlightTurnAndAlternativeIsSharp3() { g.edge(2, 3).setDistance(20).set(speedEnc, 18, 18); g.edge(2, 4).setDistance(20).set(speedEnc, 4, 4); - g.edge(1, 2).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(createKV(STREET_NAME, "pfarr")); - g.edge(2, 3).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(createKV(STREET_NAME, "pfarr")); - g.edge(2, 4).set(rcEV, RoadClass.PEDESTRIAN).setKeyValues(createKV(STREET_NAME, "markt")); + g.edge(1, 2).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(Map.of(STREET_NAME, new KValue("pfarr"))); + g.edge(2, 3).set(rcEV, RoadClass.RESIDENTIAL).setKeyValues(Map.of(STREET_NAME, new KValue("pfarr"))); + g.edge(2, 4).set(rcEV, RoadClass.PEDESTRIAN).setKeyValues(Map.of(STREET_NAME, new KValue("markt"))); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(1, 3); @@ -337,7 +340,9 @@ public void testNoInstructionIfSlightTurnAndAlternativeIsSharp3() { @Test public void testInstructionIfTurn() { DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 2, true); - EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).build(); + EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).add(RoadClass.create()). + add(VehicleAccess.create("car")).add(RoadClassLink.create()).add(Roundabout.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).build(); EnumEncodedValue rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BaseGraph g = new BaseGraph.Builder(tmpEM).create(); // real world example: https://graphhopper.com/maps/?point=48.412169%2C15.604888&point=48.412251%2C15.60543&profile=bike @@ -373,10 +378,10 @@ public void testInstructionIfTurn() { @Test public void testInstructionIfSlightTurn() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 4, 1, false); - DecimalEncodedValue priorityEnc = new DecimalEncodedValueImpl("priority", 4, PriorityCode.getFactor(1), false); - EncodingManager tmpEM = new EncodingManager.Builder().add(accessEnc).add(speedEnc).add(priorityEnc).build(); + EncodingManager tmpEM = new EncodingManager.Builder().add(speedEnc).add(Roundabout.create()). + add(VehicleAccess.create("car")).add(RoadClass.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).build(); BaseGraph g = new BaseGraph.Builder(tmpEM).create(); // real world example: https://graphhopper.com/maps/?point=43.729379,7.417697&point=43.729798,7.417263&profile=foot // From 4 to 3 and 4 to 1 @@ -396,20 +401,19 @@ public void testInstructionIfSlightTurn() { na.setNode(4, 43.729476, 7.417633); // default is priority=0 so set it to 1 - GHUtility.setSpeed(5, true, true, accessEnc, speedEnc, g.edge(1, 2).setDistance(20). - setKeyValues(createKV(STREET_NAME, "myroad")).set(priorityEnc, 1)); - GHUtility.setSpeed(5, true, true, accessEnc, speedEnc, g.edge(2, 3).setDistance(20). - setKeyValues(createKV(STREET_NAME, "myroad")).set(priorityEnc, 1)); + g.edge(1, 2).setDistance(20).set(speedEnc, 5). + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))); + g.edge(2, 3).setDistance(20).set(speedEnc, 5). + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))); PointList pointList = new PointList(); pointList.add(43.729627, 7.41749); - GHUtility.setSpeed(5, true, true, accessEnc, speedEnc, g.edge(2, 4).setDistance(20). - setKeyValues(createKV(STREET_NAME, "myroad")).set(priorityEnc, 1).setWayGeometry(pointList)); + g.edge(2, 4).setDistance(20).set(speedEnc, 5). + setKeyValues(Map.of(STREET_NAME, new KValue("myroad"))).setWayGeometry(pointList); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, - priorityEnc, tmpEM, DefaultTurnCostProvider.NO_TURN_COST_PROVIDER, - new CustomModel().setDistanceInfluence(0d)); + Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(4, 3); assertTrue(p.isFound()); + assertEquals(IntArrayList.from(4, 2, 3), p.calcNodes()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); List tmpList = getTurnDescriptions(wayList); assertEquals(Arrays.asList("continue onto myroad", "keep right onto myroad", "arrive at destination"), tmpList); @@ -417,6 +421,7 @@ public void testInstructionIfSlightTurn() { assertEquals(20, wayList.get(1).getDistance()); p = new Dijkstra(g, weighting, tMode).calcPath(4, 1); + assertEquals(IntArrayList.from(4, 2, 1), p.calcNodes()); wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); tmpList = getTurnDescriptions(wayList); assertEquals(Arrays.asList("continue onto myroad", "keep left onto myroad", "arrive at destination"), tmpList); @@ -428,7 +433,9 @@ public void testInstructionIfSlightTurn() { public void testInstructionWithHighlyCustomProfileWithRoadsBase() { BooleanEncodedValue roadsAccessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue roadsSpeedEnc = new DecimalEncodedValueImpl("speed", 7, 2, true); - EncodingManager tmpEM = EncodingManager.start().add(roadsAccessEnc).add(roadsSpeedEnc).build(); + EncodingManager tmpEM = EncodingManager.start().add(roadsAccessEnc).add(roadsSpeedEnc). + add(RoadClass.create()).add(Roundabout.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).add(VehicleAccess.create("car")).build(); EnumEncodedValue rcEV = tmpEM.getEnumEncodedValue(RoadClass.KEY, RoadClass.class); BaseGraph g = new BaseGraph.Builder(tmpEM).create(); // real world example: https://graphhopper.com/maps/?point=55.691214%2C12.57065&point=55.689957%2C12.570387 @@ -453,9 +460,11 @@ public void testInstructionWithHighlyCustomProfileWithRoadsBase() { g.edge(2, 5).setDistance(10).set(roadsSpeedEnc, 10, 10).set(roadsAccessEnc, true, true).set(rcEV, RoadClass.PEDESTRIAN); CustomModel customModel = new CustomModel(); - customModel.addToPriority(Statement.If("road_class == PEDESTRIAN", Statement.Op.MULTIPLY, "0")); - Weighting weighting = CustomModelParser.createWeighting(roadsAccessEnc, roadsSpeedEnc, null, tmpEM, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); + customModel.addToSpeed(If("true", Statement.Op.LIMIT, "speed")); + customModel.addToPriority(If("road_class == PEDESTRIAN", Statement.Op.MULTIPLY, "0")); + Weighting weighting = CustomModelParser.createWeighting(tmpEM, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); Path p = new Dijkstra(g, weighting, tMode).calcPath(3, 4); + assertEquals(IntArrayList.from(3, 2, 4), p.calcNodes()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); List tmpList = getTurnDescriptions(wayList); assertEquals(Arrays.asList("continue", "keep right", "arrive at destination"), tmpList); @@ -490,15 +499,16 @@ public void testFind() { na.setNode(6, 15.1, 10.1); na.setNode(7, 15.1, 9.8); - g.edge(1, 2).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "1-2")); - g.edge(2, 3).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "2-3")); - g.edge(2, 6).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "2-6")); - g.edge(3, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-4")).setWayGeometry(waypoint); - g.edge(3, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "3-7")); - g.edge(4, 5).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(createKV(STREET_NAME, "4-5")); + g.edge(1, 2).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); + g.edge(2, 3).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); + g.edge(2, 6).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("2-6"))); + g.edge(3, 4).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))).setWayGeometry(waypoint); + g.edge(3, 7).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("3-7"))); + g.edge(4, 5).setDistance(10000).set(speedEnc, 60, 60).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(1, 5); + assertEquals(IntArrayList.from(1, 2, 3, 4, 5), p.calcNodes()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, carManager, usTR); // query on first edge, get instruction for second edge @@ -514,6 +524,87 @@ public void testFind() { assertNull(Instructions.find(wayList, 50.8, 50.25, 1000)); } + @Test + public void testSplitWays() { + DecimalEncodedValue roadsSpeedEnc = new DecimalEncodedValueImpl("speed", 7, 2, true); + EncodingManager tmpEM = EncodingManager.start().add(roadsSpeedEnc). + add(RoadClass.create()).add(Roundabout.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()). + add(MaxSpeed.create()).add(Lanes.create()).add(VehicleAccess.create("car")).build(); + IntEncodedValue lanesEnc = tmpEM.getIntEncodedValue(Lanes.KEY); + BaseGraph g = new BaseGraph.Builder(tmpEM).create(); + // real world example: https://graphhopper.com/maps/?point=43.626238%2C-79.715268&point=43.624647%2C-79.713204&profile=car + // + // 1 3 + // \ | + // 2 + // \ + // 4 + + NodeAccess na = g.getNodeAccess(); + na.setNode(1, 43.626246, -79.71522); + na.setNode(2, 43.625503, -79.714228); + na.setNode(3, 43.626285, -79.714974); + na.setNode(4, 43.625129, -79.713692); + + PointList list = new PointList(); + list.add(43.62549, -79.714292); + g.edge(1, 2).setKeyValues(Map.of(STREET_NAME, new KValue("main"))).setWayGeometry(list). + setDistance(110).set(roadsSpeedEnc, 50, 0).set(lanesEnc, 2); + g.edge(2, 3).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). + setDistance(110).set(roadsSpeedEnc, 50, 0).set(lanesEnc, 3); + g.edge(2, 4).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). + setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); + + Weighting weighting = new SpeedWeighting(roadsSpeedEnc); + Path p = new Dijkstra(g, weighting, tMode).calcPath(1, 4); + InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); + List tmpList = getTurnDescriptions(wayList); + assertEquals(Arrays.asList("continue onto main", "arrive at destination"), tmpList); + + // Other roads should not influence instructions. Example: https://www.openstreetmap.org/node/392106581 + na.setNode(5, 43.625666, -79.714048); + g.edge(2, 5).setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); + + p = new Dijkstra(g, weighting, tMode).calcPath(1, 4); + wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); + tmpList = getTurnDescriptions(wayList); + assertEquals(Arrays.asList("continue onto main", "arrive at destination"), tmpList); + } + + @Test + public void testNotSplitWays() { + DecimalEncodedValue roadsSpeedEnc = new DecimalEncodedValueImpl("speed", 7, 2, true); + EncodingManager tmpEM = EncodingManager.start().add(roadsSpeedEnc).add(VehicleAccess.create("car")). + add(RoadClass.create()).add(Roundabout.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).add(Lanes.create()).build(); + IntEncodedValue lanesEnc = tmpEM.getIntEncodedValue(Lanes.KEY); + BaseGraph g = new BaseGraph.Builder(tmpEM).create(); + // real world example: https://graphhopper.com/maps/?point=51.425484%2C14.223298&point=51.42523%2C14.222864&profile=car + // 3 + // | + // 1-2-4 + + NodeAccess na = g.getNodeAccess(); + na.setNode(1, 51.42523, 14.222864); + na.setNode(2, 51.425256, 14.22325); + na.setNode(3, 51.425397, 14.223266); + na.setNode(4, 51.425273, 14.223427); + + g.edge(1, 2).setKeyValues(Map.of(STREET_NAME, new KValue("dresdener"))). + setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 2); + g.edge(2, 3).setKeyValues(Map.of(STREET_NAME, new KValue("dresdener"))). + setDistance(110).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 3); + g.edge(2, 4).setKeyValues(Map.of(STREET_NAME, new KValue("main"))). + setDistance(80).set(roadsSpeedEnc, 50, 50).set(lanesEnc, 5); + + Weighting weighting = new SpeedWeighting(roadsSpeedEnc); + Path p = new Dijkstra(g, weighting, tMode).calcPath(3, 1); + InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, tmpEM, usTR); + List tmpList = getTurnDescriptions(wayList); + assertEquals(Arrays.asList("continue onto dresdener", "turn right onto dresdener", "arrive at destination"), tmpList); + } + private void compare(List> expected, List> actual) { for (int i = 0; i < expected.size(); i++) { List e = expected.get(i); diff --git a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java index 378908cfcca..e75eaaafa28 100644 --- a/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java +++ b/core/src/test/java/com/graphhopper/util/PathSimplificationTest.java @@ -21,13 +21,10 @@ import com.graphhopper.routing.Dijkstra; import com.graphhopper.routing.InstructionsFromEdges; import com.graphhopper.routing.Path; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.NodeAccess; @@ -38,9 +35,9 @@ import java.util.*; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; import static com.graphhopper.util.Parameters.Details.AVERAGE_SPEED; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -55,9 +52,10 @@ public class PathSimplificationTest { @Test public void testScenario() { - BooleanEncodedValue accessEnc = new SimpleBooleanEncodedValue("access", true); DecimalEncodedValue speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - EncodingManager carManager = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + EncodingManager carManager = EncodingManager.start().add(speedEnc). + add(VehicleAccess.create("car")).add(Roundabout.create()).add(RoadClass.create()). + add(RoadEnvironment.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build(); BaseGraph g = new BaseGraph.Builder(carManager).create(); // 0-1-2 // | | | @@ -78,42 +76,42 @@ public void testScenario() { na.setNode(7, 1.0, 1.1); na.setNode(8, 1.0, 1.2); - GHUtility.setSpeed(9, true, true, accessEnc, speedEnc, g.edge(0, 1).setDistance(10000)).setKeyValues(createKV(STREET_NAME, "0-1")); - GHUtility.setSpeed(9, true, true, accessEnc, speedEnc, g.edge(1, 2).setDistance(11000)).setKeyValues(createKV(STREET_NAME, "1-2")); + g.edge(0, 1).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("0-1"))); + g.edge(1, 2).set(speedEnc, 9).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); - GHUtility.setSpeed(18, true, true, accessEnc, speedEnc, g.edge(0, 3).setDistance(11000)); - GHUtility.setSpeed(18, true, true, accessEnc, speedEnc, g.edge(1, 4).setDistance(10000)).setKeyValues(createKV(STREET_NAME, "1-4")); - GHUtility.setSpeed(18, true, true, accessEnc, speedEnc, g.edge(2, 5).setDistance(11000)).setKeyValues(createKV(STREET_NAME, "5-2")); + g.edge(0, 3).set(speedEnc, 18).setDistance(11000); + g.edge(1, 4).set(speedEnc, 18).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("1-4"))); + g.edge(2, 5).set(speedEnc, 18).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("5-2"))); - GHUtility.setSpeed(27, true, true, accessEnc, speedEnc, g.edge(3, 6).setDistance(11000)).setKeyValues(createKV(STREET_NAME, "3-6")); - GHUtility.setSpeed(27, true, true, accessEnc, speedEnc, g.edge(4, 7).setDistance(10000)).setKeyValues(createKV(STREET_NAME, "4-7")); - GHUtility.setSpeed(27, true, true, accessEnc, speedEnc, g.edge(5, 8).setDistance(10000)).setKeyValues(createKV(STREET_NAME, "5-8")); + g.edge(3, 6).set(speedEnc, 27).setDistance(11000).setKeyValues(Map.of(STREET_NAME, new KValue("3-6"))); + g.edge(4, 7).set(speedEnc, 27).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("4-7"))); + g.edge(5, 8).set(speedEnc, 27).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("5-8"))); - GHUtility.setSpeed(36, true, true, accessEnc, speedEnc, g.edge(6, 7).setDistance(11000)).setKeyValues(createKV(STREET_NAME, "6-7")); - EdgeIteratorState tmpEdge = GHUtility.setSpeed(36, true, true, accessEnc, speedEnc, g.edge(7, 8).setDistance(10000)); + g.edge(6, 7).setDistance(11000).set(speedEnc, 36).setKeyValues(Map.of(STREET_NAME, new KValue("6-7"))); + EdgeIteratorState tmpEdge = g.edge(7, 8).set(speedEnc, 36).setDistance(10000); PointList list = new PointList(); list.add(1.0, 1.15); list.add(1.0, 1.16); tmpEdge.setWayGeometry(list); - tmpEdge.setKeyValues(createKV(STREET_NAME, "7-8")); + tmpEdge.setKeyValues(Map.of(STREET_NAME, new KValue("7-8"))); // missing edge name - GHUtility.setSpeed(45, true, true, accessEnc, speedEnc, g.edge(9, 10).setDistance(10000)); - tmpEdge = GHUtility.setSpeed(45, true, true, accessEnc, speedEnc, g.edge(8, 9).setDistance(20000)); + g.edge(9, 10).set(speedEnc, 45).setDistance(10000); + tmpEdge = g.edge(8, 9).set(speedEnc, 45).setDistance(20000); list.clear(); list.add(1.0, 1.3); list.add(1.0, 1.3001); list.add(1.0, 1.3002); list.add(1.0, 1.3003); - tmpEdge.setKeyValues(createKV(STREET_NAME, "8-9")); + tmpEdge.setKeyValues(Map.of(STREET_NAME, new KValue("8-9"))); tmpEdge.setWayGeometry(list); // Path is: [0 0-1, 3 1-4, 6 4-7, 9 7-8, 11 8-9, 10 9-10] - Weighting weighting = new ShortestWeighting(accessEnc, speedEnc); + Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, tMode).calcPath(0, 10); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, carManager, usTR); Map> details = PathDetailsFromEdges.calcDetails(p, carManager, weighting, - Arrays.asList(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, g); + List.of(AVERAGE_SPEED), new PathDetailsBuilderFactory(), 0, g); PointList points = p.calcPoints(); PointList waypoints = new PointList(2, g.getNodeAccess().is3D()); diff --git a/core/src/test/resources/com/graphhopper/reader/osm/test-osm2.xml b/core/src/test/resources/com/graphhopper/reader/osm/test-osm2.xml index ee39c77ea36..a15c2686f54 100644 --- a/core/src/test/resources/com/graphhopper/reader/osm/test-osm2.xml +++ b/core/src/test/resources/com/graphhopper/reader/osm/test-osm2.xml @@ -76,6 +76,12 @@ + + + + + + diff --git a/docs/core/custom-areas-and-country-rules.md b/docs/core/custom-areas-and-country-rules.md index 1d063d5bcf1..f0236c9d293 100644 --- a/docs/core/custom-areas-and-country-rules.md +++ b/docs/core/custom-areas-and-country-rules.md @@ -23,7 +23,7 @@ example the `AustriaCountryRule` changes the default accessibility for `highway= the `GermanyCountryRule` changes it to `access=destination`. More information about such country-specific rules can be found in the OSM wiki for [access restrictions](https://wiki.openstreetmap.org/wiki/OSM_tags_for_routing/Access-Restrictions) -and [maximum speeds](https://wiki.openstreetmap.org/wiki/OSM_tags_for_routing/Maxspeed#Motorcar). Feel free to add +and [maximum speeds](https://wiki.openstreetmap.org/wiki/Default_speed_limits). Feel free to add country rules for your country and contribute to GraphHopper! # Note about the country rules used by GraphHopper diff --git a/docs/core/custom-models.md b/docs/core/custom-models.md index 4788ea9b48e..15c3763052f 100644 --- a/docs/core/custom-models.md +++ b/docs/core/custom-models.md @@ -5,7 +5,7 @@ default routing behavior by specifying a set of rules in JSON language. Here we background and then show how to use custom models in practice. Try some live examples in [this blog post](https://www.graphhopper.com/blog/2020/05/31/examples-for-customizable-routing/) -and the [custom_models](../../custom_models) folder on how to use them on the server-side. +and the [custom_models](../../core/src/main/resources/com/graphhopper/custom_models) folder on how to use them on the server-side. ## How GraphHopper's route calculations work @@ -29,10 +29,11 @@ out with a smaller total weight and thus will be preferred. Internally, GraphHopper uses the following formula for the weighting: ``` -edge_weight = edge_distance / (speed * priority) + edge_distance * distance_influence +edge_weight = edge_distance / (speed * priority) + edge_distance * distance_influence + turn_penalty ``` -To simplify the discussion, let's first assume that `distance_influence=0` and `priority=1` so the formula simply reads: +To simplify the discussion, let's first assume that `distance_influence=0`, `priority=1` and +`turn_penalty=0` so the formula simply reads: ``` edge_weight = edge_distance / speed @@ -50,7 +51,7 @@ travelling time? This is the reason why there is the `priority` factor in the ab as `speed`, but changing the priority only changes the edge weight, and not the travelling time. By default, `priority` is always `1`, so it has no effect, but it can be used to modify the edge weights as we will see in the next section. -Finally, `distance_influence` allows us to control the trade-off between a fast route (minimum time) and a short route +The `distance_influence` allows us to control the trade-off between a fast route (minimum time) and a short route (minimum distance). For example if `priority=1` setting `distance_influence=0` means that GraphHopper will return the fastest possible route and the larger `distance_influence` is the more GraphHopper will prioritize routes with a small total distance. More precisely, the `distance_influence` is the time you need to save on a detour (a longer distance @@ -63,6 +64,11 @@ alternative route that is `11km` long only if it takes no longer than `570s` (sa complicated when `priority` is not strictly `1`, but the effect stays the same: The larger `distance_influence` is, the more GraphHopper will focus on finding short routes. +The `turn_penalty` allows you to add absolute weight values to edges independent of their length and speed, +making it particularly useful for avoiding specific turn types, short road segments, or when road attributes +change. Unlike statements in `speed`, turn penalties don't affect the travelling time of a route and only +influence route selection during the pathfinding process without changing the estimated travel duration. + ### Edge attributes used by GraphHopper: Encoded Values GraphHopper stores different attributes, so called 'encoded values', for every road segment. Some frequently used @@ -72,6 +78,8 @@ encoded values are the following (some of their possible values are given in bra - road_environment: (ROAD, FERRY, BRIDGE, TUNNEL, ...) - road_access: (DESTINATION, DELIVERY, PRIVATE, NO, ...) - surface: (PAVED, DIRT, SAND, GRAVEL, ...) +- sidewalk: (YES, SEPARATE, NO, ...) stores two directions +- cycleway: (TRACK, LANE, SEPARATE, NO, ...) stores two directions - smoothness: (EXCELLENT, GOOD, INTERMEDIATE, ...) - toll: (MISSING, NO, HGV, ALL) - bike_network, foot_network: (MISSING, INTERNATIONAL, NATIONAL, REGIONAL, LOCAL, OTHER) @@ -90,23 +98,35 @@ Besides this kind of categories, which can take multiple different string values boolean value (they are either true or false for a given road segment), like: - get_off_bike +- lit - road_class_link - roundabout - with postfix `_access` contains the access (as boolean) for a specific vehicle There are also some that take on a numeric value, like: -- average_slope: a number for 100 * "elevation change" / edge_distance for a road segment; it changes the sign in reverse direction; see max_slope +- average_slope: a signed decimal for 100 * "elevation change" / edge_distance for a road segment; it changes the sign in reverse direction; see max_slope - curvature: "beeline distance" / edge_distance (0..1) e.g. a curvy road is smaller than 1 -- hike_rating, horse_rating, mtb_rating: a number from 0 to 6 for the `sac_scale` in OSM, e.g. 0 means "missing", 1 means "hiking", 2 means "mountain_hiking" and so on +- change_angle: specifies the angle difference between the current and the previous edge; only available in "turn_penalty" +- hike_rating: a number from 0 to 6 for the `sac_scale` in OSM, e.g. 0 means "missing", 1 means "hiking", 2 means "mountain_hiking", 3 means demanding_mountain_hiking, 4 means alpine_hiking, 5 means demanding_alpine_hiking, and 6 means difficult_alpine_hiking +- mtb_rating: a number from 0 to 7 for the `mtb:scale` in OSM, e.g. 0 means "missing", 1 means `mtb:scale=0`, 2 means `mtb:scale=1` and so on. A leading "+" or "-" character is ignored. +- horse_rating: a number from 0 to 6 for the `horse_scale` in OSM, e.g. 0 means "missing", 1 means "common", 2 means "demanding", 3 means difficult, 4 means critical, 5 means dangerous, and 6 means impossible - lanes: number of lanes -- max_slope: an unsigned decimal for the maximum slope (100 * "elevation change / distance_i") of an edge with `sum(distance_i)=edge_distance`. Important for longer road segments where ups (or downs) can be much bigger than the average_slope. -- max_speed: the speed limit from a sign (km/h) +- max_slope: a signed decimal for the maximum slope (100 * "elevation change / distance_i") of an edge with `sum(distance_i)=edge_distance`. Important for longer road segments where ups (or downs) can be much bigger than the average_slope. It changes the sign in reverse direction. +- max_speed: the speed limit from a sign or from local default speed limits (km/h) - max_height (meter), max_width (meter), max_length (meter) -- max_weight (ton), max_axle_load (in tons) +- max_weight (tonne), max_axle_load (tonne) - with postfix `_average_speed` contains the average speed (km/h) for a specific vehicle - with postfix `_priority` contains the road preference without changing the speed for a specific vehicle (0..1) +Special expressions: + + - `backward_*`: this expression allows to access the reverse direction of the current edge + - `in_*`: see `areas` for more information about this expression + - `prev_*`: this expression allows to access the previous edge; can only be used in statements of `turn_penalty`. + - `country.isRightHandTraffic()`: returns true if right-hand traffic is the standard in the country of the current edge; `false` otherwise + - `edge.getDistance()`: returns the distance of the current edge + In the next section will see how we can use these encoded values to customize GraphHopper's route calculations. ## How you can customize GraphHopper's route calculations: Custom Models @@ -176,10 +196,11 @@ statements that come later in the list are applied to the resulting value of pre executed if the corresponding condition applies for the current edge. This will become more clear in the following examples. -Currently the custom model language supports two operators: +The custom model language supports three operators: - `multiply_by` multiplies the speed value with a given number or expression - `limit_to` limits the speed value to a given number or expression +- `do` lists sub-statements that are executed #### `if` statements and the `multiply_by` operation @@ -335,6 +356,53 @@ A common use-case for the `limit_to` operation is the following pattern: which means that the speed is limited to `90km/h` for all road segments regardless of its properties. The condition `true` is always fulfilled. +#### The `do` operation + +The `do` operation allows multiple statements for an `if`, `else_if`, and `else` statement. +For example, for an `if` statement, it can be used as follows: + +```json +{ + "if": "country == DEU", + "do": [ + { "if": "road_class == PRIMARY", "multiply_by": "0.8" }, + { "if": "road_class == SECONDARY", "multiply_by": "0.7" } + ] +} +``` + +And then the two nested statements under `do` are only executed if the expression `country == DEU` is true. + +For `else` the `do` operation can be used in a similar way: + +```json +[ + { "if": "max_speed > 70", "limit": "70" }, + { "else": "", + "do": [ + { "if": "road_class == PRIMARY", "multiply_by": "0.8" }, + { "if": "road_class == SECONDARY", "multiply_by": "0.7" } + ] + } +] +``` + +Further nesting is also possible: + +```json +{ + "if": "country == DEU", + "do": [ + { + "if": "road_class == PRIMARY", + "do": [ + { "if": "max_speed > 70", "multiply_by": "0.5" } + ] + } + ] +} +``` + #### `else` and `else_if` statements The `else` statement allows you to define that some operations should be applied if an edge does **not** match a @@ -564,6 +632,57 @@ which means that the priority for all road segments that allow a maximum vehicle length of `10m` or a maximum vehicle weight of `3.5tons`, or less, is zero, i.e. these "narrow" road segments are blocked. +### Customizing `turn_penalty` + +Turn penalties are applied when transitioning between road edges. The feature supports both angle-based +penalties for actual turns and attribute-change penalties. + +This features allows you to specify a turn penalty. To penalize turns based on their sharpness, +you can use the `change_angle` attribute: + +```json +{ + "turn_penalty": [ + { "if": "change_angle >= 25 && change_angle < 80", "add": "1" }, + { "else_if": "change_angle >= 80 && change_angle <= 180", "add": "3" } + ] +} +``` + +This example adds a penalty of 1 for moderate turns (25-79 degrees) and a penalty of 3 for +sharp turns (80-180 degrees). The angle is measured as the change in direction when moving from one +road segment to the next. See avoid_turns.json for a full example. To completely block certain turns +you can add "Infinity". + +Note that it does not avoid curvy road segments. To avoid these cases you can use the `curvature` +road attribute in a statement of `priority`. + +To discourage routing through private roads while still allowing access when necessary e.g. destination is on a private road, +you can use the `prev_*` expression to access road attributes of previous road segments: + +```json +{ + "turn_penalty": [ + { "if": "prev_road_access != road_access && road_access == PRIVATE", "add": "300" } + ] +} +``` + +See car_avoid_private_etc.json. The advantage of using `turn_penalty` over `priority` for avoiding +private roads is that it gracefully handles cases where the destination (or a via point) is on a +private road. With `priority`, high penalty values can create routing artifacts and detours (see #3174 and #733), +but `turn_penalty` only penalizes the transition onto private roads, not traveling within them. + +Similarly, you can discourage border crossings by penalizing transitions between countries: + +```json +{ + "turn_penalty": [ + { "if": "prev_country != country && country == DEU", "add": "300" } + ] +} +``` + ### The value expression The value of `limit_to` or `multiply_by` is usually only a number but can be more complex expression like `max_speed` diff --git a/docs/core/deploy.md b/docs/core/deploy.md index 703fcfeb503..9ac7ddbccbd 100644 --- a/docs/core/deploy.md +++ b/docs/core/deploy.md @@ -24,6 +24,8 @@ However after the import, for serving the routing requests GCs like ZGC or Shena They can be enabled with `-XX:+UseZGC` or `-XX:+UseShenandoahGC`. Please note that especially ZGC and G1 require quite a bit memory additionally to the heap and so sometimes overall speed could be increased when lowering the `Xmx` value. +We do not recommend the default G1 GC for GraphHopper, as without careful alignment of the segment size in DataAccess (`graph.dataaccess.segment_size`) and the heap region size, G1's humongous allocations can waste large amounts of memory on filler objects. See for example: https://www.oracle.com/technical-resources/articles/java/g1gc.html + If you want to support none-CH requests you should consider enabling landmarks or limit requests to a certain distance via `routing.non_ch.max_waypoint_distance` (in meter, default is 1) or to a node count via `routing.max_visited_nodes`. diff --git a/docs/core/elevation.md b/docs/core/elevation.md index e1e422569c1..621b61877ac 100644 --- a/docs/core/elevation.md +++ b/docs/core/elevation.md @@ -1,13 +1,14 @@ # Elevation -Per default elevation is disabled. But you can easily enable it e.g. via -`graph.elevation.provider: cgiar`. Or use other possibilities `srtm`, `gmted` -or `multi` (combined cgiar and gmted). +Per default elevation is `srtm` and if you remove the config line you +automatically disable it and reduce storange and RAM usage a bit. +You can also change it to `graph.elevation.provider: cgiar`. Or use other possibilities `srtm`, `gmted`, `sonny`, +`multi` (combined cgiar and gmted), `multi3` (combined cgiar, gmted and sonny) or `pmtiles`. -Then GraphHopper will automatically download the necessary data for the area and include elevation -for all vehicles - making also the distances a bit more precise. +Then GraphHopper will automatically download the necessary data for the area +(except for `sonny` and `pmtiles`) and include elevation for all vehicles making also the distances a bit more precise. -The default cache directory `/tmp/` will be used. For large areas it is highly recommended to +The default cache directory `/tmp/` will be used. For large areas it is highly recommended to use a SSD disc, thus you need to specify the cache directory: `graph.elevation.cache_dir: /myssd/ele_cache/` @@ -17,27 +18,65 @@ The `average_slope` and `max_slope` attributes of a road segment can be used to elevation-aware, i.e. to prefer or avoid, to speed up or slow down your vehicle based on the elevation change. See the [custom model](custom-models.md) feature. +## Cache + +All elevation providers create an internal custom storage format for faster +access while import. The default behaviour is that these cache files are not +deleted after GraphHopper finishes because on a new import they can be +reused. To change this behaviour and delete them before exit specify: +`graph.elevation.clear: true` + ## What to download and where to store it? -All should work automatically but you can tune certain settings like the location where the files are +All should work automatically, but you can tune certain settings like the location where the files are downloaded and e.g. if the servers are not reachable, then you set: `graph.elevation.base_url` -For CGIAR the default URL is `http://srtm.csi.cgiar.org/SRT-ZIP/SRTM_V41/SRTM_Data_GeoTiff/` -and this is only accessibly if you specify the [full zip file](http://srtm.csi.cgiar.org/SRT-ZIP/SRTM_V41/SRTM_Data_GeoTiff/srtm_01_02.zip). +For CGIAR the default URL is `https://srtm.csi.cgiar.org/wp-content/uploads/files/srtm_5x5/TIFF/` +and this is only accessibly if you specify the [full zip file](https://srtm.csi.cgiar.org/wp-content/uploads/files/srtm_5x5/TIFF//srtm_01_02.zip). If the geographical area is small and you need a faster import you can change the default MMAP setting to: `graph.elevation.dataaccess: RAM_STORE` -## CGIAR vs. SRTM +### Manual Download Required + +For `sonny` and `pmtiles` you need to download the data first. Read more +about the details in [this pull request](https://github.com/graphhopper/graphhopper/pull/3183) for `sonny` +and [this pull request](https://github.com/graphhopper/graphhopper/pull/3287) for `pmtiles`. + +## PMTiles + +The mapterhorn project did an exceptional work and created a pipeline to collect many raster +tiles in a convenient single file (protomaps tiles). As it contains many +sources the attribution is a longer list that you can find [here](https://mapterhorn.com/attribution/). + +When you downloaded the file you specify it in the config.yml like this: + +``` +graph.elevation.provider: pmtiles +graph.elevation.pmtiles.location: /data/pmtiles.pmtiles +graph.elevation.pmtiles.zoom: 11 +graph.elevation.cache_dir: /tmp/pmtiles-cache +``` -The CGIAR data is preferred because of the quality but is in general not public domain. -But we got a license for our and our users' usage: https://graphhopper.com/public/license/CGIAR.txt +You can also cut a geographical area using the official pmtiles tool or cut out certain +zoom levels with [a simple go script](https://gist.github.com/karussell/e6ad9918b6cd9913ddba998af43860a2#file-trim_pmtiles-go) to reduce file size by a lot. -Using SRTM instead CGIAR has the minor advantage of a faster download, especially for smaller areas. +## Sonny's LiDAR Digital Terrain Models +Sonny's LiDAR Digital Terrain Models are available for Europe only, see https://sonny.4lima.de/. +It is a very high resolution elevation data set, but it is **not free** to use! See the discussion at +https://github.com/graphhopper/graphhopper/issues/2823. +The DTM 1" data is provided on a Google Drive https://drive.google.com/drive/folders/0BxphPoRgwhnoWkRoTFhMbTM3RDA?resourcekey=0-wRe5bWl96pwvQ9tAfI9cQg. +From there it needs to be manually downloaded and extracted into a persistent cache directory. Automatic download +is not supported due to Google Drive not providing support for hyperlinks on to the DTM data files. +The cache directory is expected to contain the DTM data files with the naming convention like +"N49E011.hgt" for the area around 49°N and 11°E. +Sonny's LiDAR Digital Terrain Model `sonny` only works for countries in Europe, which are fully covered +by the Sonny DTM 1" data. See https://sonny.4lima.de/map.png for coverage. In case the covered sonny +data area does not match your graphhopper coverage area, make sure to use the `multi3` elevation provider. ## Custom Elevation Data -Integrating your own elevation data is easy and just requires you to implement the +Integrating your own elevation data requires you to implement the ElevationProvider interface and then specify it via GraphHopper.setElevationProvider. Have a look in the existing implementations for a simple overview of caching and DataAccess usage. diff --git a/docs/core/profiles.md b/docs/core/profiles.md index e6ad6ddfac2..efb29bb5d2d 100644 --- a/docs/core/profiles.md +++ b/docs/core/profiles.md @@ -3,34 +3,33 @@ GraphHopper lets you customize how different kinds of roads shall be prioritized during its route calculations. For example when travelling long distances with a car you typically want to use the highway to minimize your travelling time. However, if you are going by bike you certainly do not want to use the highway and rather take some shorter route, -use designated bike lanes and so on. GraphHopper provides built-in vehicle types that cover some standard use cases, +use designated bike lanes and so on. GraphHopper provides built-in vehicle profiles that cover some standard use cases, which can be modified by a custom model for fine-grained control over GraphHopper's road prioritization to e.g. change the travelling speed for certain road types. -A profile is defined by a vehicle, its turn restrictions and a custom model. All profiles are specified +A profile is defined by a custom model and its turn restrictions. All profiles are specified in the 'profiles' section of `config.yml` and there has to be at least one profile. Here is an example: ```yaml profiles: - name: car - vehicle: car - custom_model_files: [] + custom_model_files: [car.json] - name: my_bike - vehicle: bike custom_model_files: [bike_elevation.json] ``` -The vehicle field must correspond to one of GraphHopper's built-in vehicle types: +By choosing a custom model file GraphHopper determines the accessibility and a default travel speed for the different road types. -- foot -- bike -- racingbike -- mtb -- car +Another important profile setting is the `turn_costs` configuration. Use this to enable turn restrictions for each profile: -By choosing a vehicle GraphHopper determines the accessibility and a default travel speed for the different road types. +```yaml +profiles: + - name: car + turn_costs: + vehicle_types: [motorcar, motor_vehicle] + custom_model_files: [car.json] +``` -Another important profile setting is `turn_costs: true/false`. Use this to enable turn restrictions for each profile. You can learn more about this setting [here](./turn-restrictions.md) The profile name is used to select the profile when executing routing queries. To do this use the `profile` request diff --git a/docs/core/technical.md b/docs/core/technical.md index a56779d0612..9b38e66c777 100644 --- a/docs/core/technical.md +++ b/docs/core/technical.md @@ -61,7 +61,7 @@ shortcuts and get the edges recursively, this is done in Path4CH. In order to traverse the _CHGraph_ like a normal _Graph_ one needs to hide the shortcuts, which is done automatically for you if you call graph.getBaseGraph(). This is necessary in a _LocationIndex_ and in the _Path_ class in order to identify how many streets leave a junction -or similar. See issue #116 for more information. +or similar. See issue [#116](https://github.com/graphhopper/graphhopper/issues/116) for more information. ### 4. Connecting the Real World to the Graph @@ -73,7 +73,7 @@ To get the coordinate from an address you will need a geocoding solution which is not part of this GraphHopper routing engine. To get the closest node or edge id from a coordinate we provide you with an efficient lookup concept: -the LocationIndexTree. See [here](../example/src/main/java/com/graphhopper/example/LocationIndexExample.java) for more information. See #17 and #221. +the LocationIndexTree. See [here](../../example/src/main/java/com/graphhopper/example/LocationIndexExample.java) for more information. See [#17](https://github.com/graphhopper/graphhopper/issues/17) and [#221](https://github.com/graphhopper/graphhopper/issues/221). ## 4.2 QueryGraph diff --git a/docs/core/translations.md b/docs/core/translations.md index ddfda3d3903..bbcd232b865 100644 --- a/docs/core/translations.md +++ b/docs/core/translations.md @@ -31,7 +31,7 @@ or if you want to try to integrate your changes you have to: * Make GraphHopper working on your computer, where you need to git clone the repository - see [here](./quickstart-from-source.md) for more information. * If you created a new language then add it in lexicographical order to TranslationMap.LOCALES (core/src/main/java/com/graphhopper/util) and to the script: core/files/update-translations.sh - * Do `cd graphhopper/core; curl -L 'https://docs.google.com/spreadsheets/d/10HKSFmxGVEIO92loVQetVmjXT0qpf3EA2jxuQSSYTdU/export?format=tsv&id=10HKSFmxGVEIO92loVQetVmjXT0qpf3EA2jxuQSSYTdU&gid=0' > tmp.tsv` + * Do `cd graphhopper/core; curl -L 'https://docs.google.com/spreadsheets/d/18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFY/export?format=tsv&id=18z00Rbt6QvLIkayEV9P89vW9oU0QbTVsjRk9nz1CeFY&gid=0' > tmp.tsv` * Then `./files/update-translations.sh tmp.tsv && rm tmp.tsv` * Now you can see your changes via `git diff`. Make sure that is the only one with `git status` * Now execute `mvn clean test` to see if you did not miss arguments in your translation (see point 2 in the questions above) and start diff --git a/docs/core/turn-restrictions.md b/docs/core/turn-restrictions.md index 63cee67b389..10bd5081538 100644 --- a/docs/core/turn-restrictions.md +++ b/docs/core/turn-restrictions.md @@ -11,26 +11,37 @@ Without turn restrictions the route will look like: ![turn with turn restrictions](./images/turn-restrictions-correct.png) -Turn restrictions have to be enabled on a vehicle basis. To enable it for one vehicle add -`|turn_costs=true` in the config, for example: `graph.vehicles=car|turn_costs=true` -and set `turn_costs: true` it for the profile too. +To enable turn restrictions for a profile set the turn_costs configuration like this: + + +```yaml +profiles: + - name: car + turn_costs: + restrictions: [motorcar, motor_vehicle] + ... +``` + +When using the Java API you can create the encoding manager like: + +```java +DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 5, 5, true); +DecimalEncodedValue turnCostEnc = TurnCost.create("car", maxTurnCosts); +EncodingManager encodingManager = new EncodingManager.Builder().add(speedEnc).addTurnCostEncodedValue(turnCostEnc).build(); +``` + +Where maxTurnCosts=1 means the turn can either be legal or forbidden. -When using the Java API you can either create the encoding manager like `EncodingManager.create("car|turn_costs=true")` or -`new EncodingManager.Builder().add(new CarFlagEncoder(5, 5, 1)` where the last parameter of `CarFlagEncoder` represents -the maximum turn costs (a value of 1 means the turn can either be legal or forbidden). To enable turn restrictions when using the 'speed mode' additional graph preparation is required, because turn restrictions require edge-based (vs. node-based) traversal of the graph. You have to configure the profiles for which the graph preparation should be run using e.g. `profiles_ch`, just like when you use the 'speed mode' without turn restrictions. You can also specify a time penalty for taking u-turns in the profile (turning from one road back to the same road at a junction). -Note, that this time-penalty only works reasonably when your weighting is time-based (like "fastest"). To use u-turn -costs with speed mode you need to specify the time penalty for each u-turn (again in the profile configuration): - `u_turn_costs: 60`. See `config-example.yml` for further details regarding these configurations. -If you prepare multiple 'speed mode' profiles you have to specify which -one to use at request time: Use the `edge_based=true/false` parameter to enforce edge-based or node-based routing and -the `u_turn_costs` parameter to specify the u-turn costs (only needed if there are multiple edge-based 'speed mode' -profiles with different u-turn costs). To disable the 'speed mode' per request you can add `ch.disable=true` and choose -the value of `u_turn_costs` freely. +Note, that this time-penalty only works reasonably when your weighting is time-based (like "fastest"). To use u-turn +costs with speed mode you need to specify the time penalty for each u-turn in the turn_costs configuration: +`u_turn_costs: 60`. See `config-example.yml` for further details regarding these configurations. + +To disable the 'speed mode' per request you can add `ch.disable=true` and choose the value of `u_turn_costs` freely in the request. While OSM data only contains turn *restrictions*, the GraphHopper routing engine can also deal with turn *costs*, i.e. you can specify custom turn costs for each turn at each junction. See [this experimental branch](https://github.com/graphhopper/graphhopper/tree/turn_costs_calc). diff --git a/docs/core/weighting.md b/docs/core/weighting.md index d8a9c9eac6d..4f264dbb5b4 100644 --- a/docs/core/weighting.md +++ b/docs/core/weighting.md @@ -1,6 +1,6 @@ ## Weighting -Instead of creating a new Weighting implementation is is highly recommended to use the CustomWeighting instead, which is explained in +Instead of creating a new Weighting implementation it is highly recommended to use the CustomWeighting instead, which is explained in the [profiles](profiles.md) and [custom models](custom-models.md) section. A weighting calculates the "weight" for an edge. The weight of an edge reflects the cost of travelling along this edge. @@ -24,4 +24,4 @@ If you only want to change small parts of an existing weighting, it might be a g a sample can be found in the [AvoidEdgesWeighting](https://github.com/graphhopper/graphhopper/blob/master/core/src/main/java/com/graphhopper/routing/weighting/AvoidEdgesWeighting.java). If your weights change on a per-request base you cannot use the 'speed mode', but have to use the 'hybrid mode' or 'flexible mode' (more details [here](https://github.com/graphhopper/graphhopper#technical-overview)). If you haven't disabled the 'speed mode' in your config, you have to disable it for the requests by appending `ch.disable=true` -in the request url. \ No newline at end of file +in the request url. diff --git a/docs/migration/config-migration-08-09.md b/docs/migration/config-migration-08-09.md new file mode 100644 index 00000000000..313e7e0a7d1 --- /dev/null +++ b/docs/migration/config-migration-08-09.md @@ -0,0 +1,136 @@ +# vehicle parameter + +The `vehicle` parameter was replaced by explicit statements in the custom model. So instead of: + +```yml +profiles: + - name: car + vehicle: car + custom_model: { + "distance_influence": 10 + } +``` + +You write: + +```yml +profiles: + - name: car + custom_model: { + "priority": [ + { "if": "!car_access", "multiply_by": "0" } + ], + "speed": [ + { "if": "true", "limit_to": "car_average_speed" } + ], + "distance_influence": 10 + } +``` + +Or for vehicles with a priority encoded value you write: + +```yml +profiles: + - name: foot + custom_model: { + "priority": [ + { "if": "foot_access", "multiply_by": "foot_priority" }, + { "else": "", "multiply_by": "0" } + ], + "speed": [ + { "if": "true", "limit_to": "foot_average_speed" } + ] + } +``` + +This looks more verbose but for the standard vehicles we have created default custom model files. So instead of the above custom model for car you can also just write: + +```yml +profiles: + - name: car + custom_model_files: [car.json] +``` + +With turn costs this looks now: + +```yml +profiles: + - name: car + turn_costs: + restrictions: [motorcar, motor_vehicle] + u_turn_costs: 60 + custom_model_files: [car.json] +``` + + +Furthermore the new approach is more flexible. A profile that previously required the special vehicle `roads` (to get unlimited priority and speed) always created the encoded values roads_access and roads_average_speed and used unnecessary memory and introduced unnecessary limitations. +Now these artificial encoded values are no longer necessary and you could write custom models even without any encoded values and only one unconditional or if-else block is necessary for `speed`: + +```yml +profiles: + - name: foot + custom_model: { + "priority": [ + { "if": "road_class == MOTORWAY", "multiply_by": "0" } + ], + "speed": [ + { "if": "true", "limit_to": "6" } + ] + } +``` + +# graph.encoded_values + +All encoded values that are used in a custom models must be listed here. + +If you used a property like block_private=false for e.g. the `car` vehicle, you can now use this property for the encoded value `car_access`: + +``` + graph.encoded_values: car_access|block_private=false +``` + +# shortest and fastest weighting + +Both weightings were replaced by the custom model. Instead of `weighting: fastest` you now use the default custom weighting as +explained in the previous section. + +Instead of `weighting: shortest` you now use a custom weighting with a high `distance_influence`: + +```yml +profiles: + - name: car + custom_model: { + "priority": [ + { "if": "!car_access", "multiply_by": "0" } + ], + "speed": [ + { "if": "true", "limit_to": "car_average_speed" } + ], + "distance_influence": 200 + } +``` + +# temporal conditional access restrictions + +`car_access`, `bike_access` and `foot_access` do no longer include the conditional +access restrictions by default. If you want the old behaviour e.g. for car you need +to add the following statement in the `priority` section of your custom model: + +```json +{ "if": "car_temporal_access == NO", "multiply_by": "0" } +``` + +Depending on the use case e.g. for foot it might make more sense to use the +new default and show the conditional restriction value via the new path details +`access_conditional`, `vehicle_conditional` etc. built from the OSM tags +`access:conditional`, `vehicle:conditional` etc. +See how we utilized this for [GraphHopper Maps](https://graphhopper.com/maps/?point=50.909136%2C14.213924&point=50.90918%2C14.213549&profile=foot) +with a separate route hint (icon below the route distance). + +# max_slope + +Is now a signed value. To get the previous behavious use: + +``` +Math.abs(max_slope) +``` \ No newline at end of file diff --git a/docs/web/api-doc.md b/docs/web/api-doc.md index f6eb4e079d0..79a7ef018b0 100644 --- a/docs/web/api-doc.md +++ b/docs/web/api-doc.md @@ -33,22 +33,23 @@ Please note that unlike to the GET endpoint, points are specified in `[longitude All official parameters are shown in the following table - Parameter | Default | Description -:----------------|:--------|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - point | - | Specify multiple points for which the route should be calculated. The order is important. Specify at least two points. - locale | en | The locale of the resulting turn instructions. E.g. `pt_PT` for Portuguese or `de` for German - instructions | true | If instruction should be calculated and returned - profile | - | The profile to be used for the route calculation. - elevation | false | If `true` a third dimension - the elevation - is included in the polyline or in the GeoJson. IMPORTANT: If enabled you have to use a modified version of the decoding method or set points_encoded to `false`. See the points_encoded attribute for more details. Additionally a request can fail if the vehicle does not support elevation. See the features object for every vehicle. - points_encoded | true | If `false` the coordinates in `point` and `snapped_waypoints` are returned as array using the order [lon,lat,elevation] for every point. If `true` the coordinates will be encoded as string leading to less bandwidth usage. You'll need a special handling for the decoding of this string on the client-side. We provide open source code in [Java](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/java/com/graphhopper/http/WebHelper.java#L43) and [JavaScript](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/webapp/js/ghrequest.js#L139). It is especially important to use no 3rd party client if you set `elevation=true`! - debug | false | If true, the output will be formatted. - calc_points | true | If the points for the route should be calculated at all printing out only distance and time. - point_hint | - | Optional parameter. Specifies a hint for each `point` parameter to prefer a certain street for the closest location lookup. E.g. if there is an address or house with two or more neighboring streets you can control for which street the closest location is looked up. - snap_prevention | - | Optional parameter to avoid snapping to a certain road class or road environment. Current supported values: `motorway`, `trunk`, `ferry`, `tunnel`, `bridge` and `ford`. Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway` - details | - | Optional parameter. You can request additional details for the route: `average_speed`, `street_name`, `edge_id`, `road_class`, `road_environment`, `max_speed` and `time` (and see which other values are configured in `graph.encoded_values`). Multiple values are specified like `details=average_speed&details=time`. The returned format for one detail segment is `[fromRef, toRef, value]`. The `ref` references the points of the response. Value can also be `null` if the property does not exist for one detail segment. - curbside | any | Optional parameter applicable to edge-based routing only. It specifies on which side a query point should be relative to the driver when she leaves/arrives at a start/target/via point. Possible values: right, left, any. Specify for every point parameter. See similar heading parameter. - force_curbside | true | Optional parameter. If it is set to true there will be an exception in case the curbside parameters cannot be fulfilled (e.g. specifying the wrong side for one-ways). - timeout_ms | infinity| Optional parameter. Limits the request runtime to the minimum between the given value in milli-seconds and the server-side timeout configuration + Parameter | Default | Description +:----------------|:--------------------------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + point | - | Specify multiple points for which the route should be calculated. The order is important. Specify at least two points. + locale | en | The locale of the resulting turn instructions. E.g. `pt_PT` for Portuguese or `de` for German + instructions | true | If instruction should be calculated and returned + profile | - | The profile to be used for the route calculation. + elevation | false | If `true` a third dimension - the elevation - is included in the polyline or in the GeoJson. IMPORTANT: If enabled you have to use a modified version of the decoding method or set points_encoded to `false`. See the points_encoded attribute for more details. Additionally a request can fail if the vehicle does not support elevation. See the features object for every vehicle. + points_encoded | true | If `false` the coordinates in `point` and `snapped_waypoints` are returned as array using the order [lon,lat,elevation] for every point. If `true` the coordinates will be encoded as string leading to less bandwidth usage. You'll need a special handling for the decoding of this string on the client-side. We provide open source code in [Java](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/java/com/graphhopper/http/WebHelper.java#L43) and [JavaScript](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/webapp/js/ghrequest.js#L139). It is especially important to use no 3rd party client if you set `elevation=true`! + points_encoded_multiplier | 1e5 | Used in case `points_encoded=true` to encode the `points` string into an array of coordinates. + debug | false | If true, the output will be formatted. + calc_points | true | If the points for the route should be calculated at all printing out only distance and time. + point_hint | - | Optional parameter. When finding the closest road location for GPS coordinates provided in the `point` parameter this hint prefers a road with a similar name. E.g. if there is an address with two close roads you can control which street is preferred. Only include the road name and not the house number to improve the name matching quality. + snap_prevention | `[tunnel, bridge, ferry]` | 'Snapping' is the process of finding the closest road location for GPS coordinates provided in the `point` parameter. The `snap_prevention` parameter allows you to prevent snapping to specific types of roads. For example, if `snap_prevention` is set to bridge, the routing engine will avoid snapping to a bridge, even if it is the closest road for the given `point`. Current supported values: `motorway`, `trunk`, `ferry`, `tunnel`, `bridge` and `ford`. Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway`. Note that once snapped the routing algorithm can still route over bridges (or the other values). To avoid this you need to use the `custom_model`. + details | - | Optional parameter. You can request additional details for the route: `average_speed`, `street_name`, `edge_id`, `road_class`, `road_environment`, `max_speed` and `time` (and see which other values are configured in `graph.encoded_values`). Multiple values are specified like `details=average_speed&details=time`. The returned format for one detail segment is `[fromRef, toRef, value]`. The `ref` references the points of the response. Value can also be `null` if the property does not exist for one detail segment. + curbside | any | Optional parameter applicable to edge-based routing only. It specifies on which side a query point should be relative to the driver when she leaves/arrives at a start/target/via point. Possible values: right, left, any. Specify for every point parameter. See similar heading parameter. + curbside_strictness| strict | Optional parameter. If it is set to "strict" there will be an exception in case the curbside parameters cannot be fulfilled (e.g. specifying the wrong side for one-ways). If you don't want this use "soft". + timeout_ms | infinity | Optional parameter. Limits the request runtime to the minimum between the given value in milli-seconds and the server-side timeout configuration ### Hybrid @@ -71,7 +72,7 @@ ch.disable | `false` | Use this parameter in combination with one or mo custom_model | - | Customize the route calculations. See [the documentation](../core/custom-models.md) for more information. Only available for POST requests. algorithm |`astarbi` | The algorithm to calculate the route. Other options are `dijkstra`, `astar`, `astarbi`, `alternative_route` and `round_trip`. heading | NaN | Favour a heading direction for a certain point. Specify either one heading for the start point or as many as there are points. In this case headings are associated by their order to the specific points. Headings are given as north based clockwise angle between 0 and 360 degree. This parameter also influences the tour generated with `algorithm=round_trip` and forces the initial direction. -heading_penalty | 120 | Penalty for omitting a specified heading. The penalty corresponds to the accepted time delay in seconds in comparison to the route without a heading. +heading_penalty | 300 | Penalty for omitting a specified heading. The penalty corresponds to the accepted time delay in seconds in comparison to the route without a heading. pass_through | `false` | If `true` u-turns are avoided at via-points with regard to the `heading_penalty`. round_trip.distance | 10000 | If `algorithm=round_trip` this parameter configures approximative length of the resulting round trip round_trip.seed | 0 | If `algorithm=round_trip` this parameter introduces randomness if e.g. the first try wasn't good. diff --git a/example/pom.xml b/example/pom.xml index a68f84f8d70..f231b5e5594 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-example - 9.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Example com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT diff --git a/example/src/main/java/com/graphhopper/example/HeadingExample.java b/example/src/main/java/com/graphhopper/example/HeadingExample.java index 863b7b3738e..d2ef9ac102b 100644 --- a/example/src/main/java/com/graphhopper/example/HeadingExample.java +++ b/example/src/main/java/com/graphhopper/example/HeadingExample.java @@ -4,7 +4,6 @@ import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; import com.graphhopper.config.CHProfile; - import com.graphhopper.config.Profile; import com.graphhopper.util.CustomModel; import com.graphhopper.util.Parameters; @@ -13,6 +12,7 @@ import java.util.Arrays; import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.Op.LIMIT; import static com.graphhopper.json.Statement.Op.MULTIPLY; public class HeadingExample { @@ -34,9 +34,11 @@ static GraphHopper createGraphHopperInstance(String ghLoc) { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile(ghLoc); hopper.setGraphHopperLocation("target/heading-graph-cache"); - hopper.setProfiles(new Profile("car").setCustomModel(new CustomModel(). - addToPriority(If("road_access == DESTINATION", MULTIPLY, "0.1"))). - setVehicle("car").setTurnCosts(false)); + hopper.setEncodedValuesString("car_access, road_access, car_average_speed"); + hopper.setProfiles(new Profile("car"). + setCustomModel(new CustomModel(). + addToSpeed(If("true", LIMIT, "car_average_speed")). + addToPriority(If("!car_access", MULTIPLY, "0")))); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.importOrLoad(); return hopper; diff --git a/example/src/main/java/com/graphhopper/example/IsochroneExample.java b/example/src/main/java/com/graphhopper/example/IsochroneExample.java index 27b9af0d24c..bb2b76ab9ea 100644 --- a/example/src/main/java/com/graphhopper/example/IsochroneExample.java +++ b/example/src/main/java/com/graphhopper/example/IsochroneExample.java @@ -3,15 +3,15 @@ import com.graphhopper.GraphHopper; import com.graphhopper.config.Profile; import com.graphhopper.isochrone.algorithm.ShortestPathTree; -import com.graphhopper.routing.ev.*; +import com.graphhopper.routing.ev.Subnetwork; import com.graphhopper.routing.querygraph.QueryGraph; import com.graphhopper.routing.util.DefaultSnapFilter; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; import com.graphhopper.routing.weighting.Weighting; -import com.graphhopper.routing.weighting.custom.CustomModelParser; - import com.graphhopper.storage.index.Snap; +import com.graphhopper.util.GHUtility; +import com.graphhopper.util.PMap; import java.util.concurrent.atomic.AtomicInteger; @@ -21,11 +21,9 @@ public static void main(String[] args) { GraphHopper hopper = createGraphHopperInstance(relDir + "core/files/andorra.osm.pbf"); // get encoder from GraphHopper instance EncodingManager encodingManager = hopper.getEncodingManager(); - BooleanEncodedValue accessEnc = encodingManager.getBooleanEncodedValue(VehicleAccess.key("car")); - DecimalEncodedValue speedEnc = encodingManager.getDecimalEncodedValue(VehicleSpeed.key("car")); // snap some GPS coordinates to the routing graph and build a query graph - Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); + Weighting weighting = hopper.createWeighting(hopper.getProfile("car"), new PMap()); Snap snap = hopper.getLocationIndex().findClosest(42.508679, 1.532078, new DefaultSnapFilter(weighting, encodingManager.getBooleanEncodedValue(Subnetwork.key("car")))); QueryGraph queryGraph = QueryGraph.create(hopper.getBaseGraph(), snap); @@ -51,7 +49,7 @@ static GraphHopper createGraphHopperInstance(String ghLoc) { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile(ghLoc); hopper.setGraphHopperLocation("target/isochrone-graph-cache"); - hopper.setProfiles(new Profile("car").setVehicle("car").setTurnCosts(false)); + hopper.setProfiles(new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json"))); hopper.importOrLoad(); return hopper; } diff --git a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java index 1c0729af0ad..fff3369f135 100644 --- a/example/src/main/java/com/graphhopper/example/LocationIndexExample.java +++ b/example/src/main/java/com/graphhopper/example/LocationIndexExample.java @@ -3,12 +3,15 @@ import com.graphhopper.GraphHopper; import com.graphhopper.config.Profile; import com.graphhopper.routing.util.EdgeFilter; -import com.graphhopper.search.KVStorage; +import com.graphhopper.search.KVStorage.KValue; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.EdgeIteratorState; +import com.graphhopper.util.GHUtility; + +import java.util.Map; public class LocationIndexExample { public static void main(String[] args) { @@ -19,7 +22,8 @@ public static void main(String[] args) { public static void graphhopperLocationIndex(String relDir) { GraphHopper hopper = new GraphHopper(); - hopper.setProfiles(new Profile("car").setVehicle("car")); + hopper.setEncodedValuesString("car_access, car_average_speed, road_access, max_speed, road_environment, ferry_speed"); + hopper.setProfiles(new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json"))); hopper.setOSMFile(relDir + "core/files/andorra.osm.pbf"); hopper.setGraphHopperLocation("./target/locationindex-graph-cache"); hopper.importOrLoad(); @@ -34,8 +38,8 @@ public static void graphhopperLocationIndex(String relDir) { public static void lowLevelLocationIndex() { // If you don't use the GraphHopper class you have to use the low level API: - BaseGraph graph = new BaseGraph.Builder(1).create(); - graph.edge(0, 1).setKeyValues(KVStorage.KeyValue.createKV("name", "test edge")); + BaseGraph graph = new BaseGraph.Builder(4).create(); + graph.edge(0, 1).setKeyValues(Map.of("name", new KValue( "test edge"))); graph.getNodeAccess().setNode(0, 12, 42); graph.getNodeAccess().setNode(1, 12.01, 42.01); diff --git a/example/src/main/java/com/graphhopper/example/LowLevelAPIExample.java b/example/src/main/java/com/graphhopper/example/LowLevelAPIExample.java index 57a4444c185..b261b2e3e55 100644 --- a/example/src/main/java/com/graphhopper/example/LowLevelAPIExample.java +++ b/example/src/main/java/com/graphhopper/example/LowLevelAPIExample.java @@ -1,7 +1,7 @@ package com.graphhopper.example; -import com.graphhopper.routing.EdgeToEdgeRoutingAlgorithm; import com.graphhopper.routing.Dijkstra; +import com.graphhopper.routing.EdgeToEdgeRoutingAlgorithm; import com.graphhopper.routing.Path; import com.graphhopper.routing.ch.CHRoutingAlgorithmFactory; import com.graphhopper.routing.ch.PrepareContractionHierarchies; @@ -24,6 +24,10 @@ import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; +import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.Op.LIMIT; +import static com.graphhopper.json.Statement.Op.MULTIPLY; + /** * Use this example to gain access to the low level API of GraphHopper. * If you want to keep using the GraphHopper class but want to customize the internal EncodingManager @@ -42,7 +46,7 @@ public static void createAndSaveGraph() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 7, 2, false); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); - BaseGraph graph = new BaseGraph.Builder(em).setDir(new RAMDirectory(graphLocation, true)).create(); + BaseGraph graph = new BaseGraph.Builder(em).setDir(new GHDirectory(graphLocation, DAType.RAM_STORE)).create(); // Make a weighted edge between two nodes and set average speed to 50km/h EdgeIteratorState edge = graph.edge(0, 1).setDistance(1234).set(speedEnc, 50); @@ -67,7 +71,7 @@ public static void createAndSaveGraph() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 7, 2, false); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); - BaseGraph graph = new BaseGraph.Builder(em).setDir(new RAMDirectory(graphLocation, true)).build(); + BaseGraph graph = new BaseGraph.Builder(em).setDir(new GHDirectory(graphLocation, DAType.RAM_STORE)).build(); graph.loadExisting(); // Load the location index @@ -79,8 +83,9 @@ public static void createAndSaveGraph() { Snap fromSnap = index.findClosest(15.15, 20.20, EdgeFilter.ALL_EDGES); Snap toSnap = index.findClosest(15.25, 20.21, EdgeFilter.ALL_EDGES); QueryGraph queryGraph = QueryGraph.create(graph, fromSnap, toSnap); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, em, TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel()); - Path path = new Dijkstra(queryGraph, weighting, TraversalMode.NODE_BASED).calcPath(fromSnap.getClosestNode(), toSnap.getClosestNode()); + Weighting weighting = CustomModelParser.createWeighting(em, TurnCostProvider.NO_TURN_COST_PROVIDER, + new CustomModel().addToPriority(If("!" + accessEnc.getName(), MULTIPLY, "0")).addToSpeed(If("true", LIMIT, speedEnc.getName()))); + Path path = new Dijkstra(queryGraph, queryGraph.wrapWeighting(weighting), TraversalMode.NODE_BASED).calcPath(fromSnap.getClosestNode(), toSnap.getClosestNode()); assert Helper.round(path.getDistance(), -2) == 1500; // calculate without location index (get the fromId and toId nodes from other code parts) @@ -94,12 +99,13 @@ public static void useContractionHierarchiesToMakeQueriesFaster() { BooleanEncodedValue accessEnc = VehicleAccess.create("car"); DecimalEncodedValue speedEnc = VehicleSpeed.create("car", 7, 2, false); EncodingManager em = EncodingManager.start().add(accessEnc).add(speedEnc).build(); - Weighting weighting = CustomModelParser.createWeighting(accessEnc, speedEnc, null, em, TurnCostProvider.NO_TURN_COST_PROVIDER, new CustomModel()); - CHConfig chConfig = CHConfig.nodeBased("my_profile", weighting); BaseGraph graph = new BaseGraph.Builder(em) - .setDir(new RAMDirectory(graphLocation, true)) + .setDir(new GHDirectory(graphLocation, DAType.RAM_STORE)) .create(); graph.flush(); + Weighting weighting = CustomModelParser.createWeighting(em, TurnCostProvider.NO_TURN_COST_PROVIDER, + new CustomModel().addToPriority(If("!" + accessEnc.getName(), MULTIPLY, "0")).addToSpeed(If("true", LIMIT, speedEnc.getName()))); + CHConfig chConfig = CHConfig.nodeBased("my_profile", weighting); // Set node coordinates and build location index NodeAccess na = graph.getNodeAccess(); diff --git a/example/src/main/java/com/graphhopper/example/RoutingExample.java b/example/src/main/java/com/graphhopper/example/RoutingExample.java index a1cfb314014..19940a5fb8c 100644 --- a/example/src/main/java/com/graphhopper/example/RoutingExample.java +++ b/example/src/main/java/com/graphhopper/example/RoutingExample.java @@ -35,8 +35,10 @@ static GraphHopper createGraphHopperInstance(String ghLoc) { // specify where to store graphhopper files hopper.setGraphHopperLocation("target/routing-graph-cache"); + // add all encoded values that are used in the custom model, these are also available as path details or for client-side custom models + hopper.setEncodedValuesString("car_access, car_average_speed, road_access, road_environment, max_speed, ferry_speed"); // see docs/core/profiles.md to learn more about profiles - hopper.setProfiles(new Profile("car").setVehicle("car").setTurnCosts(false)); + hopper.setProfiles(new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json"))); // this enables speed mode for the profile we called car hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); @@ -74,7 +76,7 @@ public static void routing(GraphHopper hopper) { // System.out.println("distance " + instruction.getDistance() + " for instruction: " + instruction.getTurnDescription(tr)); } assert il.size() == 6; - assert Helper.round(path.getDistance(), -2) == 900; + assert Helper.round(path.getDistance(), -2) == 600; } public static void speedModeVersusFlexibleMode(GraphHopper hopper) { @@ -83,20 +85,20 @@ public static void speedModeVersusFlexibleMode(GraphHopper hopper) { GHResponse res = hopper.route(req); if (res.hasErrors()) throw new RuntimeException(res.getErrors().toString()); - assert Helper.round(res.getBest().getDistance(), -2) == 900; + assert Helper.round(res.getBest().getDistance(), -2) == 600; } public static void alternativeRoute(GraphHopper hopper) { // calculate alternative routes between two points (supported with and without CH) GHRequest req = new GHRequest().setProfile("car"). - addPoint(new GHPoint(42.502904, 1.514714)).addPoint(new GHPoint(42.508774, 1.537094)). + addPoint(new GHPoint(42.506701, 1.521668)).addPoint(new GHPoint(42.509533, 1.540185)). setAlgorithm(Parameters.Algorithms.ALT_ROUTE); req.getHints().putObject(Parameters.Algorithms.AltRoute.MAX_PATHS, 3); GHResponse res = hopper.route(req); if (res.hasErrors()) throw new RuntimeException(res.getErrors().toString()); assert res.getAll().size() == 2; - assert Helper.round(res.getBest().getDistance(), -2) == 2200; + assert Helper.round(res.getBest().getDistance(), -2) == 1800; } /** @@ -107,8 +109,8 @@ public static void customizableRouting(String ghLoc) { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile(ghLoc); hopper.setGraphHopperLocation("target/routing-custom-graph-cache"); - CustomModel serverSideCustomModel = new CustomModel(); - hopper.setProfiles(new Profile("car_custom").setCustomModel(serverSideCustomModel).setVehicle("car")); + hopper.setEncodedValuesString("car_access, car_average_speed, road_access, road_environment, max_speed, ferry_speed"); + hopper.setProfiles(new Profile("car_custom").setCustomModel(GHUtility.loadCustomModelFromJar("car.json"))); // The hybrid mode uses the "landmark algorithm" and is up to 15x faster than the flexible mode (Dijkstra). // Still it is slower than the speed mode ("contraction hierarchies algorithm") ... @@ -124,21 +126,21 @@ public static void customizableRouting(String ghLoc) { if (res.hasErrors()) throw new RuntimeException(res.getErrors().toString()); - assert Math.round(res.getBest().getTime() / 1000d) == 94; + assert Math.round(res.getBest().getTime() / 1000d) == 95; - // 2. now avoid primary roads and reduce maximum speed, see docs/core/custom-models.md for an in-depth explanation + // 2. now avoid the secondary road and reduce the maximum speed, see docs/core/custom-models.md for an in-depth explanation // and also the blog posts https://www.graphhopper.com/?s=customizable+routing CustomModel model = new CustomModel(); - model.addToPriority(If("road_class == PRIMARY", MULTIPLY, "0.5")); + model.addToPriority(If("road_class == SECONDARY", MULTIPLY, "0.5")); - // unconditional limit to 100km/h - model.addToPriority(If("true", LIMIT, "100")); + // unconditional limit to 20km/h + model.addToSpeed(If("true", LIMIT, "30")); req.setCustomModel(model); res = hopper.route(req); if (res.hasErrors()) throw new RuntimeException(res.getErrors().toString()); - assert Math.round(res.getBest().getTime() / 1000d) == 164; + assert Math.round(res.getBest().getTime() / 1000d) == 184; } } diff --git a/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java b/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java index 4cda68fb83e..17fb67af5a7 100644 --- a/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java +++ b/example/src/main/java/com/graphhopper/example/RoutingExampleTC.java @@ -7,9 +7,12 @@ import com.graphhopper.ResponsePath; import com.graphhopper.config.CHProfile; import com.graphhopper.config.Profile; +import com.graphhopper.util.TurnCostsConfig; +import com.graphhopper.util.GHUtility; import com.graphhopper.util.Parameters; import java.util.Arrays; +import java.util.List; import static com.graphhopper.util.Parameters.Curbsides.CURBSIDE_ANY; import static com.graphhopper.util.Parameters.Curbsides.CURBSIDE_RIGHT; @@ -23,7 +26,7 @@ public static void main(String[] args) { GraphHopper hopper = createGraphHopperInstance(relDir + "core/files/andorra.osm.pbf"); routeWithTurnCosts(hopper); routeWithTurnCostsAndCurbsides(hopper); - routeWithTurnCostsAndOtherUTurnCosts(hopper); + routeWithTurnCostsAndCurbsidesAndOtherUTurnCosts(hopper); } public static void routeWithTurnCosts(GraphHopper hopper) { @@ -36,18 +39,19 @@ public static void routeWithTurnCostsAndCurbsides(GraphHopper hopper) { GHRequest req = new GHRequest(42.50822, 1.533966, 42.506899, 1.525372). setCurbsides(Arrays.asList(CURBSIDE_ANY, CURBSIDE_RIGHT)). setProfile("car"); - route(hopper, req, 1729, 110_800); + route(hopper, req, 1370, 89_500); } - public static void routeWithTurnCostsAndOtherUTurnCosts(GraphHopper hopper) { + public static void routeWithTurnCostsAndCurbsidesAndOtherUTurnCosts(GraphHopper hopper) { GHRequest req = new GHRequest(42.50822, 1.533966, 42.506899, 1.525372) .setCurbsides(Arrays.asList(CURBSIDE_ANY, CURBSIDE_RIGHT)) // to change u-turn costs per request we have to disable CH. otherwise the u-turn costs we set per request // will be ignored and those set for our profile will be used. .putHint(Parameters.CH.DISABLE, true) .setProfile("car"); - route(hopper, req.putHint(Parameters.Routing.U_TURN_COSTS, 10), 1370, 98_700); - route(hopper, req.putHint(Parameters.Routing.U_TURN_COSTS, 15), 1370, 103_700); + route(hopper, req.putHint(Parameters.Routing.U_TURN_COSTS, 10), 1370, 89_500); + route(hopper, req.putHint(Parameters.Routing.U_TURN_COSTS, 100), 1730, 112_000); // be aware there is a shorter (but slower) alternative with very similar weight via Avinguda de Tarragona + route(hopper, req.putHint(Parameters.Routing.U_TURN_COSTS, 200), 1730, 112_000); } private static void route(GraphHopper hopper, GHRequest req, int expectedDistance, int expectedTime) { @@ -55,7 +59,7 @@ private static void route(GraphHopper hopper, GHRequest req, int expectedDistanc // handle errors if (rsp.hasErrors()) // if you get: Impossible curbside constraint: 'curbside=right' - // you either specify 'curbside=any' or Parameters.Routing.FORCE_CURBSIDE=false to ignore this situation + // you can specify 'curbside=any' or Parameters.Routing.CURBSIDE_STRICTNESS="soft" to avoid an error throw new RuntimeException(rsp.getErrors().toString()); ResponsePath path = rsp.getBest(); assert Math.abs(expectedDistance - path.getDistance()) < 1 : "unexpected distance : " + path.getDistance() + " vs. " + expectedDistance; @@ -67,12 +71,12 @@ static GraphHopper createGraphHopperInstance(String ghLoc) { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile(ghLoc); hopper.setGraphHopperLocation("target/routing-tc-graph-cache"); - Profile profile = new Profile("car").setVehicle("car") - // enabling turn costs means OSM turn restriction constraints like 'no_left_turn' will be taken into account - .setTurnCosts(true) - // we can also set u_turn_costs (in seconds). by default no u-turns are allowed, but with this setting - // we will consider u-turns at all junctions with a 40s time penalty - .putHint("u_turn_costs", 40); + // add all encoded values that are used in the custom model, these are also available as path details or for client-side custom models + hopper.setEncodedValuesString("car_access, car_average_speed, road_access, road_environment, max_speed, ferry_speed"); + Profile profile = new Profile("car").setCustomModel(GHUtility.loadCustomModelFromJar("car.json")) + // enabling turn costs means OSM turn restriction constraints like 'no_left_turn' will be taken into account for the specified access restrictions + // we can also set u_turn_costs (in seconds). i.e. we will consider u-turns at all junctions with a 40s time penalty + .setTurnCostsConfig(new TurnCostsConfig(List.of("motorcar", "motor_vehicle"), 40)); hopper.setProfiles(profile); // enable CH for our profile. since turn costs are enabled this will take more time and memory to prepare than // without turn costs. diff --git a/map-matching/pom.xml b/map-matching/pom.xml index 07e445177d7..7aa9cfe10b4 100644 --- a/map-matching/pom.xml +++ b/map-matching/pom.xml @@ -3,14 +3,14 @@ 4.0.0 com.graphhopper graphhopper-map-matching - 9.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Map Matching com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT diff --git a/map-matching/src/main/java/com/graphhopper/matching/Distributions.java b/map-matching/src/main/java/com/graphhopper/matching/Distributions.java index 2e330ec9e15..7a4d82d1f72 100644 --- a/map-matching/src/main/java/com/graphhopper/matching/Distributions.java +++ b/map-matching/src/main/java/com/graphhopper/matching/Distributions.java @@ -16,11 +16,7 @@ */ package com.graphhopper.matching; -import static java.lang.Math.PI; -import static java.lang.Math.exp; -import static java.lang.Math.log; -import static java.lang.Math.pow; -import static java.lang.Math.sqrt; +import static java.lang.Math.*; /** * Implements various probability distributions. @@ -36,7 +32,7 @@ static double normalDistribution(double sigma, double x) { * arithmetic underflow for very small probabilities. */ public static double logNormalDistribution(double sigma, double x) { - return Math.log(1.0 / (sqrt(2.0 * PI) * sigma)) + (-0.5 * pow(x / sigma, 2)); + return -0.5 * pow(x / sigma, 2); } /** @@ -53,6 +49,6 @@ static double exponentialDistribution(double beta, double x) { * @param beta =1/lambda with lambda being the standard exponential distribution rate parameter */ static double logExponentialDistribution(double beta, double x) { - return log(1.0 / beta) - (x / beta); + return -x / beta; } } diff --git a/map-matching/src/main/java/com/graphhopper/matching/EdgeMatch.java b/map-matching/src/main/java/com/graphhopper/matching/EdgeMatch.java index 52ebcde40df..319a6536ffa 100644 --- a/map-matching/src/main/java/com/graphhopper/matching/EdgeMatch.java +++ b/map-matching/src/main/java/com/graphhopper/matching/EdgeMatch.java @@ -18,6 +18,7 @@ package com.graphhopper.matching; import com.graphhopper.util.EdgeIteratorState; + import java.util.List; /** diff --git a/map-matching/src/main/java/com/graphhopper/matching/MapMatching.java b/map-matching/src/main/java/com/graphhopper/matching/MapMatching.java index 651c6764848..43bebb4799f 100644 --- a/map-matching/src/main/java/com/graphhopper/matching/MapMatching.java +++ b/map-matching/src/main/java/com/graphhopper/matching/MapMatching.java @@ -69,10 +69,14 @@ public class MapMatching { private final BaseGraph graph; private final Router router; private final LocationIndexTree locationIndex; - private double measurementErrorSigma = 50.0; + private double measurementErrorSigma = 10.0; private double transitionProbabilityBeta = 2.0; + private double uTurnCost = 40.0; private final DistanceCalc distanceCalc = new DistancePlaneProjection(); private QueryGraph queryGraph; + private boolean collectDebugInfo = false; + private MatchDebugInfo debugInfo; + private List debugTransitions; private Map statistics = new HashMap<>(); @@ -195,6 +199,18 @@ public void setMeasurementErrorSigma(double measurementErrorSigma) { this.measurementErrorSigma = measurementErrorSigma; } + public void setUTurnCost(double uTurnCost) { + this.uTurnCost = uTurnCost; + } + + public void setCollectDebugInfo(boolean collectDebugInfo) { + this.collectDebugInfo = collectDebugInfo; + } + + public MatchDebugInfo getDebugInfo() { + return debugInfo; + } + public MatchResult match(List observations) { List filteredObservations = filterObservations(observations); statistics.put("filteredObservations", filteredObservations.size()); @@ -215,11 +231,19 @@ public MatchResult match(List observations) { // Compute the most likely sequence of map matching candidates: List> seq = computeViterbiSequence(timeSteps); + // For statistics, deduplicate to one entry per time step (U-turns create multiple entries per step). + // Keep the last entry for each observation (the final state after any U-turns). + List> seqPerTimeStep = new ArrayList<>(); + for (int i = 0; i < seq.size(); i++) { + if (i == seq.size() - 1 || seq.get(i).observation != seq.get(i + 1).observation) { + seqPerTimeStep.add(seq.get(i)); + } + } statistics.put("transitionDistances", seq.stream().filter(s -> s.transitionDescriptor != null).mapToLong(s -> Math.round(s.transitionDescriptor.getDistance())).toArray()); statistics.put("visitedNodes", router.getVisitedNodes()); - statistics.put("snapDistanceRanks", IntStream.range(0, seq.size()).map(i -> snapsPerObservation.get(i).indexOf(seq.get(i).state.getSnap())).toArray()); - statistics.put("snapDistances", seq.stream().mapToDouble(s -> s.state.getSnap().getQueryDistance()).toArray()); - statistics.put("maxSnapDistances", IntStream.range(0, seq.size()).mapToDouble(i -> snapsPerObservation.get(i).stream().mapToDouble(Snap::getQueryDistance).max().orElse(-1.0)).toArray()); + statistics.put("snapDistanceRanks", IntStream.range(0, seqPerTimeStep.size()).map(i -> snapsPerObservation.get(i).indexOf(seqPerTimeStep.get(i).state.getSnap())).toArray()); + statistics.put("snapDistances", seqPerTimeStep.stream().mapToDouble(s -> s.state.getSnap().getQueryDistance()).toArray()); + statistics.put("maxSnapDistances", IntStream.range(0, seqPerTimeStep.size()).mapToDouble(i -> snapsPerObservation.get(i).stream().mapToDouble(Snap::getQueryDistance).max().orElse(-1.0)).toArray()); List path = seq.stream().filter(s1 -> s1.transitionDescriptor != null).flatMap(s1 -> s1.transitionDescriptor.calcEdges().stream()).collect(Collectors.toList()); @@ -231,9 +255,130 @@ public MatchResult match(List observations) { result.setGPXEntriesLength(gpxLength(observations)); result.setGraph(queryGraph); result.setWeighting(queryGraphWeighting); + + if (collectDebugInfo) { + buildDebugInfo(observations, filteredObservations, timeSteps, seq); + } + return result; } + private void buildDebugInfo(List originalObservations, List filteredObservations, + List timeSteps, + List> seq) { + HmmProbabilities probabilities = new HmmProbabilities(measurementErrorSigma, transitionProbabilityBeta); + + // Build set of chosen states for marking + Set chosenStates = seq.stream().map(s -> s.state).collect(Collectors.toSet()); + + // Build a map from State -> (timeStep, candidateIndex) + Map stateIndex = new HashMap<>(); + for (int t = 0; t < timeSteps.size(); t++) { + List cands = timeSteps.get(t).candidates; + for (int c = 0; c < cands.size(); c++) { + stateIndex.put(cands.get(c), new int[]{t, c}); + } + } + + // Build set of chosen transition keys + Set chosenTransitionKeys = new HashSet<>(); + for (int i = 1; i < seq.size(); i++) { + int[] fromIdx = stateIndex.get(seq.get(i - 1).state); + int[] toIdx = stateIndex.get(seq.get(i).state); + if (fromIdx != null && toIdx != null) { + chosenTransitionKeys.add(fromIdx[0] + ":" + fromIdx[1] + "->" + toIdx[0] + ":" + toIdx[1]); + } + } + + // Mark chosen transitions + if (debugTransitions != null) { + List marked = new ArrayList<>(); + for (MatchDebugInfo.TransitionInfo ti : debugTransitions) { + String key = ti.fromTimeStep + ":" + ti.fromCandidateIndex + "->" + ti.toTimeStep + ":" + ti.toCandidateIndex; + if (chosenTransitionKeys.contains(key)) { + marked.add(new MatchDebugInfo.TransitionInfo( + ti.fromTimeStep, ti.fromCandidateIndex, ti.toTimeStep, ti.toCandidateIndex, + ti.transitionLogProbability, ti.routeDistance, ti.linearDistance, + true, ti.routeGeometry, ti.uTurn, ti.uTurnCost + )); + } else { + marked.add(ti); + } + } + debugTransitions = marked; + } + + // Add chosen U-turn transitions (same-timestep transitions in the sequence) + for (int i = 1; i < seq.size(); i++) { + int[] fromIdx = stateIndex.get(seq.get(i - 1).state); + int[] toIdx = stateIndex.get(seq.get(i).state); + if (fromIdx != null && toIdx != null && fromIdx[0] == toIdx[0]) { + // Same time step = U-turn + State fromState = seq.get(i - 1).state; + double[] snapPt = new double[]{fromState.getSnap().getSnappedPoint().lat, fromState.getSnap().getSnappedPoint().lon}; + debugTransitions.add(new MatchDebugInfo.TransitionInfo( + fromIdx[0], fromIdx[1], toIdx[0], toIdx[1], + 0.0, 0.0, 0.0, + true, Arrays.asList(snapPt, snapPt), true, uTurnCost + )); + } + } + + // Original observations + List origObs = originalObservations.stream() + .map(o -> new double[]{o.getPoint().lat, o.getPoint().lon}) + .collect(Collectors.toList()); + + // Filtered observations + List filtObs = filteredObservations.stream() + .map(o -> new double[]{o.getPoint().lat, o.getPoint().lon}) + .collect(Collectors.toList()); + + // Time steps with candidates + List timeStepInfos = new ArrayList<>(); + for (int t = 0; t < timeSteps.size(); t++) { + ObservationWithCandidateStates ts = timeSteps.get(t); + List candidates = new ArrayList<>(); + for (int c = 0; c < ts.candidates.size(); c++) { + State state = ts.candidates.get(c); + double distance = state.getSnap().getQueryDistance(); + candidates.add(new MatchDebugInfo.Candidate( + t, c, + state.getSnap().getSnappedPoint().lat, + state.getSnap().getSnappedPoint().lon, + distance, + probabilities.emissionLogProbability(distance), + state.isOnDirectedEdge(), + state.getSnap().getClosestNode(), + chosenStates.contains(state) + )); + } + timeStepInfos.add(new MatchDebugInfo.TimeStepInfo( + t, + ts.observation.getPoint().lat, + ts.observation.getPoint().lon, + candidates + )); + } + + // Matched route geometry + List matchedRoute = new ArrayList<>(); + for (SequenceState s : seq) { + if (s.transitionDescriptor != null) { + PointList pl = s.transitionDescriptor.calcPoints(); + for (int i = 0; i < pl.size(); i++) { + matchedRoute.add(new double[]{pl.getLat(i), pl.getLon(i)}); + } + } + } + + debugInfo = new MatchDebugInfo( + measurementErrorSigma, transitionProbabilityBeta, + origObs, filtObs, timeStepInfos, + debugTransitions, matchedRoute + ); + } + /** * Filters observations to only those which will be used for map matching (i.e. those which * are separated by at least 2 * measurementErrorSigman @@ -276,8 +421,8 @@ public List filterObservations(List observations) { } public List findCandidateSnaps(final double queryLat, final double queryLon) { - double rLon = (measurementErrorSigma * 360.0 / DistanceCalcEarth.DIST_EARTH.calcCircumference(queryLat)); - double rLat = measurementErrorSigma / DistanceCalcEarth.METERS_PER_DEGREE; + double rLon = (2 * measurementErrorSigma * 360.0 / DistanceCalcEarth.DIST_EARTH.calcCircumference(queryLat)); + double rLat = 2 * measurementErrorSigma / DistanceCalcEarth.METERS_PER_DEGREE; Envelope envelope = new Envelope(queryLon, queryLon, queryLat, queryLat); for (int i = 0; i < 50; i++) { envelope.expandBy(rLon, rLat); @@ -384,6 +529,39 @@ private List> computeViterbiSequence(Lis return Collections.emptyList(); } + if (collectDebugInfo) { + debugTransitions = new ArrayList<>(); + } + + // Build state-to-index mapping for debug + final Map stateToIndex = collectDebugInfo ? new HashMap<>() : null; + if (collectDebugInfo) { + for (int t = 0; t < timeSteps.size(); t++) { + List cands = timeSteps.get(t).candidates; + for (int c = 0; c < cands.size(); c++) { + stateToIndex.put(cands.get(c), new int[]{t, c}); + } + } + } + + // Build U-turn pair map: for each directed candidate, find its opposite-direction twin + // (same snap point, swapped incoming/outgoing edges) + final Map uTurnPairs = new HashMap<>(); + for (ObservationWithCandidateStates ts : timeSteps) { + for (int a = 0; a < ts.candidates.size(); a++) { + State sa = ts.candidates.get(a); + if (!sa.isOnDirectedEdge()) continue; + for (int b = a + 1; b < ts.candidates.size(); b++) { + State sb = ts.candidates.get(b); + if (!sb.isOnDirectedEdge()) continue; + if (sa.getSnap().getClosestNode() == sb.getSnap().getClosestNode()) { + uTurnPairs.put(sa, sb); + uTurnPairs.put(sb, sa); + } + } + } + } + final HmmProbabilities probabilities = new HmmProbabilities(measurementErrorSigma, transitionProbabilityBeta); final Map labels = new HashMap<>(); Map, Path> roadPaths = new HashMap<>(); @@ -406,6 +584,25 @@ private List> computeViterbiSequence(Lis if (qe.timeStep == timeSteps.size() - 1) break; State from = qe.state; + + // U-turn edge: transition to opposite-direction candidate at same snap point, same time step + State uTurnTarget = uTurnPairs.get(from); + if (uTurnTarget != null) { + double uTurnMinusLogProbability = qe.minusLogProbability + uTurnCost; + Label existingLabel = labels.get(uTurnTarget); + if (existingLabel == null || uTurnMinusLogProbability < existingLabel.minusLogProbability) { + q.stream().filter(oldQe -> !oldQe.isDeleted && oldQe.state == uTurnTarget).findFirst().ifPresent(oldQe -> oldQe.isDeleted = true); + Label label = new Label(); + label.state = uTurnTarget; + label.timeStep = qe.timeStep; + label.back = qe; + label.minusLogProbability = uTurnMinusLogProbability; + q.add(label); + labels.put(uTurnTarget, label); + roadPaths.put(new Transition<>(from, uTurnTarget), new Path(queryGraph)); + } + } + ObservationWithCandidateStates timeStep = timeSteps.get(qe.timeStep); ObservationWithCandidateStates nextTimeStep = timeSteps.get(qe.timeStep + 1); final double linearDistance = distanceCalc.calcDist(timeStep.observation.getPoint().lat, timeStep.observation.getPoint().lon, @@ -424,6 +621,21 @@ private List> computeViterbiSequence(Lis Transition transition = new Transition<>(from, to); roadPaths.put(transition, path); double minusLogProbability = qe.minusLogProbability - probabilities.emissionLogProbability(to.getSnap().getQueryDistance()) - transitionLogProbability; + + if (collectDebugInfo) { + int[] fromIdx = stateToIndex.get(from); + int[] toIdx = stateToIndex.get(to); + List routeGeom = new ArrayList<>(); + PointList pl = path.calcPoints(); + for (int j = 0; j < pl.size(); j++) { + routeGeom.add(new double[]{pl.getLat(j), pl.getLon(j)}); + } + debugTransitions.add(new MatchDebugInfo.TransitionInfo( + fromIdx[0], fromIdx[1], toIdx[0], toIdx[1], + transitionLogProbability, path.getDistance(), linearDistance, + false, routeGeom, false, 0.0 + )); + } Label label1 = labels.get(to); if (label1 == null || minusLogProbability < label1.minusLogProbability) { q.stream().filter(oldQe -> !oldQe.isDeleted && oldQe.state == to).findFirst().ifPresent(oldQe -> oldQe.isDeleted = true); @@ -550,7 +762,7 @@ private static class MapMatchedPath extends Path { super(graph); int prevEdge = EdgeIterator.NO_EDGE; for (EdgeIteratorState edge : edges) { - addDistance(edge.getDistance()); + addDistance_mm(edge.getDistance_mm()); addTime(GHUtility.calcMillisWithTurnMillis(weighting, edge, false, prevEdge)); addEdge(edge.getEdge()); prevEdge = edge.getEdge(); diff --git a/map-matching/src/main/java/com/graphhopper/matching/MatchDebugInfo.java b/map-matching/src/main/java/com/graphhopper/matching/MatchDebugInfo.java new file mode 100644 index 00000000000..859c6987812 --- /dev/null +++ b/map-matching/src/main/java/com/graphhopper/matching/MatchDebugInfo.java @@ -0,0 +1,117 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.matching; + +import java.util.List; + +public class MatchDebugInfo { + + public static class Candidate { + public final int timeStep; + public final int candidateIndex; + public final double snappedLat; + public final double snappedLon; + public final double queryDistance; + public final double emissionLogProbability; + public final boolean directed; + public final int closestNode; + public final boolean chosen; + + public Candidate(int timeStep, int candidateIndex, double snappedLat, double snappedLon, + double queryDistance, double emissionLogProbability, boolean directed, + int closestNode, boolean chosen) { + this.timeStep = timeStep; + this.candidateIndex = candidateIndex; + this.snappedLat = snappedLat; + this.snappedLon = snappedLon; + this.queryDistance = queryDistance; + this.emissionLogProbability = emissionLogProbability; + this.directed = directed; + this.closestNode = closestNode; + this.chosen = chosen; + } + } + + public static class TimeStepInfo { + public final int index; + public final double observationLat; + public final double observationLon; + public final List candidates; + + public TimeStepInfo(int index, double observationLat, double observationLon, List candidates) { + this.index = index; + this.observationLat = observationLat; + this.observationLon = observationLon; + this.candidates = candidates; + } + } + + public static class TransitionInfo { + public final int fromTimeStep; + public final int fromCandidateIndex; + public final int toTimeStep; + public final int toCandidateIndex; + public final double transitionLogProbability; + public final double routeDistance; + public final double linearDistance; + public final boolean chosen; + public final List routeGeometry; + public final boolean uTurn; + public final double uTurnCost; + + public TransitionInfo(int fromTimeStep, int fromCandidateIndex, + int toTimeStep, int toCandidateIndex, + double transitionLogProbability, double routeDistance, + double linearDistance, boolean chosen, + List routeGeometry, + boolean uTurn, double uTurnCost) { + this.fromTimeStep = fromTimeStep; + this.fromCandidateIndex = fromCandidateIndex; + this.toTimeStep = toTimeStep; + this.toCandidateIndex = toCandidateIndex; + this.transitionLogProbability = transitionLogProbability; + this.routeDistance = routeDistance; + this.linearDistance = linearDistance; + this.chosen = chosen; + this.routeGeometry = routeGeometry; + this.uTurn = uTurn; + this.uTurnCost = uTurnCost; + } + } + + public final double measurementErrorSigma; + public final double transitionProbabilityBeta; + public final List originalObservations; + public final List filteredObservations; + public final List timeSteps; + public final List transitions; + public final List matchedRoute; + + public MatchDebugInfo(double measurementErrorSigma, double transitionProbabilityBeta, + List originalObservations, List filteredObservations, + List timeSteps, List transitions, + List matchedRoute) { + this.measurementErrorSigma = measurementErrorSigma; + this.transitionProbabilityBeta = transitionProbabilityBeta; + this.originalObservations = originalObservations; + this.filteredObservations = filteredObservations; + this.timeSteps = timeSteps; + this.transitions = transitions; + this.matchedRoute = matchedRoute; + } +} diff --git a/map-matching/tools/debug-visualizer-template.html b/map-matching/tools/debug-visualizer-template.html new file mode 100644 index 00000000000..e8d605f4f06 --- /dev/null +++ b/map-matching/tools/debug-visualizer-template.html @@ -0,0 +1,336 @@ + + + + +MapMatching Debug Visualizer + + + + + +

    +
    + + + diff --git a/map-matching/tools/mapmatch.py b/map-matching/tools/mapmatch.py new file mode 100755 index 00000000000..fc19284b739 --- /dev/null +++ b/map-matching/tools/mapmatch.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python3 +"""CLI tool to post a GPX file to the GraphHopper map-matching API and visualize the result.""" + +import argparse +import http.server +import json +import os +import sys +import threading +import urllib.request +import webbrowser +import xml.etree.ElementTree as ET + +NS = {"gpx": "http://www.topografix.com/GPX/1/1"} +MAX_POINTS = 3000 # chunk size threshold +DEBUG_TEMPLATE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "debug-visualizer-template.html") + + +def main(): + parser = argparse.ArgumentParser(description="Map-match a GPX file via GraphHopper and visualize the result") + parser.add_argument("gpx_file", help="Path to GPX file") + parser.add_argument("--profile", default="car", help="Routing profile (default: car)") + parser.add_argument("--url", default="https://graphhopper.com/api/1", help="GraphHopper base URL") + parser.add_argument("--key", default="7830772c-f1a9-4b29-8c27-b9a82f86ee7f", help="GraphHopper API key") + parser.add_argument("--port", type=int, default=8899, help="Local server port for visualization") + parser.add_argument("--chunk-size", type=int, default=MAX_POINTS, help=f"Max trackpoints per chunk (default: {MAX_POINTS})") + parser.add_argument("--chunk", type=int, default=None, help="Only match and show this chunk (1-based)") + parser.add_argument("--start", type=int, default=None, help="First trackpoint index (0-based, before chunking)") + parser.add_argument("--end", type=int, default=None, help="Last trackpoint index (0-based, inclusive, before chunking)") + parser.add_argument("--gps-accuracy", type=int, default=None, help="GPS accuracy in meters (GraphHopper gps_accuracy parameter)") + parser.add_argument("--debug", action="store_true", help="Request debug output and open the debug visualizer") + parser.add_argument("--debug-template", default=DEBUG_TEMPLATE, help="Path to debug-visualizer-template.html") + args = parser.parse_args() + + with open(args.gpx_file, "rb") as f: + gpx_text = f.read().decode() + + root = ET.fromstring(gpx_text) + trkpts = root.findall(".//gpx:trkpt", NS) + + print(f"{len(trkpts)} trackpoints in GPX file") + + if args.start is not None or args.end is not None: + s = args.start if args.start is not None else 0 + e = (args.end + 1) if args.end is not None else len(trkpts) + trkpts = trkpts[s:e] + print(f"Using points {s}-{e-1} ({len(trkpts)} points)") + + if len(trkpts) <= args.chunk_size: + chunks = [trkpts] + else: + chunks = [] + for i in range(0, len(trkpts), args.chunk_size): + chunks.append(trkpts[i:i + args.chunk_size]) + print(f"Splitting into {len(chunks)} chunks of up to {args.chunk_size} points") + + if args.chunk is not None: + if args.chunk < 1 or args.chunk > len(chunks): + print(f"Error: --chunk must be between 1 and {len(chunks)}", file=sys.stderr) + sys.exit(1) + idx = args.chunk - 1 + chunks = [chunks[idx]] + # narrow original coords to just this chunk's range + start = idx * args.chunk_size + end = min(start + args.chunk_size, len(trkpts)) + original_coords = [[float(tp.get("lon")), float(tp.get("lat"))] for tp in trkpts[start:end]] + print(f"Selected chunk {args.chunk} (points {start}-{end-1})") + else: + original_coords = [[float(tp.get("lon")), float(tp.get("lat"))] for tp in trkpts] + + if args.debug: + # Debug mode: single request, no chunking, use debug visualizer + all_pts = [tp for chunk in chunks for tp in chunk] + gpx_data = build_gpx(all_pts) + print(f"Matching {len(all_pts)} points in debug mode...", end=" ", flush=True) + debug_json = post_match(gpx_data, args.url, args.profile, args.key, args.gps_accuracy, debug=True) + print("done") + html = build_debug_html(debug_json, args.debug_template) + serve_and_open(html, args.port) + else: + matched_chunks = [] + for i, chunk in enumerate(chunks): + gpx_chunk = build_gpx(chunk) + label = f"chunk {i+1}/{len(chunks)}" if len(chunks) > 1 else "GPX" + if args.chunk is not None: + label = f"chunk {args.chunk}" + print(f"Matching {label} ({len(chunk)} points)...", end=" ", flush=True) + result = post_match(gpx_chunk, args.url, args.profile, args.key, args.gps_accuracy) + coords = decode_points(result["paths"][0]["points"]) + matched_chunks.append(coords) + print(f"-> {len(coords)} matched points") + + html = build_html(matched_chunks, original_coords) + serve_and_open(html, args.port) + + +def build_gpx(trkpts): + """Build a minimal GPX XML string from a list of trkpt Elements.""" + lines = ['', + '', + ''] + for tp in trkpts: + lat, lon = tp.get("lat"), tp.get("lon") + time_el = tp.find("gpx:time", NS) + if time_el is not None and time_el.text: + lines.append(f'') + else: + lines.append(f'') + lines.append('') + return "\n".join(lines).encode() + + +def post_match(gpx_data, base_url, profile, key, gps_accuracy=None, debug=False): + out_type = "debug" if debug else "json" + api_url = f"{base_url}/match?profile={profile}&type={out_type}&key={key}" + if gps_accuracy is not None: + api_url += f"&gps_accuracy={gps_accuracy}" + req = urllib.request.Request(api_url, data=gpx_data, headers={"Content-Type": "application/xml"}) + try: + with urllib.request.urlopen(req) as resp: + return json.loads(resp.read()) + except urllib.error.HTTPError as e: + body = e.read().decode() + print(f"\nAPI error {e.code}: {body}", file=sys.stderr) + sys.exit(1) + + +def decode_points(points): + """Decode points from the API response — either encoded polyline string or GeoJSON-like object.""" + if isinstance(points, str): + # Decode Google encoded polyline + coords = [] + index, lat, lng = 0, 0, 0 + while index < len(points): + for is_lng in (False, True): + shift, result = 0, 0 + while True: + b = ord(points[index]) - 63 + index += 1 + result |= (b & 0x1F) << shift + shift += 5 + if b < 0x20: + break + delta = ~(result >> 1) if (result & 1) else (result >> 1) + if is_lng: + lng += delta + else: + lat += delta + coords.append([lng / 1e5, lat / 1e5]) + return coords + else: + return points["coordinates"] + + +def snake_to_camel(obj): + """Recursively convert snake_case keys to camelCase.""" + if isinstance(obj, dict): + return {_to_camel(k): snake_to_camel(v) for k, v in obj.items()} + if isinstance(obj, list): + return [snake_to_camel(item) for item in obj] + return obj + + +def _to_camel(s): + parts = s.split("_") + return parts[0] + "".join(p.capitalize() for p in parts[1:]) + + +def build_debug_html(debug_json, template_path): + """Read the debug visualizer template and inject the debug JSON data.""" + template_path = os.path.normpath(template_path) + if not os.path.exists(template_path): + print(f"Error: debug template not found at {template_path}", file=sys.stderr) + sys.exit(1) + with open(template_path) as f: + template = f.read() + camel_json = snake_to_camel(debug_json) + return template.replace("/*DEBUG_DATA_PLACEHOLDER*/", json.dumps(camel_json)) + + +COLORS = [ + '#2196F3', '#4CAF50', '#FF9800', '#9C27B0', '#00BCD4', + '#E91E63', '#3F51B5', '#009688', '#FF5722', '#607D8B', +] + + +def build_html(matched_chunks, original_coords): + chunk_data = json.dumps([ + {"type": "Feature", "geometry": {"type": "LineString", "coordinates": coords}, + "properties": {"chunk": i}} + for i, coords in enumerate(matched_chunks) + ]) + + original_geojson = json.dumps({ + "type": "Feature", + "geometry": {"type": "LineString", "coordinates": original_coords}, + "properties": {"name": "original"} + }) + + colors_js = json.dumps(COLORS) + num_chunks = len(matched_chunks) + + return f""" + + + +Map Matching Result + + + + + +
    + + +""" + + +def serve_and_open(html, port): + html_bytes = html.encode() + + class Handler(http.server.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header("Content-Type", "text/html") + self.end_headers() + self.wfile.write(html_bytes) + + def log_message(self, format, *args): + pass + + server = http.server.HTTPServer(("127.0.0.1", port), Handler) + url = f"http://127.0.0.1:{port}" + print(f"Opening {url} — press Ctrl+C to stop") + threading.Timer(0.5, lambda: webbrowser.open(url)).start() + try: + server.serve_forever() + except KeyboardInterrupt: + print("\nDone.") + + +if __name__ == "__main__": + main() diff --git a/navigation/pom.xml b/navigation/pom.xml index 034ead2da21..bb7529b29ad 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-nav - 9.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Navigation com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT diff --git a/navigation/src/main/java/com/graphhopper/navigation/DistanceConfig.java b/navigation/src/main/java/com/graphhopper/navigation/DistanceConfig.java index b698b71d5bf..da1509e62d7 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/DistanceConfig.java +++ b/navigation/src/main/java/com/graphhopper/navigation/DistanceConfig.java @@ -17,6 +17,9 @@ */ package com.graphhopper.navigation; +import com.graphhopper.config.Profile; +import com.graphhopper.routing.util.TransportationMode; +import com.graphhopper.util.Helper; import com.graphhopper.util.TranslationMap; import java.util.ArrayList; @@ -28,22 +31,67 @@ public class DistanceConfig { final List voiceInstructions; + final DistanceUtils.Unit unit; + final String mode; - public DistanceConfig(DistanceUtils.Unit unit, TranslationMap translationMap, Locale locale) { - if (unit == DistanceUtils.Unit.METRIC) { - voiceInstructions = Arrays.asList( - new InitialVoiceInstructionConfig(FOR_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 4250, 250, unit), - new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 2000, 2), - new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_SINGULAR.metric, translationMap, locale, 1000, 1), - new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.metric, translationMap, locale, new int[]{400, 200}, new int[]{400, 200}) - ); - } else { - voiceInstructions = Arrays.asList( - new InitialVoiceInstructionConfig(FOR_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 4250, 250, unit), - new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_PLURAL.imperial, translationMap, locale, 3220, 2), - new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_SINGULAR.imperial, translationMap, locale, 1610, 1), - new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.imperial, translationMap, locale, new int[]{400, 200}, new int[]{1300, 600}) - ); + public DistanceConfig(DistanceUtils.Unit unit, TranslationMap translationMap, Locale locale, TransportationMode mode) { + this(unit, translationMap, locale, mode.name()); + } + + public DistanceConfig(DistanceUtils.Unit unit, TranslationMap translationMap, Locale locale, String mode) { + this.unit = unit; + switch (Helper.toLowerCase(mode)) { + case "biking": + case "cycling": + case "cyclist": + case "mtb": + case "racingbike": + case "bike": + this.mode = "cycling"; + if (unit == DistanceUtils.Unit.METRIC) { + voiceInstructions = List.of( + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.metric, translationMap, locale, new int[]{150}, + new int[]{150})); + } else { + voiceInstructions = List.of( + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.imperial, translationMap, locale, new int[]{150}, + new int[]{500})); + } + break; + case "walking": + case "walk": + case "hiking": + case "hike": + case "foot": + case "pedestrian": + this.mode = "walking"; + if (unit == DistanceUtils.Unit.METRIC) { + voiceInstructions = List.of( + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.metric, translationMap, locale, new int[]{50}, + new int[]{50})); + } else { + voiceInstructions = List.of( + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.imperial, translationMap, locale, new int[]{50}, + new int[]{150})); + } + break; + default: + this.mode = "driving"; + if (unit == DistanceUtils.Unit.METRIC) { + voiceInstructions = Arrays.asList( + new InitialVoiceInstructionConfig(FOR_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 4250, 250, unit), + new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 2000, 2), + new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_SINGULAR.metric, translationMap, locale, 1000, 1), + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.metric, translationMap, locale, new int[]{400, 200}, new int[]{400, 200}) + ); + } else { + voiceInstructions = Arrays.asList( + new InitialVoiceInstructionConfig(FOR_HIGHER_DISTANCE_PLURAL.metric, translationMap, locale, 4250, 250, unit), + new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_PLURAL.imperial, translationMap, locale, 3220, 2), + new FixedDistanceVoiceInstructionConfig(IN_HIGHER_DISTANCE_SINGULAR.imperial, translationMap, locale, 1610, 1), + new ConditionalDistanceVoiceInstructionConfig(IN_LOWER_DISTANCE_PLURAL.imperial, translationMap, locale, new int[]{400, 200}, new int[]{1300, 600}) + ); + } } } @@ -59,5 +107,12 @@ public List getVoiceInstructionsFo return instructionsConfigs; } + /** + * Returns the Mapbox-compatible mode string for this transportation mode. + * @return "cycling" for bike profiles, "walking" for foot profiles, or "driving" for vehicle profiles + */ + public String getMode() { + return mode; + } -} \ No newline at end of file +} diff --git a/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java b/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java index c1a4c2d7818..6d1f817a54c 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java +++ b/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java @@ -21,6 +21,7 @@ import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; import com.graphhopper.GraphHopperConfig; +import com.graphhopper.routing.ev.MaxSpeed; import com.graphhopper.util.Helper; import com.graphhopper.util.Parameters; import com.graphhopper.util.StopWatch; @@ -29,24 +30,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.*; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import java.util.*; -import static com.graphhopper.util.Parameters.Details.INTERSECTION; +import static com.graphhopper.util.Parameters.Details.*; import static com.graphhopper.util.Parameters.Routing.*; /** * Provides an endpoint that is consumable with the Mapbox Navigation SDK. The Mapbox Navigation SDK consumes json * responses that follow the specification of the Mapbox API v5. *

    - * See: https://www.mapbox.com/api-documentation/#directions + * See: mapbox docs. *

    * The baseurl of this endpoint is: [YOUR-IP/HOST]/navigate * The version of this endpoint is: v5 @@ -54,7 +56,7 @@ * * @author Robin Boldt */ -@Path("navigate/directions/v5/gh") +@Path("navigate") public class NavigateResource { private static final Logger logger = LoggerFactory.getLogger(NavigateResource.class); @@ -78,7 +80,7 @@ public NavigateResource(GraphHopper graphHopper, TranslationMap translationMap, } @GET - @Path("/{profile}/{coordinatesArray : .+}") + @Path("/directions/v5/gh/{profile}/{coordinatesArray : .+}") @Produces({MediaType.APPLICATION_JSON}) public Response doGet( @Context HttpServletRequest httpReq, @@ -95,10 +97,9 @@ public Response doGet( @QueryParam("language") @DefaultValue("en") String localeStr, @PathParam("profile") String mapboxProfile) { - /* - Currently, the NavigateResponseConverter is pretty limited. - Therefore, we enforce these values to make sure the client does not receive an unexpected answer. - */ + StopWatch sw = new StopWatch().start(); + + // Make sure the client does not receive an unexpected answer as the NavigateResponseConverter is limited if (!geometries.equals("polyline6")) throw new IllegalArgumentException("Currently, we only support polyline6"); if (!enableInstructions) @@ -110,32 +111,20 @@ public Response doGet( if (!bannerInstructions) throw new IllegalArgumentException("You need to enable banner instructions right now"); - double minPathPrecision = 1; - if (overview.equals("full")) - minPathPrecision = 0; - - DistanceUtils.Unit unit; - if (voiceUnits.equals("metric")) { - unit = DistanceUtils.Unit.METRIC; - } else { - unit = DistanceUtils.Unit.IMPERIAL; - } - + double minPathPrecision = overview.equals("full") ? 0 : 1; String ghProfile = resolverMap.getOrDefault(mapboxProfile, mapboxProfile); List requestPoints = getPointsFromRequest(httpReq, mapboxProfile); List favoredHeadings = getBearing(bearings); - if (favoredHeadings.size() > 0 && favoredHeadings.size() != requestPoints.size()) { + if (!favoredHeadings.isEmpty() && favoredHeadings.size() != requestPoints.size()) { throw new IllegalArgumentException("Number of bearings and waypoints did not match"); } - StopWatch sw = new StopWatch().start(); - - GHResponse ghResponse = calcRoute(favoredHeadings, requestPoints, ghProfile, localeStr, enableInstructions, minPathPrecision); + GHResponse ghResponse = calcRouteForGET(favoredHeadings, requestPoints, ghProfile, localeStr, enableInstructions, minPathPrecision); // Only do this, when there are more than 2 points, otherwise we use alternative routes - if (!ghResponse.hasErrors() && favoredHeadings.size() > 0) { - GHResponse noHeadingResponse = calcRoute(Collections.EMPTY_LIST, requestPoints, ghProfile, localeStr, enableInstructions, minPathPrecision); + if (!ghResponse.hasErrors() && !favoredHeadings.isEmpty()) { + GHResponse noHeadingResponse = calcRouteForGET(Collections.emptyList(), requestPoints, ghProfile, localeStr, enableInstructions, minPathPrecision); if (ghResponse.getBest().getDistance() != noHeadingResponse.getBest().getDistance()) { ghResponse.getAll().add(noHeadingResponse.getBest()); } @@ -145,8 +134,6 @@ public Response doGet( String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent"); String logStr = httpReq.getQueryString() + " " + infoStr + " " + requestPoints + ", took:" + took + ", " + ghProfile; - Locale locale = Helper.getLocale(localeStr); - DistanceConfig config = new DistanceConfig(unit, translationMap, locale); if (ghResponse.hasErrors()) { logger.error(logStr + ", errors:" + ghResponse.getErrors()); @@ -155,6 +142,9 @@ public Response doGet( header("X-GH-Took", "" + Math.round(took * 1000)). build(); } else { + DistanceUtils.Unit unit = voiceUnits.equals("metric") ? DistanceUtils.Unit.METRIC : DistanceUtils.Unit.IMPERIAL; + Locale locale = Helper.getLocale(localeStr); + DistanceConfig config = new DistanceConfig(unit, translationMap, locale, graphHopper.getNavigationMode(ghProfile)); logger.info(logStr); return Response.ok(NavigateResponseConverter.convertFromGHResponse(ghResponse, translationMap, locale, config)). header("X-GH-Took", "" + Math.round(took * 1000)). @@ -162,28 +152,101 @@ public Response doGet( } } - private GHResponse calcRoute(List headings, List requestPoints, String profileStr, - String localeStr, boolean enableInstructions, double minPathPrecision) { + @POST + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response doPost(@NotNull GHRequest request, @Context HttpServletRequest httpReq) { + StopWatch sw = new StopWatch().start(); + + // do not use routing.snap_preventions_default here as they are not always good for navigation + + // request is a GraphHopper so reject mapbox parameters + if (request.getHints().has("geometries")) + throw new IllegalArgumentException("Do not set 'geometries'. Per default it is 'polyline6'."); + if (request.getHints().has("steps")) + throw new IllegalArgumentException("Do not set 'steps'. Per default it is true."); + if (request.getHints().has("roundabout_exits")) + throw new IllegalArgumentException("Do not set 'roundabout_exits'. Per default it is true."); + if (request.getHints().has("voice_instructions")) + throw new IllegalArgumentException("Do not set 'voice_instructions'. Per default it is true."); + if (request.getHints().has("banner_instructions")) + throw new IllegalArgumentException("Do not set 'banner_instructions'. Per default it is true."); + if (request.getHints().has("elevation")) + throw new IllegalArgumentException("Do not set 'elevation'. Per default it is false."); + if (request.getHints().has("overview")) + throw new IllegalArgumentException("Do not set 'overview'. Per default it is 'full'."); + if (request.getHints().has("language")) + throw new IllegalArgumentException("Instead of 'language' use 'locale'. Per default it is 'en'."); + if (request.getHints().has("points_encoded")) + throw new IllegalArgumentException("Do not set 'points_encoded'. Per default it is true."); + if (request.getHints().has("points_encoded_multiplier")) + throw new IllegalArgumentException("Do not set 'points_encoded_multiplier'. Per default it is 1e6."); + if (!request.getHints().getString("type", "").equals("mapbox")) + throw new IllegalArgumentException("Currently type=mapbox required."); + + if (request.getPathDetails().isEmpty()) { + if (graphHopper.getEncodingManager().hasEncodedValue(MaxSpeed.KEY)) + request.setPathDetails(List.of(INTERSECTION, MaxSpeed.KEY, TIME, AVERAGE_SPEED)); + else + request.setPathDetails(List.of(INTERSECTION)); + } + + GHResponse ghResponse = graphHopper.route(request); + + double took = sw.stop().getMillisDouble(); + String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent"); + String logStr = infoStr + " " + request.getPoints().size() + ", took: " + + String.format("%.1f", took) + " ms, algo: " + request.getAlgorithm() + ", profile: " + request.getProfile() + + ", points: " + request.getPoints() + + ", custom_model: " + request.getCustomModel(); + + if (ghResponse.hasErrors()) { + logger.error(logStr + ", errors:" + ghResponse.getErrors()); + // TODO Mapbox specifies 422 return type for input errors + return Response.status(422).entity(NavigateResponseConverter.convertFromGHResponseError(ghResponse)). + header("X-GH-Took", "" + Math.round(took * 1000)). + build(); + } else { + DistanceUtils.Unit unit; + if (request.getHints().getString("voice_units", "metric").equals("metric")) { + unit = DistanceUtils.Unit.METRIC; + } else { + unit = DistanceUtils.Unit.IMPERIAL; + } + + DistanceConfig config = new DistanceConfig(unit, translationMap, request.getLocale(), graphHopper.getNavigationMode(request.getProfile())); + logger.info(logStr); + return Response.ok(NavigateResponseConverter.convertFromGHResponse(ghResponse, translationMap, request.getLocale(), config)). + header("X-GH-Took", "" + Math.round(took * 1000)). + build(); + } + } + + private GHResponse calcRouteForGET(List headings, List requestPoints, String profileStr, + String localeStr, boolean enableInstructions, double minPathPrecision) { GHRequest request = new GHRequest(requestPoints); - if (headings.size() > 0) + if (!headings.isEmpty()) request.setHeadings(headings); request.setProfile(profileStr). setLocale(localeStr). // We force the intersection details here as we cannot easily add this to the URL - setPathDetails(Arrays.asList(INTERSECTION)). + setPathDetails(List.of(INTERSECTION)). putHint(CALC_POINTS, true). putHint(INSTRUCTIONS, enableInstructions). - putHint(WAY_POINT_MAX_DISTANCE, minPathPrecision). - putHint(Parameters.CH.DISABLE, true). - putHint(Parameters.Routing.PASS_THROUGH, false); + putHint(WAY_POINT_MAX_DISTANCE, minPathPrecision); + + if (requestPoints.size() > 2 || !headings.isEmpty()) { + request.putHint(Parameters.CH.DISABLE, true). + putHint(Parameters.Routing.PASS_THROUGH, true); + } return graphHopper.route(request); } /** * This method is parsing the request URL String. Unfortunately it seems that there is no better options right now. - * See: https://stackoverflow.com/q/51420380/1548788 + * See: ... *

    * The url looks like: ".../{profile}/1.522438,42.504606;1.527209,42.504776;1.526113,42.505144;1.527218,42.50529?.." */ @@ -194,8 +257,8 @@ private List getPointsFromRequest(HttpServletRequest httpServletRequest url = url.substring(urlStart.length()); String[] pointStrings = url.split(";"); List points = new ArrayList<>(pointStrings.length); - for (int i = 0; i < pointStrings.length; i++) { - points.add(GHPoint.fromStringLonLat(pointStrings[i])); + for (String pointString : pointStrings) { + points.add(GHPoint.fromStringLonLat(pointString)); } return points; @@ -203,13 +266,12 @@ private List getPointsFromRequest(HttpServletRequest httpServletRequest static List getBearing(String bearingString) { if (bearingString == null || bearingString.isEmpty()) - return Collections.EMPTY_LIST; + return Collections.emptyList(); String[] bearingArray = bearingString.split(";", -1); List bearings = new ArrayList<>(bearingArray.length); - for (int i = 0; i < bearingArray.length; i++) { - String singleBearing = bearingArray[i]; + for (String singleBearing : bearingArray) { if (singleBearing.isEmpty()) { bearings.add(Double.NaN); } else { diff --git a/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java b/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java index ada50511e59..6929ae6396c 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java +++ b/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java @@ -23,25 +23,42 @@ import com.graphhopper.GHResponse; import com.graphhopper.ResponsePath; import com.graphhopper.jackson.ResponsePathSerializer; +import com.graphhopper.routing.ev.MaxSpeed; import com.graphhopper.util.*; +import com.graphhopper.util.details.IntersectionValues; import com.graphhopper.util.details.PathDetail; +import com.graphhopper.util.shapes.GHPoint3D; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; import static com.graphhopper.util.Parameters.Details.INTERSECTION; +import static com.graphhopper.util.Parameters.Details.TIME; -public class NavigateResponseConverter { +enum ManeuverType { + ARRIVE, + DEPART, + TURN, + ROUNDABOUT +}; +public class NavigateResponseConverter { + private static final Logger LOGGER = LoggerFactory.getLogger(NavigateResponseConverter.class); private static final int VOICE_INSTRUCTION_MERGE_TRESHHOLD = 100; /** * Converts a GHResponse into a json that follows the Mapbox API specification */ - public static ObjectNode convertFromGHResponse(GHResponse ghResponse, TranslationMap translationMap, Locale locale, DistanceConfig distanceConfig) { + public static ObjectNode convertFromGHResponse(GHResponse ghResponse, TranslationMap translationMap, Locale locale, + DistanceConfig distanceConfig) { ObjectNode json = JsonNodeFactory.instance.objectNode(); if (ghResponse.hasErrors()) - throw new IllegalStateException("If the response has errors, you should use the method NavigateResponseConverter#convertFromGHResponseError"); + throw new IllegalStateException( + "If the response has errors, you should use the method NavigateResponseConverter#convertFromGHResponseError"); PointList waypoints = ghResponse.getBest().getWaypoints(); @@ -67,11 +84,11 @@ public static ObjectNode convertFromGHResponse(GHResponse ghResponse, Translatio json.put("code", "Ok"); // TODO: Maybe we need a different format... uuid: "cji4ja4f8004o6xrsta8w4p4h" json.put("uuid", UUID.randomUUID().toString().replaceAll("-", "")); - return json; } - private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, int routeNr, TranslationMap translationMap, Locale locale, DistanceConfig distanceConfig) { + private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, int routeNr, + TranslationMap translationMap, Locale locale, DistanceConfig distanceConfig) { InstructionList instructions = path.getInstructions(); pathJson.put("geometry", ResponsePathSerializer.encodePolyline(path.getPoints(), false, 1e6)); @@ -82,34 +99,71 @@ private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, long time = 0; double distance = 0; - boolean isFirstInstructionOfLeg = true; + boolean isDepartInstruction = true; int pointIndexFrom = 0; Map> pathDetails = path.getPathDetails(); List intersectionDetails = pathDetails.getOrDefault(INTERSECTION, Collections.emptyList()); + ObjectNode annotation = pathDetails.isEmpty() ? null : legJson.putObject("annotation"); + ArrayNode maxSpeedArray = pathDetails.containsKey(MaxSpeed.KEY) ? annotation.putArray("maxspeed") : null; + ArrayNode durationArray = pathDetails.containsKey(TIME) ? annotation.putArray("duration") : null; + // The "distance" is a special case as we can calculate it without previously calculated path + // details and should be available as soon as maxspeed is requested e.g. the maplibre SDK requires this to use maxspeed. + ArrayNode distanceArray = pathDetails.isEmpty() ? null : annotation.putArray("distance"); + for (int i = 0; i < instructions.size(); i++) { - ObjectNode instructionJson = steps.addObject(); + ObjectNode stepJson = steps.addObject(); Instruction instruction = instructions.get(i); - int pointIndexTo = pointIndexFrom; - if (instruction.getSign() != Instruction.REACHED_VIA && instruction.getSign() != Instruction.FINISH) { - pointIndexTo += instructions.get(i).getPoints().size(); + int pointIndexTo = pointIndexFrom + instruction.getLength(); + + ManeuverType maneuverType; + if (isDepartInstruction) { + maneuverType = ManeuverType.DEPART; + fixDepartIntersectionDetail(intersectionDetails, i); + // if DEPART is on a REACHED_VIA or FINISH node, add the summary + if (instruction.getSign() == Instruction.REACHED_VIA || instruction.getSign() == Instruction.FINISH) { + putLegInformation(legJson, path, routeNr, time, distance); + } + } else { + maneuverType = switch (instruction.getSign()) { + case Instruction.REACHED_VIA, Instruction.FINISH -> ManeuverType.ARRIVE; + case Instruction.USE_ROUNDABOUT -> ManeuverType.ROUNDABOUT; + default -> ManeuverType.TURN; + }; } - putInstruction(path.getPoints(), instructions, i, locale, translationMap, instructionJson, isFirstInstructionOfLeg, distanceConfig, intersectionDetails, pointIndexFrom, pointIndexTo); + + // important: OSRM annotations are per-segment (N-1 entries for N coordinates) and so + // we have to skip all instructions (like ARRIVE) with length == 0 and this is implicitly + // done because then pointIndexTo == pointIndexFrom. + if (maxSpeedArray != null) + putMaxSpeedAnnotation(maxSpeedArray, pathDetails.get(MaxSpeed.KEY), distanceConfig.unit, pointIndexFrom, pointIndexTo); + if (durationArray != null) + putDurationAnnotation(durationArray, pathDetails.get(TIME), path.getPoints(), pointIndexFrom, pointIndexTo); + if (!pathDetails.isEmpty()) + putDistanceAnnotation(distanceArray, path.getPoints(), pointIndexFrom, pointIndexTo); + putInstruction(path.getPoints(), instructions, i, locale, translationMap, stepJson, + maneuverType, distanceConfig, intersectionDetails, pointIndexFrom, pointIndexTo); pointIndexFrom = pointIndexTo; time += instruction.getTime(); distance += instruction.getDistance(); - isFirstInstructionOfLeg = false; - if (instruction.getSign() == Instruction.REACHED_VIA || instruction.getSign() == Instruction.FINISH) { + isDepartInstruction = false; + if (maneuverType == ManeuverType.ARRIVE) { putLegInformation(legJson, path, routeNr, time, distance); - isFirstInstructionOfLeg = true; - time = 0; - distance = 0; - if (instruction.getSign() == Instruction.REACHED_VIA) { // Create new leg and steps after a via points legJson = legsJson.addObject(); steps = legJson.putArray("steps"); + if (annotation != null) { + annotation = legJson.putObject("annotation"); + maxSpeedArray = pathDetails.containsKey(MaxSpeed.KEY) ? annotation.putArray("maxspeed") : null; + durationArray = pathDetails.containsKey(TIME) ? annotation.putArray("duration") : null; + // "distance" is always available when any path details are requested + distanceArray = annotation.putArray("distance"); + } + isDepartInstruction = true; + time = 0; + distance = 0; } } } @@ -121,8 +175,98 @@ private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, pathJson.put("voiceLocale", locale.toLanguageTag()); } + private static void putMaxSpeedAnnotation(ArrayNode maxSpeedArray, List maxSpeeds, + DistanceUtils.Unit metric, + final int fromIdx, final int toIdx) { + if (maxSpeeds.isEmpty()) return; + + String unitValue = metric == DistanceUtils.Unit.METRIC ? "km/h" : "mph"; + int nextPDIdx = 0; + for (int idx = fromIdx; idx < toIdx; ) { + for (; nextPDIdx < maxSpeeds.size(); nextPDIdx++) { + PathDetail pd = maxSpeeds.get(nextPDIdx); + if (idx >= pd.getFirst() && idx < pd.getLast()) break; + } + if (nextPDIdx >= maxSpeeds.size()) break; // should not happen + + PathDetail pd = maxSpeeds.get(nextPDIdx); + // max_speed boundaries might exceed instruction boundaries (unlike 'time' path detail, which is created for every edge) + int until = Math.min(toIdx, pd.getLast()); + if (pd.getValue() == null) { + for (; idx < until; idx++) { + maxSpeedArray.addObject().put("unknown", true); + } + } else if (((Number) pd.getValue()).doubleValue() == MaxSpeed.MAXSPEED_150) { + for (; idx < until; idx++) { + maxSpeedArray.addObject().put("none", true); + } + } else { + long value = metric == DistanceUtils.Unit.METRIC + ? Math.round(((Number) pd.getValue()).doubleValue()) + : Math.round(((Number) pd.getValue()).doubleValue() / DistanceCalcEarth.KM_MILE); + for (; idx < until; idx++) { + ObjectNode object = maxSpeedArray.addObject(); + object.put("speed", value); + object.put("unit", unitValue); + } + } + } + } + + private static void putDistanceAnnotation(ArrayNode distanceArray, PointList points, + final int fromIdx, final int toIdx) { + for (int idx = fromIdx; idx < toIdx; idx++) { + double dist = DistanceCalcEarth.DIST_EARTH.calcDist( + points.getLat(idx), points.getLon(idx), + points.getLat(idx + 1), points.getLon(idx + 1)); + distanceArray.add(Helper.round(dist, 1)); + } + } + + private static void putDurationAnnotation(ArrayNode durationArray, List timeDetails, + PointList points, final int fromIdx, final int toIdx) { + if (timeDetails.isEmpty()) return; + + int nextPDIdx = 0; + for (int idx = fromIdx; idx < toIdx; ) { + for (; nextPDIdx < timeDetails.size(); nextPDIdx++) { + PathDetail pd = timeDetails.get(nextPDIdx); + if (idx >= pd.getFirst() && idx < pd.getLast()) break; + } + if (nextPDIdx >= timeDetails.size()) break; // should not happen + + PathDetail pd = timeDetails.get(nextPDIdx); + if (pd.getValue() != null) { + double totalTimeSeconds = convertToSeconds(((Number) pd.getValue()).doubleValue()); + + // compute total distance for this edge from the geometry + double totalDist = 0; + for (int j = pd.getFirst(); j < pd.getLast(); j++) { + totalDist += DistanceCalcEarth.DIST_EARTH.calcDist( + points.getLat(j), points.getLon(j), + points.getLat(j + 1), points.getLon(j + 1)); + } + + if (toIdx < pd.getLast()) + throw new IllegalStateException("toIdx " + toIdx + " is smaller than pd.getLast() " + pd.getLast() + + ". 'time' path detail is only for one edge and so instruction boundaries should align."); + for (; idx < pd.getLast(); idx++) { + if (totalDist > 0) { + double segDist = DistanceCalcEarth.DIST_EARTH.calcDist( + points.getLat(idx), points.getLon(idx), + points.getLat(idx + 1), points.getLon(idx + 1)); + durationArray.add(Helper.round(totalTimeSeconds * segDist / totalDist, 1)); + } else { + durationArray.add(0); + } + } + } + } + } + private static void putLegInformation(ObjectNode legJson, ResponsePath path, int i, long time, double distance) { - // TODO: Improve path descriptions, so that every path has a description, not just alternative routes + // TODO: Improve path descriptions, so that every path has a description, not + // just alternative routes String summary; if (!path.getDescription().isEmpty()) summary = String.join(",", path.getDescription()); @@ -136,120 +280,255 @@ private static void putLegInformation(ObjectNode legJson, ResponsePath path, int legJson.put("distance", Helper.round(distance, 1)); } - private static ObjectNode putInstruction(PointList points, InstructionList instructions, int instructionIndex, Locale locale, - TranslationMap translationMap, ObjectNode instructionJson, boolean isFirstInstructionOfLeg, - DistanceConfig distanceConfig, List intersectionDetails, int pointIndexFrom, - int pointIndexTo) { - Instruction instruction = instructions.get(instructionIndex); - ArrayNode intersections = instructionJson.putArray("intersections"); + /** + * fix the first IntersectionDetail which is a Depart + *

    + * Departs should only have one "bearings" and one + * "out" entry + */ + private static void fixDepartIntersectionDetail(List intersectionDetails, int position) { + + if (intersectionDetails.size() < position + 2) { + // Can happen if start and stop are at the same spot and other edge cases + return; + } + + final Map departIntersectionMap = (Map) intersectionDetails.get(position).getValue(); + + int out = (int) departIntersectionMap.get("out"); + departIntersectionMap.put("out", 0); + + // bearings + List oldBearings = (List) departIntersectionMap.get("bearings"); + List newBearings = new ArrayList<>(); + newBearings.add(oldBearings.get(out)); + departIntersectionMap.put("bearings", newBearings); + + // entries + final List oldEntries = (List) departIntersectionMap.get("entries"); + List newEntries = new ArrayList<>(); + newEntries.add(oldEntries.get(out)); + departIntersectionMap.put("entries", newEntries); + } + + /** + * Merge the IntersectionDetails: + *

    + * The first job is to find the interesting part in the interSectionDetails based on + * pointIndexFrom and pointIndexTo. + *

    + * Next job is to eliminate intersections colocated in the same point + * since Mapbox chokes on geometries with intersections laying ontop of + * each other. + *

    + * These type of intersections is used for barrier nodes + *

    + * We look for intersections in the lists and merge these adjacent, colocated + * intersection into each other taking the edges from both intersections and + * removing the connecting zero length edge. + * Care has to be taken that the result is sorted by bearing + */ + private static List mergeIntersectionDetails(PointList points, List intersectionDetails, + int pointIndexFrom, int pointIndexTo) { + List list = new ArrayList<>(); + // job1: find out the interesting part of the intersectionDetails for (PathDetail intersectionDetail : intersectionDetails) { - if (intersectionDetail.getFirst() >= pointIndexTo) { + int first = intersectionDetail.getFirst(); + if (first >= pointIndexTo) { break; } - if (intersectionDetail.getFirst() >= pointIndexFrom) { - ObjectNode intersection = intersections.addObject(); - Map intersectionValue = (Map) intersectionDetail.getValue(); - // Location - ArrayNode locationArray = intersection.putArray("location"); - locationArray.add(Helper.round6(points.getLon(intersectionDetail.getFirst()))); - locationArray.add(Helper.round6(points.getLat(intersectionDetail.getFirst()))); - // Entry - List entries = (List) intersectionValue.getOrDefault("entries", Collections.emptyList()); - ArrayNode entryArray = intersection.putArray("entry"); - for (Boolean entry : entries) { - entryArray.add(entry); - } - // Bearings - List bearingsList = (List) intersectionValue.getOrDefault("bearings", Collections.emptyList()); - ArrayNode bearingsrray = intersection.putArray("bearings"); - for (Integer bearing : bearingsList) { - bearingsrray.add(bearing); - } - // in - if (intersectionValue.containsKey("in")) { - intersection.put("in", (int) intersectionValue.get("in")); - } - // out - if (intersectionValue.containsKey("out")) { - intersection.put("out", (int) intersectionValue.get("out")); - } + if (first >= pointIndexFrom) { + list.add(intersectionDetail); + } + } + // nothing to be done for job 2. Either no entry or only one + if (list.size() < 2) { + return list; + } + + // Now look for adjacent intersections colocated + GHPoint3D intersectionPoint = points.get(list.get(0).getFirst()); + List duplicates = new ArrayList<>(); + for (int i = 1; i < list.size(); i++) { + GHPoint3D currentIntersectionPoint = points.get(list.get(i).getFirst()); + if (intersectionPoint.equals(currentIntersectionPoint)) { + duplicates.add(i - 1); // store the first index of the duplicate + } + intersectionPoint = currentIntersectionPoint; + } + + // now iterate backwards over all duplicates, since we will remove entries from + // list + for (int dup = duplicates.size() - 1; dup >= 0; dup--) { + int i = duplicates.get(dup); + // member i and i+1 are on the same point + // out edge of (i) points to in edge of (i+1) + // ... -------> intersection[i].out --------> intersection[i+1].in -----------> + // + // Create a new PathDetail for merging both intersections into one + // ... -------> intersection[i] ------> + try { + final Map intersectionMap = (Map) list.get(i).getValue(); + final List intersectionValueList = IntersectionValues.createList(intersectionMap); + + final Map nextIntersectionMap = (Map) list.get(i + 1).getValue(); + final List nextIntersectionValueList = IntersectionValues + .createList(nextIntersectionMap); + + // merge both Lists while + final List mergedInterSectionValueList = Stream.concat( + // removing out from Intersection + intersectionValueList.stream().filter(x -> !x.out), + // removing in from nextIntersection + nextIntersectionValueList.stream().filter(x -> !x.in)). + // sort the merged list by bearing + sorted((x, y) -> Integer.compare(x.bearing, y.bearing)). + // create the result list + collect(Collectors.toList()); + + // remove the duplicate Intersection from the Path (we are at "i" currently) + list.remove(i + 1); + + Map mergedIntersection = IntersectionValues + .createIntersection(mergedInterSectionValueList); + PathDetail mergedPathDetail = new PathDetail(mergedIntersection); + mergedPathDetail.setFirst(list.get(i).getFirst()); + // and replace the intersection with the merged one + list.set(i, mergedPathDetail); + } catch (ClassCastException e) { + LOGGER.warn("Exception :" + e); + continue; } } - //Make pointList mutable + return list; + } + + private static void putInstruction(PointList points, InstructionList instructions, int instructionIndex, + Locale locale, + TranslationMap translationMap, ObjectNode stepJson, ManeuverType maneuverType, + DistanceConfig distanceConfig, List intersectionDetails, + int pointIndexFrom, int pointIndexTo) { + Instruction instruction = instructions.get(instructionIndex); + ArrayNode intersections = stepJson.putArray("intersections"); + + // make pointList writeable PointList pointList = instruction.getPoints().clone(false); - if (instructionIndex + 2 < instructions.size()) { - // Add the first point of the next instruction + if (maneuverType != ManeuverType.ARRIVE && instructionIndex + 1 < instructions.size()) { + // modify pointlist to include the first point of the next instruction + // for all instructions but the arrival# + // but not for instructions with an DEPART and ARRIVAL at the same last point PointList nextPoints = instructions.get(instructionIndex + 1).getPoints(); pointList.add(nextPoints.getLat(0), nextPoints.getLon(0), nextPoints.getEle(0)); - } else if (pointList.size() == 1) { - // Duplicate the last point in the arrive instruction, if the size is 1 + } else { + // we are at the arrival (or via point arrival instruction) + // Duplicate the last point in the arrival instruction, which has only one point pointList.add(pointList.getLat(0), pointList.getLon(0), pointList.getEle(0)); - } - if (intersections.size() == 0) { - // this is the fallback if we don't have any intersections. - // this can happen for via points or finish instructions or when no intersection details have been requested + // Add an arrival intersection with only one enty ObjectNode intersection = intersections.addObject(); - intersection.putArray("entry"); - intersection.putArray("bearings"); + ArrayNode entryArray = intersection.putArray("entry"); + entryArray.add(true); + + // copy the bearing from the previous instruction + ArrayNode bearingsArray = intersection.putArray("bearings"); + bearingsArray.add(0); + + // add the in tag + intersection.put("in", 0); putLocation(pointList.getLat(0), pointList.getLon(0), intersection); } - instructionJson.put("driving_side", "right"); + // preprocess intersectionDetails + List mergedIntersectionDetails = mergeIntersectionDetails(points, intersectionDetails, + pointIndexFrom, pointIndexTo); + + for (PathDetail intersectionDetail : mergedIntersectionDetails) { + ObjectNode intersection = intersections.addObject(); + Map intersectionValue = (Map) intersectionDetail.getValue(); + // Location + ArrayNode locationArray = intersection.putArray("location"); + locationArray.add(Helper.round6(points.getLon(intersectionDetail.getFirst()))); + locationArray.add(Helper.round6(points.getLat(intersectionDetail.getFirst()))); + // Entry + List entries = (List) intersectionValue.getOrDefault("entries", Collections.emptyList()); + ArrayNode entryArray = intersection.putArray("entry"); + for (Boolean entry : entries) { + entryArray.add(entry); + } + // Bearings + List bearingsList = (List) intersectionValue.getOrDefault("bearings", + Collections.emptyList()); + ArrayNode bearingsrray = intersection.putArray("bearings"); + for (Integer bearing : bearingsList) { + bearingsrray.add(bearing); + } + // in + if (intersectionValue.containsKey("in")) { + intersection.put("in", (int) intersectionValue.get("in")); + } + // out + if (intersectionValue.containsKey("out")) { + intersection.put("out", (int) intersectionValue.get("out")); + } + } + + stepJson.put("driving_side", "right"); // Does not include elevation - instructionJson.put("geometry", ResponsePathSerializer.encodePolyline(pointList, false, 1e6)); + stepJson.put("geometry", ResponsePathSerializer.encodePolyline(pointList, false, 1e6)); - // TODO: how about other modes? - instructionJson.put("mode", "driving"); + stepJson.put("mode", instruction.getSign() == Instruction.FERRY ? "ferry" : distanceConfig.getMode()); - putManeuver(instruction, instructionJson, locale, translationMap, isFirstInstructionOfLeg); + putManeuver(instruction, stepJson, locale, translationMap, maneuverType); // TODO distance = weight, is weight even important? double distance = Helper.round(instruction.getDistance(), 1); - instructionJson.put("weight", distance); - instructionJson.put("duration", convertToSeconds(instruction.getTime())); - instructionJson.put("name", instruction.getName()); - instructionJson.put("distance", distance); + stepJson.put("weight", distance); + stepJson.put("duration", convertToSeconds(instruction.getTime())); + stepJson.put("name", instruction.getName()); + stepJson.put("distance", distance); - ArrayNode voiceInstructions = instructionJson.putArray("voiceInstructions"); - ArrayNode bannerInstructions = instructionJson.putArray("bannerInstructions"); + ArrayNode voiceInstructions = stepJson.putArray("voiceInstructions"); + ArrayNode bannerInstructions = stepJson.putArray("bannerInstructions"); // Voice and banner instructions are empty for the last element if (instructionIndex + 1 < instructions.size()) { - putVoiceInstructions(instructions, distance, instructionIndex, locale, translationMap, voiceInstructions, distanceConfig); + putVoiceInstructions(instructions, distance, instructionIndex, locale, translationMap, voiceInstructions, + distanceConfig); putBannerInstructions(instructions, distance, instructionIndex, locale, translationMap, bannerInstructions); } - - return instructionJson; } - private static void putVoiceInstructions(InstructionList instructions, double distance, int index, Locale locale, TranslationMap translationMap, + private static void putVoiceInstructions(InstructionList instructions, double distance, int index, + Locale locale, TranslationMap translationMap, ArrayNode voiceInstructions, DistanceConfig distanceConfig) { /* - A VoiceInstruction Object looks like this - { - distanceAlongGeometry: 40.9, - announcement: "Exit the traffic circle", - ssmlAnnouncement: "Exit the traffic circle", - } - */ + * A VoiceInstruction Object looks like this + * { + * distanceAlongGeometry: 40.9, + * announcement: "Exit the traffic circle", + * ssmlAnnouncement: "Exit the traffic circle", + * } + */ Instruction nextInstruction = instructions.get(index + 1); String turnDescription = nextInstruction.getTurnDescription(translationMap.getWithFallBack(locale)); String thenVoiceInstruction = getThenVoiceInstructionpart(instructions, index, locale, translationMap); - List voiceValues = distanceConfig.getVoiceInstructionsForDistance(distance, turnDescription, thenVoiceInstruction); + List voiceValues = distanceConfig + .getVoiceInstructionsForDistance(distance, turnDescription, thenVoiceInstruction); for (VoiceInstructionConfig.VoiceInstructionValue voiceValue : voiceValues) { putSingleVoiceInstruction(voiceValue.spokenDistance, voiceValue.turnDescription, voiceInstructions); } // Speak 80m instructions 80 before the turn - // Note: distanceAlongGeometry: "how far from the upcoming maneuver the voice instruction should begin" + // Note: distanceAlongGeometry: "how far from the upcoming maneuver the voice + // instruction should begin" double distanceAlongGeometry = Helper.round(Math.min(distance, 80), 1); // Special case for the arrive instruction @@ -259,28 +538,36 @@ private static void putVoiceInstructions(InstructionList instructions, double di putSingleVoiceInstruction(distanceAlongGeometry, turnDescription + thenVoiceInstruction, voiceInstructions); } - private static void putSingleVoiceInstruction(double distanceAlongGeometry, String turnDescription, ArrayNode voiceInstructions) { + private static void putSingleVoiceInstruction(double distanceAlongGeometry, String turnDescription, + ArrayNode voiceInstructions) { ObjectNode voiceInstruction = voiceInstructions.addObject(); voiceInstruction.put("distanceAlongGeometry", distanceAlongGeometry); - //TODO: ideally, we would even generate instructions including the instructions after the next like turn left **then** turn right + // TODO: ideally, we would even generate instructions including the instructions + // after the next like turn left **then** turn right voiceInstruction.put("announcement", turnDescription); - voiceInstruction.put("ssmlAnnouncement", "" + turnDescription + ""); + voiceInstruction.put("ssmlAnnouncement", "" + + turnDescription + ""); } /** - * For close turns, it is important to announce the next turn in the earlier instruction. - * e.g.: instruction i+1= turn right, instruction i+2=turn left, with instruction i+1 distance < VOICE_INSTRUCTION_MERGE_TRESHHOLD + * For close turns, it is important to announce the next turn in the earlier + * instruction. + * e.g.: instruction i+1= turn right, instruction i+2=turn left, with + * instruction i+1 distance < VOICE_INSTRUCTION_MERGE_TRESHHOLD * The voice instruction should be like "turn right, then turn left" *

    - * For instruction i+1 distance > VOICE_INSTRUCTION_MERGE_TRESHHOLD an empty String will be returned + * For instruction i+1 distance > VOICE_INSTRUCTION_MERGE_TRESHHOLD an empty + * String will be returned */ - private static String getThenVoiceInstructionpart(InstructionList instructions, int index, Locale locale, TranslationMap translationMap) { + private static String getThenVoiceInstructionpart(InstructionList instructions, int index, Locale locale, + TranslationMap translationMap) { if (instructions.size() > index + 2) { Instruction firstInstruction = instructions.get(index + 1); if (firstInstruction.getDistance() < VOICE_INSTRUCTION_MERGE_TRESHHOLD) { Instruction secondInstruction = instructions.get(index + 2); if (secondInstruction.getSign() != Instruction.REACHED_VIA) - return ", " + translationMap.getWithFallBack(locale).tr("navigate.then") + " " + secondInstruction.getTurnDescription(translationMap.getWithFallBack(locale)); + return ", " + translationMap.getWithFallBack(locale).tr("navigate.then") + " " + + secondInstruction.getTurnDescription(translationMap.getWithFallBack(locale)); } } @@ -288,31 +575,34 @@ private static String getThenVoiceInstructionpart(InstructionList instructions, } /** - * Banner instructions are the turn instructions that are shown to the user in the top bar. + * Banner instructions are the turn instructions that are shown to the user in + * the top bar. *

    - * Between two instructions we can show multiple banner instructions, you can control when they pop up using distanceAlongGeometry. + * Between two instructions we can show multiple banner instructions, you can + * control when they pop up using distanceAlongGeometry. */ - private static void putBannerInstructions(InstructionList instructions, double distance, int index, Locale locale, TranslationMap translationMap, ArrayNode bannerInstructions) { + private static void putBannerInstructions(InstructionList instructions, double distance, int index, Locale locale, + TranslationMap translationMap, ArrayNode bannerInstructions) { /* - A BannerInstruction looks like this - distanceAlongGeometry: 107, - primary: { - text: "Lichtensteinstraße", - components: [ - { - text: "Lichtensteinstraße", - type: "text", - } - ], - type: "turn", - modifier: "right", - }, - secondary: null, + * A BannerInstruction looks like this + * distanceAlongGeometry: 107, + * primary: { + * text: "Lichtensteinstraße", + * components: [ + * { + * text: "Lichtensteinstraße", + * type: "text", + * } + * ], + * type: "turn", + * modifier: "right", + * }, + * secondary: null, */ ObjectNode bannerInstruction = bannerInstructions.addObject(); - //Show from the beginning + // Show from the beginning bannerInstruction.put("distanceAlongGeometry", distance); ObjectNode primary = bannerInstruction.putObject("primary"); @@ -327,14 +617,16 @@ private static void putBannerInstructions(InstructionList instructions, double d } } - private static void putSingleBannerInstruction(Instruction instruction, Locale locale, TranslationMap translationMap, ObjectNode singleBannerInstruction) { + private static void putSingleBannerInstruction(Instruction instruction, Locale locale, + TranslationMap translationMap, ObjectNode singleBannerInstruction) { String bannerInstructionName = instruction.getName(); if (bannerInstructionName.isEmpty()) { // Fix for final instruction and for instructions without name bannerInstructionName = instruction.getTurnDescription(translationMap.getWithFallBack(locale)); // Uppercase first letter - // TODO: should we do this for all cases? Then we might change the spelling of street names though + // TODO: should we do this for all cases? Then we might change the spelling of + // street names though bannerInstructionName = Helper.firstBig(bannerInstructionName); } @@ -345,7 +637,7 @@ private static void putSingleBannerInstruction(Instruction instruction, Locale l component.put("text", bannerInstructionName); component.put("type", "text"); - singleBannerInstruction.put("type", getTurnType(instruction, false)); + singleBannerInstruction.put("type", getTurnType(instruction)); String modifier = getModifier(instruction); if (modifier != null) singleBannerInstruction.put("modifier", modifier); @@ -357,13 +649,14 @@ private static void putSingleBannerInstruction(Instruction instruction, Locale l singleBannerInstruction.putNull("degrees"); } else { double degree = (Math.abs(turnAngle) * 180) / Math.PI; - singleBannerInstruction.put("degrees", degree); + singleBannerInstruction.put("degrees", Math.round(degree)); } } } } - private static void putManeuver(Instruction instruction, ObjectNode instructionJson, Locale locale, TranslationMap translationMap, boolean isFirstInstructionOfLeg) { + private static void putManeuver(Instruction instruction, ObjectNode instructionJson, Locale locale, + TranslationMap translationMap, ManeuverType maneuverType) { ObjectNode maneuver = instructionJson.putObject("maneuver"); maneuver.put("bearing_after", 0); maneuver.put("bearing_before", 0); @@ -371,48 +664,54 @@ private static void putManeuver(Instruction instruction, ObjectNode instructionJ PointList points = instruction.getPoints(); putLocation(points.getLat(0), points.getLon(0), maneuver); + // see https://docs.mapbox.com/api/navigation/directions/#maneuver-types + switch (maneuverType) { + case ARRIVE: + maneuver.put("type", "arrive"); + break; + case DEPART: + maneuver.put("type", "depart"); + break; + case ROUNDABOUT: + maneuver.put("type", "roundabout"); + maneuver.put("exit", ((RoundaboutInstruction) instruction).getExitNumber()); + break; + default: // i.e. ManeuverType.TURN: + maneuver.put("type", "turn"); + } String modifier = getModifier(instruction); if (modifier != null) maneuver.put("modifier", modifier); - - maneuver.put("type", getTurnType(instruction, isFirstInstructionOfLeg)); - // exit number - if (instruction instanceof RoundaboutInstruction) - maneuver.put("exit", ((RoundaboutInstruction) instruction).getExitNumber()); - maneuver.put("instruction", instruction.getTurnDescription(translationMap.getWithFallBack(locale))); } /** - * Relevant maneuver types are: - * depart (firs instruction) + * Relevant turn types for banners are: * turn (regular turns) * roundabout (enter roundabout, maneuver contains also the exit number) * arrive (last instruction and waypoints) *

    - * You can find all maneuver types at: https://www.mapbox.com/api-documentation/#maneuver-types + * You can find all turn types at: + * https://docs.mapbox.com/api/navigation/directions/#banner-instruction-object */ - private static String getTurnType(Instruction instruction, boolean isFirstInstructionOfLeg) { - if (isFirstInstructionOfLeg) { - return "depart"; - } else { - switch (instruction.getSign()) { - case Instruction.FINISH: - case Instruction.REACHED_VIA: - return "arrive"; - case Instruction.USE_ROUNDABOUT: - return "roundabout"; - default: - return "turn"; - } + private static String getTurnType(Instruction instruction) { + switch (instruction.getSign()) { + case Instruction.FINISH: + case Instruction.REACHED_VIA: + return "arrive"; + case Instruction.USE_ROUNDABOUT: + return "roundabout"; + default: + return "turn"; } } /** * No modifier values for arrive and depart *

    - * Find modifier values here: https://www.mapbox.com/api-documentation/#stepmaneuver-object + * Find modifier values here: + * https://www.mapbox.com/api-documentation/#stepmaneuver-object */ private static String getModifier(Instruction instruction) { switch (instruction.getSign()) { @@ -437,7 +736,8 @@ private static String getModifier(Instruction instruction) { case Instruction.TURN_SHARP_RIGHT: return "sharp right"; case Instruction.USE_ROUNDABOUT: - // TODO: This might be an issue in left-handed traffic, because there it schould be left + // TODO: This might be an issue in left-handed traffic, because there it schould + // be left return "right"; default: return null; @@ -470,3 +770,4 @@ public static ObjectNode convertFromGHResponseError(GHResponse ghResponse) { return json; } } + diff --git a/navigation/src/main/java/com/graphhopper/navigation/VoiceInstructionConfig.java b/navigation/src/main/java/com/graphhopper/navigation/VoiceInstructionConfig.java index 2275755a75f..64fd9a86abf 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/VoiceInstructionConfig.java +++ b/navigation/src/main/java/com/graphhopper/navigation/VoiceInstructionConfig.java @@ -19,7 +19,7 @@ public VoiceInstructionConfig(String translationKey, TranslationMap translationM this.locale = locale; } - class VoiceInstructionValue { + public static class VoiceInstructionValue { final int spokenDistance; final String turnDescription; diff --git a/navigation/src/test/java/com/graphhopper/navigation/DistanceConfigTest.java b/navigation/src/test/java/com/graphhopper/navigation/DistanceConfigTest.java new file mode 100644 index 00000000000..421057b1a6a --- /dev/null +++ b/navigation/src/test/java/com/graphhopper/navigation/DistanceConfigTest.java @@ -0,0 +1,55 @@ +package com.graphhopper.navigation; + +import com.graphhopper.GraphHopper; +import com.graphhopper.config.Profile; +import com.graphhopper.routing.util.TransportationMode; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DistanceConfigTest { + + @Test + public void distanceConfigTest() { + // from TransportationMode + DistanceConfig car = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.CAR); + assertEquals(4, car.voiceInstructions.size()); + DistanceConfig foot = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.FOOT); + assertEquals(1, foot.voiceInstructions.size()); + DistanceConfig bike = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.BIKE); + assertEquals(1, bike.voiceInstructions.size()); + DistanceConfig bus = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.BUS); + assertEquals(4, bus.voiceInstructions.size()); + + // from Profile + GraphHopper hopper = new GraphHopper().setProfiles( + new Profile("my_truck"), + new Profile("foot"), + new Profile("ebike").putHint("navigation_mode", "bike")); + assertEquals(TransportationMode.CAR, hopper.getNavigationMode("unknown")); + assertEquals(TransportationMode.CAR, hopper.getNavigationMode("my_truck")); + assertEquals(TransportationMode.FOOT, hopper.getNavigationMode("foot")); + assertEquals(TransportationMode.BIKE, hopper.getNavigationMode("ebike")); + + // from String + DistanceConfig driving = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, "driving"); + assertEquals(4, driving.voiceInstructions.size()); + DistanceConfig anything = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, "anything"); + assertEquals(4, anything.voiceInstructions.size()); + DistanceConfig none = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, ""); + assertEquals(4, none.voiceInstructions.size()); + DistanceConfig biking = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, "biking"); + assertEquals(1, biking.voiceInstructions.size()); + } + + @Test + public void testModeMapping() { + // Test TransportationMode enum values + DistanceConfig car = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.CAR); + assertEquals("driving", car.getMode()); + + DistanceConfig foot = new DistanceConfig(DistanceUtils.Unit.METRIC, null, null, TransportationMode.FOOT); + assertEquals("walking", foot.getMode()); + } + +} diff --git a/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java b/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java index c5dea1f271c..f7d7cc39813 100644 --- a/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java +++ b/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java @@ -5,9 +5,12 @@ import com.graphhopper.GHRequest; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; -import com.graphhopper.config.Profile; +import com.graphhopper.jackson.ResponsePathSerializer; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.routing.util.TransportationMode; import com.graphhopper.util.Helper; import com.graphhopper.util.Parameters; +import com.graphhopper.util.PointList; import com.graphhopper.util.TranslationMap; import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.AfterAll; @@ -16,6 +19,7 @@ import org.junit.jupiter.api.Test; import java.io.File; +import java.util.Arrays; import java.util.Collections; import java.util.Locale; @@ -26,23 +30,19 @@ public class NavigateResponseConverterTest { private static final String graphFolder = "target/graphhopper-test-car"; private static final String osmFile = "../core/files/andorra.osm.gz"; private static GraphHopper hopper; - private static final String vehicle = "car"; private static final String profile = "my_car"; - private final TranslationMap trMap = hopper.getTranslationMap(); - private final DistanceConfig distanceConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH); + private final DistanceConfig distanceConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH, + TransportationMode.CAR); @BeforeAll public static void beforeClass() { // make sure we are using fresh files with correct vehicle Helper.removeDir(new File(graphFolder)); - hopper = new GraphHopper(). - setOSMFile(osmFile). - setStoreOnFlush(true). - setGraphHopperLocation(graphFolder). - setProfiles(new Profile(profile).setVehicle(vehicle).setTurnCosts(false)). - importOrLoad(); + hopper = new GraphHopper().setOSMFile(osmFile).setStoreOnFlush(true).setGraphHopperLocation(graphFolder) + .setEncodedValuesString("car_access, car_average_speed, max_speed") + .setProfiles(TestProfiles.accessAndSpeed(profile, "car")).importOrLoad(); } @AfterAll @@ -53,8 +53,8 @@ public static void afterClass() { @Test public void basicTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile) + .setPathDetails(Collections.singletonList("intersection"))); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); @@ -76,7 +76,8 @@ public void basicTest() { JsonNode step = steps.get(0); JsonNode maneuver = step.get("maneuver"); // Intersection coordinates should be equal to maneuver coordinates - assertEquals(maneuver.get("location").get(0).asDouble(), step.get("intersections").get(0).get("location").get(0).asDouble(), .00001); + assertEquals(maneuver.get("location").get(0).asDouble(), + step.get("intersections").get(0).get("location").get(0).asDouble(), .00001); assertEquals("depart", maneuver.get("type").asText()); assertEquals("straight", maneuver.get("modifier").asText()); @@ -85,6 +86,9 @@ public void basicTest() { double instructionDistance = step.get("distance").asDouble(); assertTrue(instructionDistance < routeDistance); + // Check that the mode is correctly set to "driving" for car profile + assertEquals("driving", step.get("mode").asText()); + JsonNode voiceInstructions = step.get("voiceInstructions"); assertEquals(1, voiceInstructions.size()); JsonNode voiceInstruction = voiceInstructions.get(0); @@ -120,11 +124,42 @@ public void basicTest() { } + @Test + public void arriveGeometryTest() { + + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Step 17 is the last before arrival + JsonNode step = steps.get(17); + + PointList expectedArrivePointList = rsp.getBest().getInstructions().get(17).getPoints().clone(false); + PointList ghArrive = rsp.getBest().getInstructions().get(18).getPoints(); + // We expect that the Mapbox compatible response builds the geometry to the + // arrival coordinate + expectedArrivePointList.add(ghArrive); + String encodedExpected = ResponsePathSerializer.encodePolyline(expectedArrivePointList, false, 1e6); + + assertEquals(encodedExpected, step.get("geometry").asText()); + } + + @Test + public void departureArrivalTest() { + + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + // don't crash and we are happy + } + @Test public void voiceInstructionsTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); @@ -137,7 +172,8 @@ public void voiceInstructionsTest() { assertEquals(2, voiceInstructions.size()); JsonNode voiceInstruction = voiceInstructions.get(0); assertEquals(200, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); - assertEquals("In 200 meters At roundabout, take exit 2 onto CS-340, then At roundabout, take exit 2 onto CG-3", voiceInstruction.get("announcement").asText()); + assertEquals("In 200 meters at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); // Step 14 is over 3km long step = steps.get(14); @@ -155,27 +191,25 @@ public void voiceInstructionsTest() { @Test public void voiceInstructionsImperialTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, - new DistanceConfig(DistanceUtils.Unit.IMPERIAL, trMap, Locale.ENGLISH)); + new DistanceConfig(DistanceUtils.Unit.IMPERIAL, trMap, Locale.ENGLISH, TransportationMode.CAR)); JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); // Step 4 is about 240m long JsonNode step = steps.get(4); - JsonNode maneuver = step.get("maneuver"); JsonNode voiceInstructions = step.get("voiceInstructions"); assertEquals(2, voiceInstructions.size()); JsonNode voiceInstruction = voiceInstructions.get(0); assertEquals(200, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); - assertEquals("In 600 feet At roundabout, take exit 2 onto CS-340, then At roundabout, take exit 2 onto CG-3", voiceInstruction.get("announcement").asText()); + assertEquals("In 600 feet at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); // Step 14 is over 3km long step = steps.get(14); - maneuver = step.get("maneuver"); voiceInstructions = step.get("voiceInstructions"); assertEquals(4, voiceInstructions.size()); @@ -187,12 +221,141 @@ public void voiceInstructionsImperialTest() { assertEquals("keep right", voiceInstruction.get("announcement").asText()); } + @Test + public void voiceInstructionsWalkingMetricTest() { + + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, + new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH, TransportationMode.FOOT)); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Step 4 is about 240m long + JsonNode step = steps.get(4); + + JsonNode voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + JsonNode voiceInstruction = voiceInstructions.get(0); + assertEquals(50, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 50 meters at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); + + // Step 14 is over 3km long + step = steps.get(14); + + voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + voiceInstruction = voiceInstructions.get(0); + assertEquals(50, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 50 meters keep right", voiceInstruction.get("announcement").asText()); + + voiceInstruction = voiceInstructions.get(1); + assertEquals("keep right", voiceInstruction.get("announcement").asText()); + } + + @Test + public void voiceInstructionsWalkingImperialTest() { + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, + new DistanceConfig(DistanceUtils.Unit.IMPERIAL, trMap, Locale.ENGLISH, TransportationMode.FOOT)); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Step 4 is about 240m long + JsonNode step = steps.get(4); + + JsonNode voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + JsonNode voiceInstruction = voiceInstructions.get(0); + assertEquals(50, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 150 feet at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); + + // Step 14 is over 3km long + step = steps.get(14); + + voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + voiceInstruction = voiceInstructions.get(0); + assertEquals(50, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 150 feet keep right", voiceInstruction.get("announcement").asText()); + + voiceInstruction = voiceInstructions.get(1); + assertEquals("keep right", voiceInstruction.get("announcement").asText()); + } + + @Test + public void voiceInstructionsCyclingMetricTest() { + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, + new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH, TransportationMode.BIKE)); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Step 4 is about 240m long + JsonNode step = steps.get(4); + + JsonNode voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + JsonNode voiceInstruction = voiceInstructions.get(0); + assertEquals(150, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 150 meters at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); + + // Step 14 is over 3km long + step = steps.get(14); + + voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + voiceInstruction = voiceInstructions.get(0); + assertEquals(150, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 150 meters keep right", voiceInstruction.get("announcement").asText()); + + voiceInstruction = voiceInstructions.get(1); + assertEquals("keep right", voiceInstruction.get("announcement").asText()); + } + + @Test + public void voiceInstructionsCyclingImperialTest() { + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, + new DistanceConfig(DistanceUtils.Unit.IMPERIAL, trMap, Locale.ENGLISH, TransportationMode.BIKE)); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Step 4 is about 240m long + JsonNode step = steps.get(4); + + JsonNode voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + JsonNode voiceInstruction = voiceInstructions.get(0); + assertEquals(150, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 500 feet at roundabout, take exit 2 onto CS-340, then at roundabout, take exit 2 onto CG-3", + voiceInstruction.get("announcement").asText()); + + // Step 14 is over 3km long + step = steps.get(14); + + voiceInstructions = step.get("voiceInstructions"); + assertEquals(2, voiceInstructions.size()); + voiceInstruction = voiceInstructions.get(0); + assertEquals(150, voiceInstruction.get("distanceAlongGeometry").asDouble(), 1); + assertEquals("In 500 feet keep right", voiceInstruction.get("announcement").asText()); + + voiceInstruction = voiceInstructions.get(1); + assertEquals("keep right", voiceInstruction.get("announcement").asText()); + } + @Test @Disabled public void alternativeRoutesTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile).setAlgorithm(Parameters.Algorithms.ALT_ROUTE)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile) + .setAlgorithm(Parameters.Algorithms.ALT_ROUTE)); assertEquals(2, rsp.getAll().size()); @@ -208,8 +371,7 @@ public void alternativeRoutesTest() { @Test public void voiceInstructionTranslationTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); @@ -217,10 +379,11 @@ public void voiceInstructionTranslationTest() { JsonNode voiceInstruction = steps.get(14).get("voiceInstructions").get(0); assertEquals("In 2 kilometers keep right", voiceInstruction.get("announcement").asText()); - rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile).setLocale(Locale.GERMAN)); + rsp = hopper.route( + new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile).setLocale(Locale.GERMAN)); - DistanceConfig distanceConfigGerman = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.GERMAN); + DistanceConfig distanceConfigGerman = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.GERMAN, + TransportationMode.CAR); json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.GERMAN, distanceConfigGerman); @@ -232,8 +395,7 @@ public void voiceInstructionTranslationTest() { @Test public void roundaboutDegreesTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); @@ -252,8 +414,8 @@ public void roundaboutDegreesTest() { @Test public void intersectionTest() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128). - setProfile(profile).setPathDetails(Collections.singletonList("intersection"))); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile) + .setPathDetails(Collections.singletonList("intersection"))); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); @@ -264,8 +426,8 @@ public void intersectionTest() { JsonNode intersection = step.get("intersections").get(0); assertFalse(intersection.has("in")); - assertEquals(1, intersection.get("out").asInt()); - + assertEquals(0, intersection.get("out").asInt()); + assertEquals(1, intersection.get("bearings").size()); JsonNode location = intersection.get("location"); // The first intersection to be equal to the first snapped waypoint assertEquals(rsp.getBest().getWaypoints().get(0).lon, location.get(0).asDouble(), .000001); @@ -278,23 +440,118 @@ public void intersectionTest() { location = intersection.get("location"); assertEquals(1.534679, location.get(0).asDouble(), .000001); assertEquals(42.556444, location.get(1).asDouble(), .000001); + + int nrSteps = steps.size(); + JsonNode lastStep = steps.get(nrSteps - 1); + intersection = lastStep.get("intersections").get(0); + assertFalse(intersection.has("out")); + assertEquals(0, intersection.get("in").asInt()); + assertEquals(1, intersection.get("bearings").size()); } @Test - public void testMultipleWaypoints() { + public void barrierTest() { + // There is a barrier https://www.openstreetmap.org/node/2206610569 on the route + GHResponse rsp = hopper.route(new GHRequest(42.601991, 1.687227, 42.601616, 1.687888).setProfile(profile) + .setPathDetails(Collections.singletonList("intersection"))); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + JsonNode step = steps.get(1); + + JsonNode intersection = step.get("intersections").get(1); + + // checking order of entries + assertEquals(0, intersection.get("out").asInt()); + JsonNode location = intersection.get("location"); + // The location of the barrier + assertEquals(1.6878903, location.get(0).asDouble(), .000001); + assertEquals(42.601764, location.get(1).asDouble(), .000001); + + int inPosition = intersection.get("in").asInt(); + int outPosition = intersection.get("out").asInt(); + JsonNode entry = intersection.get("entry"); + assertFalse(entry.get(inPosition).asBoolean()); + assertTrue(entry.get(outPosition).asBoolean()); + + JsonNode bearings = intersection.get("bearings"); + double inBearing = bearings.get(inPosition).asDouble(); + double outBearing = bearings.get(outPosition).asDouble(); + + // and these should be the bearings + assertEquals(353, inBearing); + assertEquals(171, outBearing); + + // and no additional intersection + assertEquals(2, step.get("intersections").size()); + } + + @Test + public void startAtBarrierTest() { + // Start the route exactly at the barrier + // https://www.openstreetmap.org/node/2206610569 + // The barrier should be deduplicated and have only one "out" link + GHResponse rsp = hopper.route(new GHRequest(42.6017641, 1.6878903, 42.601616, 1.687888).setProfile(profile) + .setPathDetails(Collections.singletonList("intersection"))); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + // expecting one departure and arrival node + assertEquals(2, steps.size()); + JsonNode step = steps.get(0); + JsonNode intersections = step.get("intersections"); + assertEquals(1, intersections.size()); + JsonNode intersection = intersections.get(0); + + // Departure should have only one out node, even for a barrier! + assertEquals(0, intersection.get("out").asInt()); + assertNull(intersection.get("in")); + JsonNode location = intersection.get("location"); + // The location of the barrier + assertEquals(1.687890, location.get(0).asDouble(), .000001); + assertEquals(42.601764, location.get(1).asDouble(), .000001); + + JsonNode bearings = intersection.get("bearings"); + + double outBearing = bearings.get(0).asDouble(); + + // and these should be the bearing + assertEquals(171, outBearing); + + // Second step has an arrival intersection, with one in no out + // The location of the arrival intersection should be different from barrier + JsonNode step2 = steps.get(1); + JsonNode intersections2 = step2.get("intersections"); + assertEquals(1, intersections2.size()); + JsonNode intersection2 = intersections2.get(0); + + JsonNode location2 = intersection2.get("location"); + + assertNotEquals(location.get(0).asDouble(), location2.get(0).asDouble(), .0000001); + assertNotEquals(location.get(1).asDouble(), location2.get(1).asDouble(), .0000001); + // checking order of entries + assertEquals(0, intersection2.get("in").asInt()); + assertNull(intersection2.get("out")); + } + + @Test + public void testMultipleWaypoints() { GHRequest request = new GHRequest(); request.addPoint(new GHPoint(42.504606, 1.522438)); request.addPoint(new GHPoint(42.504776, 1.527209)); request.addPoint(new GHPoint(42.505144, 1.526113)); request.addPoint(new GHPoint(42.50529, 1.527218)); - request.setProfile(profile); + request.setProfile(profile).setPathDetails(Collections.singletonList("intersection")); GHResponse rsp = hopper.route(request); ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); - //Check that all waypoints are there and in the right order + // Check that all waypoints are there and in the right order JsonNode waypointsJson = json.get("waypoints"); assertEquals(4, waypointsJson.size()); @@ -310,7 +567,7 @@ public void testMultipleWaypoints() { waypointLoc = waypointsJson.get(3).get("location"); assertEquals(1.527218, waypointLoc.get(0).asDouble(), .00001); - //Check that there are 3 legs + // Check that there are 3 legs JsonNode route = json.get("routes").get(0); JsonNode legs = route.get("legs"); assertEquals(3, legs.size()); @@ -331,17 +588,131 @@ public void testMultipleWaypoints() { maneuver = steps.get(steps.size() - 1).get("maneuver"); assertEquals("arrive", maneuver.get("type").asText()); + + JsonNode lastStep = steps.get(steps.size() - 1); // last step + JsonNode intersections = lastStep.get("intersections"); + assertNotEquals(null, intersections); } - // Check if the duration and distance of the legs sum up to the overall route distance and duration + // Check if the duration and distance of the legs sum up to the overall route + // distance and duration assertEquals(route.get("duration").asDouble(), duration, 1); assertEquals(route.get("distance").asDouble(), distance, 1); } + @Test + public void testAnnotationCountsMatch() { + GHRequest request = new GHRequest(); + request.addPoint(new GHPoint(42.504606, 1.522438)); + request.addPoint(new GHPoint(42.504776, 1.527209)); + request.addPoint(new GHPoint(42.505144, 1.526113)); + request.setProfile(profile); + request.setPathDetails(Arrays.asList("max_speed", "time", "intersection")); + + GHResponse rsp = hopper.route(request); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + JsonNode route = json.get("routes").get(0); + JsonNode legs = route.get("legs"); + assertEquals(2, legs.size(), "expected 2 legs"); + + int totalAnnotationSegments = 0; + double totalAnnotationDistance = 0; + double totalAnnotationDuration = 0; + + for (int i = 0; i < legs.size(); i++) { + JsonNode leg = legs.get(i); + JsonNode annotation = leg.get("annotation"); + assertNotNull(annotation, "leg " + i + " should have annotation"); + + JsonNode distanceArr = annotation.get("distance"); + JsonNode durationArr = annotation.get("duration"); + JsonNode maxSpeedArr = annotation.get("maxspeed"); + + int distanceCount = distanceArr.size(); + int durationCount = durationArr.size(); + int maxSpeedCount = maxSpeedArr.size(); + + assertTrue(distanceCount > 0, "leg " + i + " should have annotations"); + assertEquals(distanceCount, maxSpeedCount, + "leg " + i + ": distance and maxspeed annotation counts should match"); + assertEquals(distanceCount, durationCount, + "leg " + i + ": distance and duration annotation counts should match"); + + // Verify all annotation distances are positive + double legAnnotationDistance = 0; + for (int j = 0; j < distanceCount; j++) { + double d = distanceArr.get(j).asDouble(); + assertTrue(d > 0, "leg " + i + " annotation distance[" + j + "] should be positive but was " + d); + legAnnotationDistance += d; + } + + // Verify sum of annotation distances approximately equals leg distance + double legDistance = leg.get("distance").asDouble(); + assertEquals(legDistance, legAnnotationDistance, legDistance * 0.01, + "leg " + i + ": sum of annotation distances should match leg distance"); + + // Verify sum of annotation durations approximately equals leg duration + double legAnnotationDuration = 0; + for (int j = 0; j < durationCount; j++) { + double d = durationArr.get(j).asDouble(); + assertTrue(d >= 0, "leg " + i + " annotation duration[" + j + "] should be non-negative"); + legAnnotationDuration += d; + } + double legDuration = leg.get("duration").asDouble(); + assertEquals(legDuration, legAnnotationDuration, legDuration * 0.01, + "leg " + i + ": sum of annotation durations should match leg duration"); + + totalAnnotationSegments += distanceCount; + totalAnnotationDistance += legAnnotationDistance; + totalAnnotationDuration += legAnnotationDuration; + } + + // Each leg shares the via point, so leg1 covers [0,V] and leg2 covers [V,end]) => -1 + assertEquals(rsp.getBest().getPoints().size() - 1, totalAnnotationSegments, + "total annotation segments should equal path points - 1"); + + double routeDistance = route.get("distance").asDouble(); + assertEquals(routeDistance, totalAnnotationDistance, 1, + "distance sum (in annotation) should match route distance"); + double time = route.get("duration").asDouble(); + assertEquals(time, totalAnnotationDuration, 1, + "duration sum (in annotation) should match route duration"); + } + + @Test + public void testMultipleWaypointsAndLastDuplicate() { + GHRequest request = new GHRequest(); + request.addPoint(new GHPoint(42.505144, 1.526113)); + request.addPoint(new GHPoint(42.50529, 1.527218)); + request.addPoint(new GHPoint(42.50529, 1.527218)); + request.setProfile(profile).setPathDetails(Collections.singletonList("intersection")); + + GHResponse rsp = hopper.route(request); + + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, distanceConfig); + + // Check that all waypoints are there and in the right order + JsonNode waypointsJson = json.get("waypoints"); + assertEquals(3, waypointsJson.size()); + + // Check that there are 2 legs + JsonNode route = json.get("routes").get(0); + JsonNode legs = route.get("legs"); + assertEquals(2, legs.size()); + + // check last leg + JsonNode leg = legs.get(1); + + JsonNode summary = leg.get("summary"); + assertNotNull(summary); + } + @Test public void testError() { - GHResponse rsp = hopper.route(new GHRequest(42.554851, 111.536198, 42.510071, 1.548128). - setProfile(profile)); + GHResponse rsp = hopper.route(new GHRequest(42.554851, 111.536198, 42.510071, 1.548128).setProfile(profile)); ObjectNode json = NavigateResponseConverter.convertFromGHResponseError(rsp); @@ -349,4 +720,33 @@ public void testError() { assertTrue(json.get("message").asText().startsWith("Point 0 is out of bounds: 42.554851,111.536198")); } + @Test + public void testTransportationModeInSteps() { + GHResponse rsp = hopper.route(new GHRequest(42.554851, 1.536198, 42.510071, 1.548128).setProfile(profile)); + + // Test walking mode + DistanceConfig walkingConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH, TransportationMode.FOOT); + ObjectNode json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, walkingConfig); + JsonNode steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + // Check first non-ferry step has walking mode + for (int i = 0; i < steps.size(); i++) { + JsonNode step = steps.get(i); + String expectedMode = "walking"; + // Ferry instructions should override to "ferry" mode + // We don't have ferry in this test data, but this shows the expected behavior + assertEquals(expectedMode, step.get("mode").asText(), "Step " + i + " should have mode 'walking'"); + } + + // Test cycling mode + DistanceConfig cyclingConfig = new DistanceConfig(DistanceUtils.Unit.METRIC, trMap, Locale.ENGLISH, TransportationMode.BIKE); + json = NavigateResponseConverter.convertFromGHResponse(rsp, trMap, Locale.ENGLISH, cyclingConfig); + steps = json.get("routes").get(0).get("legs").get(0).get("steps"); + + for (int i = 0; i < steps.size(); i++) { + JsonNode step = steps.get(i); + assertEquals("cycling", step.get("mode").asText(), "Step " + i + " should have mode 'cycling'"); + } + } + } diff --git a/pom.xml b/pom.xml index 2ac8eb87469..3a78050f610 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.graphhopper graphhopper-parent GraphHopper Parent Project - 9.0-SNAPSHOT + 12.0-SNAPSHOT pom https://www.graphhopper.com 2012 @@ -75,14 +75,14 @@ io.dropwizard dropwizard-dependencies - 2.1.11 + 5.0.1 pom import com.graphhopper.external jackson-datatype-jts - 2.14 + 2.21.0 com.fasterxml.jackson.core @@ -98,17 +98,22 @@ org.locationtech.jts jts-core - 1.19.0 + 1.20.0 org.apache.commons commons-compress - 1.21 + 1.26.0 + + + com.github.usefulness + webp-imageio + 0.10.2 org.junit junit-bom - 5.9.1 + 6.0.2 pom import @@ -120,7 +125,7 @@ commons-io commons-io - 2.11.0 + 2.14.0 org.mapdb @@ -128,9 +133,9 @@ 1.0.8 - io.mobilitydata.transit + org.mobilitydata gtfs-realtime-bindings - 0.0.5 + 0.0.8 @@ -139,15 +144,16 @@ + - javax.inject - javax.inject - 1 + jakarta.inject + jakarta.inject-api + 2.0.1 org.hamcrest hamcrest-library - 1.3 + 3.0 test @@ -176,7 +182,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.8.1 + 3.14.1 org.apache.maven.plugins maven-checkstyle-plugin - 3.1.0 + 3.6.0 ${user.dir}/core/files/checkstyle.xml true @@ -251,7 +257,7 @@ de.thetaphi forbiddenapis - 2.6 + 3.10 true @@ -339,7 +342,7 @@ org.apache.maven.plugins maven-source-plugin - 3.2.1 + 3.4.0 attach-sources diff --git a/reader-gtfs/config-example-pt.yml b/reader-gtfs/config-example-pt.yml index fad859855fc..84c8f689a53 100644 --- a/reader-gtfs/config-example-pt.yml +++ b/reader-gtfs/config-example-pt.yml @@ -6,8 +6,13 @@ graphhopper: profiles: - name: foot - vehicle: foot - weighting: custom + custom_model_files: + - foot.json + # optionally add + # - foot_elevation.json + + import.osm.ignored_highways: motorway,trunk + graph.encoded_values: foot_access, foot_average_speed, foot_priority, foot_road_access, hike_rating, mtb_rating, country, road_class server: application_connectors: diff --git a/reader-gtfs/pom.xml b/reader-gtfs/pom.xml index 7d430a51dfe..b17e9a42f1a 100644 --- a/reader-gtfs/pom.xml +++ b/reader-gtfs/pom.xml @@ -10,7 +10,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT @@ -40,13 +40,9 @@ slf4j-api - io.mobilitydata.transit + org.mobilitydata gtfs-realtime-bindings - - javax.inject - javax.inject - ch.qos.logback logback-classic @@ -74,6 +70,10 @@ mockito-core test + + jakarta.inject + jakarta.inject-api + diff --git a/reader-gtfs/src/main/java/com/conveyal/gtfs/GTFSFeed.java b/reader-gtfs/src/main/java/com/conveyal/gtfs/GTFSFeed.java index 68ad264b3c7..b7e7239f851 100644 --- a/reader-gtfs/src/main/java/com/conveyal/gtfs/GTFSFeed.java +++ b/reader-gtfs/src/main/java/com/conveyal/gtfs/GTFSFeed.java @@ -31,6 +31,7 @@ import com.conveyal.gtfs.model.Calendar; import com.conveyal.gtfs.model.*; import com.google.common.collect.Iterables; +import com.graphhopper.gtfs.Trips; import org.locationtech.jts.geom.Coordinate; import org.locationtech.jts.geom.CoordinateList; import org.locationtech.jts.geom.GeometryFactory; @@ -51,6 +52,7 @@ import java.util.*; import java.util.concurrent.ConcurrentNavigableMap; import java.util.stream.Collectors; +import java.util.stream.Stream; import java.util.stream.StreamSupport; /** @@ -174,6 +176,35 @@ public FeedInfo getFeedInfo () { return this.hasFeedInfo() ? this.feedInfo.values().iterator().next() : null; } + + public static class StopTimesForTripWithTripPatternKey { + public StopTimesForTripWithTripPatternKey(String feedId, Trip trip, Service service, int routeType, List stopTimes, Trips.Pattern pattern) { + this.feedId = feedId; + this.trip = trip; + this.service = service; + this.routeType = routeType; + this.stopTimes = stopTimes; + this.pattern = pattern; + } + + public final String feedId; + public final Trip trip; + public final Service service; + public final int routeType; + public final List stopTimes; + public final Trips.Pattern pattern; + public int idx; + public int endIdxOfPattern; // exclusive + public int getDepartureTime() { + for (StopTime stopTime : stopTimes) { + if (stopTime != null) { + return stopTime.departure_time; + } + } + throw new RuntimeException(); + } + } + /** * For the given trip ID, fetch all the stop times in order of increasing stop_sequence. * This is an efficient iteration over a tree map. @@ -196,9 +227,9 @@ public Shape getShape (String shape_id) { /** * For the given trip ID, fetch all the stop times in order, and interpolate stop-to-stop travel times. */ - public Iterable getInterpolatedStopTimesForTrip (String trip_id) throws FirstAndLastStopsDoNotHaveTimes { + public List getInterpolatedStopTimesForTrip (String trip_id) throws FirstAndLastStopsDoNotHaveTimes { // clone stop times so as not to modify base GTFS structures - StopTime[] stopTimes = StreamSupport.stream(getOrderedStopTimesForTrip(trip_id).spliterator(), false) + StopTime[] stopTimes = StreamSupport.stream(Spliterators.spliteratorUnknownSize(getOrderedStopTimesForTrip(trip_id).iterator(), 0), false) .map(st -> st.clone()) .toArray(i -> new StopTime[i]); @@ -489,4 +520,9 @@ public LocalDate getCalendarDateEnd() { return endDate; } + // Utility to more efficiently stream MapDB collections -- by default, stream() expensively determines collection size + public static Stream stream(Collection collection) { + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(collection.iterator(), 0), false); + } + } diff --git a/reader-gtfs/src/main/java/com/conveyal/gtfs/error/RangeError.java b/reader-gtfs/src/main/java/com/conveyal/gtfs/error/RangeError.java index fcc99c9087c..5d1b706cc7c 100644 --- a/reader-gtfs/src/main/java/com/conveyal/gtfs/error/RangeError.java +++ b/reader-gtfs/src/main/java/com/conveyal/gtfs/error/RangeError.java @@ -43,7 +43,7 @@ public RangeError(String file, long line, String field, double min, double max, } @Override public String getMessage() { - return String.format(Locale.getDefault(), "Number %s outside of acceptable range [%s,%s].", actual, min, max); + return String.format(Locale.getDefault(), "Number %s in field %s outside of acceptable range [%s,%s].", actual, field, min, max); } } diff --git a/reader-gtfs/src/main/java/com/conveyal/gtfs/error/ReferentialIntegrityError.java b/reader-gtfs/src/main/java/com/conveyal/gtfs/error/ReferentialIntegrityError.java index ed7d9c9b6a4..e22e3ce858a 100644 --- a/reader-gtfs/src/main/java/com/conveyal/gtfs/error/ReferentialIntegrityError.java +++ b/reader-gtfs/src/main/java/com/conveyal/gtfs/error/ReferentialIntegrityError.java @@ -27,7 +27,6 @@ package com.conveyal.gtfs.error; import java.io.Serializable; -import java.util.Locale; /** Indicates that an entity referenced another entity that does not exist. */ public class ReferentialIntegrityError extends GTFSError implements Serializable { diff --git a/reader-gtfs/src/main/java/com/conveyal/gtfs/model/StopTime.java b/reader-gtfs/src/main/java/com/conveyal/gtfs/model/StopTime.java index f9cee5fa03d..480c6bef77d 100644 --- a/reader-gtfs/src/main/java/com/conveyal/gtfs/model/StopTime.java +++ b/reader-gtfs/src/main/java/com/conveyal/gtfs/model/StopTime.java @@ -51,6 +51,16 @@ public class StopTime extends Entity implements Cloneable, Serializable { public double shape_dist_traveled; public int timepoint = INT_MISSING; + @Override + public String toString() { + return "StopTime{" + + "stop_sequence=" + stop_sequence + + ", arrival_time=" + arrival_time + + ", departure_time=" + departure_time + + ", stop_id='" + stop_id + '\'' + + '}'; + } + public static class Loader extends Entity.Loader { public Loader(GTFSFeed feed) { diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java index bfc1756f5ab..4d7b6782e75 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java @@ -65,7 +65,7 @@ public GraphExplorer(Graph graph, PtGraph ptGraph, Weighting accessEgressWeighti this.walkSpeedKmH = walkSpeedKmh; } - Iterable exploreEdgesAround(Label label) { + public Iterable exploreEdgesAround(Label label) { return () -> { Iterator ptEdges = label.node.ptNode != -1 ? ptEdgeStream(label.node.ptNode, label.currentTime).iterator() : Collections.emptyIterator(); Iterator streetEdges = label.node.streetNode != -1 ? streetEdgeStream(label.node.streetNode).iterator() : Collections.emptyIterator(); @@ -74,18 +74,17 @@ Iterable exploreEdgesAround(Label label) { } private Iterable realtimeEdgesAround(int node) { - return () -> realtimeFeed.getAdditionalEdges().stream().filter(e -> e.getBaseNode() == node).iterator(); + return () -> realtimeFeed.getAdditionalEdgesFrom(node).stream().iterator(); } private Iterable backRealtimeEdgesAround(int node) { - return () -> realtimeFeed.getAdditionalEdges().stream() - .filter(e -> e.getAdjNode() == node) + return () -> realtimeFeed.getAdditionalEdgesTo(node).stream() .map(e -> new PtGraph.PtEdge(e.getId(), e.getAdjNode(), e.getBaseNode(), e.getAttrs())) .iterator(); } - private Iterable ptEdgeStream(int ptNode, long currentTime) { + public Iterable ptEdgeStream(int ptNode, long currentTime) { return () -> Spliterators.iterator(new Spliterators.AbstractSpliterator(0, 0) { final Iterator edgeIterator = reverse ? Iterators.concat(ptNode < ptGraph.getNodeCount() ? ptGraph.backEdgesAround(ptNode).iterator() : Collections.emptyIterator(), backRealtimeEdgesAround(ptNode).iterator()) : @@ -161,8 +160,11 @@ private Iterable streetEdgeStream(int streetNode) { public boolean tryAdvance(Consumer action) { while (e.next()) { if (Double.isFinite(accessEgressWeighting.calcEdgeWeight(e, reverse))) { - action.accept(new MultiModalEdge(e.getEdge(), e.getBaseNode(), e.getAdjNode(), (long) (accessEgressWeighting.calcEdgeMillis(e.detach(false), reverse) * (5.0 / walkSpeedKmH)), e.getDistance())); - return true; + long travelTimeOrInfty = accessEgressWeighting.calcEdgeMillis(e.detach(false), reverse); + if (travelTimeOrInfty != Long.MAX_VALUE) { + action.accept(new MultiModalEdge(e.getEdge(), e.getBaseNode(), e.getAdjNode(), (long) (travelTimeOrInfty * (5.0 / walkSpeedKmH)), e.getDistance())); + return true; + } } } return false; diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphHopperGtfs.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphHopperGtfs.java index 8812922f913..6546252f355 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphHopperGtfs.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphHopperGtfs.java @@ -18,7 +18,11 @@ package com.graphhopper.gtfs; +import com.conveyal.gtfs.GTFSFeed; +import com.conveyal.gtfs.model.Stop; import com.conveyal.gtfs.model.Transfer; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimaps; import com.graphhopper.GraphHopper; import com.graphhopper.GraphHopperConfig; import com.graphhopper.routing.ev.Subnetwork; @@ -37,6 +41,7 @@ import java.io.File; import java.time.Duration; import java.time.Instant; +import java.time.LocalDate; import java.util.*; import java.util.stream.Collectors; @@ -65,10 +70,23 @@ protected void importOSM() { protected void importPublicTransit() { ptGraph = new PtGraph(getBaseGraph().getDirectory(), 100); gtfsStorage = new GtfsStorage(getBaseGraph().getDirectory()); + gtfsStorage.setPtGraph(ptGraph); LineIntIndex stopIndex = new LineIntIndex(new BBox(-180.0, 180.0, -90.0, 90.0), getBaseGraph().getDirectory(), "stop_index"); if (getGtfsStorage().loadExisting()) { ptGraph.loadExisting(); stopIndex.loadExisting(); + if (ghConfig.getBool("gtfs.trip_based", false)) { + for (String trafficDayString : ghConfig.getString("gtfs.schedule_day", null).split(",")) { + LocalDate trafficDay = LocalDate.parse(trafficDayString); + LOGGER.info("Loading trip-based transfers for pt router. Schedule day: {}", trafficDay); + gtfsStorage.tripTransfers.getTripTransfers().put(trafficDay, gtfsStorage.deserializeTripTransfersMap("trip_transfers_" + trafficDayString)); + } + for (Map.Entry entry : this.gtfsStorage.getGtfsFeeds().entrySet()) { + for (Stop stop : entry.getValue().stops.values()) { + gtfsStorage.tripTransfers.getPatternBoardings(new GtfsStorage.FeedIdWithStopId(entry.getKey(), stop.stop_id)); + } + } + } } else { ensureWriteAccess(); getGtfsStorage().create(); @@ -103,6 +121,17 @@ protected void importPublicTransit() { allReaders.put(id, gtfsReader); }); interpolateTransfers(allReaders, allTransfers); + if (ghConfig.getBool("gtfs.trip_based", false)) { + ArrayListMultimap stopsForStationNode = Multimaps.invertFrom(Multimaps.forMap(gtfsStorage.getStationNodes()), ArrayListMultimap.create()); + for (String trafficDayString : ghConfig.getString("gtfs.schedule_day", null).split(",")) { + LocalDate trafficDay = LocalDate.parse(trafficDayString); + LOGGER.info("Computing trip-based transfers for pt router. Schedule day: {}", trafficDay); + Map> tripTransfersMap = gtfsStorage.tripTransfers.getTripTransfers(trafficDay); + gtfsStorage.tripTransfers.findAllTripTransfersInto(tripTransfersMap, trafficDay, allTransfers, stopsForStationNode); + LOGGER.info("Writing. Schedule day: {}", trafficDay); + gtfsStorage.serializeTripTransfersMap("trip_transfers_" + trafficDayString, tripTransfersMap); + } + } } catch (Exception e) { throw new RuntimeException("Error while constructing transit network. Is your GTFS file valid? Please check log for possible causes.", e); } @@ -112,7 +141,6 @@ protected void importPublicTransit() { stopIndex.flush(); } gtfsStorage.setStopIndex(stopIndex); - gtfsStorage.setPtGraph(ptGraph); } private void interpolateTransfers(HashMap readers, Map allTransfers) { @@ -135,12 +163,14 @@ private void interpolateTransfers(HashMap readers, Map " + toPlatformDescriptor); if (!toPlatformDescriptor.feed_id.equals(fromPlatformDescriptor.feed_id)) { LOGGER.debug(" Different feed. Inserting transfer with " + (int) (label.streetTime / 1000L) + " s."); - insertInterpolatedTransfer(label, toPlatformDescriptor, readers); + insertInterpolatedTripTransfer(fromPlatformDescriptor, toPlatformDescriptor, (int) (label.streetTime / 1000L), getSkippedEdgesForTransfer(label)); + insertInterpolatedTransfer(label, toPlatformDescriptor, readers, getSkippedEdgesForTransfer(label)); } else { List transfersToStop = transfers.getTransfersToStop(toPlatformDescriptor.stop_id, routeIdOrNull(toPlatformDescriptor)); if (transfersToStop.stream().noneMatch(t -> t.from_stop_id.equals(fromPlatformDescriptor.stop_id))) { LOGGER.debug(" Inserting transfer with " + (int) (label.streetTime / 1000L) + " s."); - insertInterpolatedTransfer(label, toPlatformDescriptor, readers); + insertInterpolatedTripTransfer(fromPlatformDescriptor, toPlatformDescriptor, (int) (label.streetTime / 1000L), getSkippedEdgesForTransfer(label)); + insertInterpolatedTransfer(label, toPlatformDescriptor, readers, getSkippedEdgesForTransfer(label)); } } } @@ -151,15 +181,16 @@ private void interpolateTransfers(HashMap readers, Map readers) { + + private void insertInterpolatedTripTransfer(GtfsStorage.PlatformDescriptor fromPlatformDescriptor, GtfsStorage.PlatformDescriptor toPlatformDescriptor, int streetTime, int[] skippedEdgesForTransfer) { + if (skippedEdgesForTransfer.length > 0) { // TODO: Elsewhere, we distinguish empty path ("at" a node) from no path + gtfsStorage.interpolatedTransfers.put(new GtfsStorage.FeedIdWithStopId(fromPlatformDescriptor.feed_id, fromPlatformDescriptor.stop_id), new GtfsStorage.InterpolatedTransfer(new GtfsStorage.FeedIdWithStopId(toPlatformDescriptor.feed_id, toPlatformDescriptor.stop_id), streetTime, skippedEdgesForTransfer)); + } + } + + private void insertInterpolatedTransfer(Label label, GtfsStorage.PlatformDescriptor toPlatformDescriptor, HashMap readers, int[] skippedEdgesForTransfer) { GtfsReader toFeedReader = readers.get(toPlatformDescriptor.feed_id); List transferEdgeIds = toFeedReader.insertTransferEdges(label.node.ptNode, (int) (label.streetTime / 1000L), toPlatformDescriptor); - List transitions = Label.getTransitions(label.parent, true); - int[] skippedEdgesForTransfer = transitions.stream().filter(t -> t.edge != null).mapToInt(t -> { - Label.NodeId adjNode = t.label.node; - EdgeIteratorState edgeIteratorState = getBaseGraph().getEdgeIteratorState(t.edge.getId(), adjNode.streetNode); - return edgeIteratorState.getEdgeKey(); - }).toArray(); if (skippedEdgesForTransfer.length > 0) { // TODO: Elsewhere, we distinguish empty path ("at" a node) from no path assert isValidPath(skippedEdgesForTransfer); for (Integer transferEdgeId : transferEdgeIds) { @@ -168,6 +199,16 @@ private void insertInterpolatedTransfer(Label label, GtfsStorage.PlatformDescrip } } + private int[] getSkippedEdgesForTransfer(Label label) { + List transitions = Label.getTransitions(label.parent, true); + int[] skippedEdgesForTransfer = transitions.stream().filter(t -> t.edge != null).mapToInt(t -> { + Label.NodeId adjNode = t.label.node; + EdgeIteratorState edgeIteratorState = getBaseGraph().getEdgeIteratorState(t.edge.getId(), adjNode.streetNode); + return edgeIteratorState.getEdgeKey(); + }).toArray(); + return skippedEdgesForTransfer; + } + private boolean isValidPath(int[] edgeKeys) { List edges = Arrays.stream(edgeKeys).mapToObj(i -> getBaseGraph().getEdgeIteratorStateForKey(i)).collect(Collectors.toList()); for (int i = 1; i < edges.size(); i++) { diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsReader.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsReader.java index 95d18400a83..317f9fca466 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsReader.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsReader.java @@ -240,14 +240,18 @@ void wireUpAdditionalDeparturesAndArrivals(ZoneId zoneId) { private void addTrips(ZoneId zoneId, List trips, int time, boolean frequencyBased) { List arrivalNodes = new ArrayList<>(); for (TripWithStopTimes trip : trips) { - GtfsRealtime.TripDescriptor.Builder tripDescriptor = GtfsRealtime.TripDescriptor.newBuilder() - .setTripId(trip.trip.trip_id) - .setRouteId(trip.trip.route_id); - if (frequencyBased) { - tripDescriptor = tripDescriptor.setStartTime(convertToGtfsTime(time)); - } - addTrip(zoneId, time, arrivalNodes, trip, tripDescriptor.build()); + addTrip(zoneId, time, arrivalNodes, trip, getTripDescriptor(time, frequencyBased, trip)); + } + } + + private static GtfsRealtime.TripDescriptor getTripDescriptor(int time, boolean frequencyBased, TripWithStopTimes trip) { + GtfsRealtime.TripDescriptor.Builder tripDescriptor = GtfsRealtime.TripDescriptor.newBuilder() + .setTripId(trip.trip.trip_id) + .setRouteId(trip.trip.route_id); + if (frequencyBased) { + tripDescriptor = tripDescriptor.setStartTime(convertToGtfsTime(time)); } + return tripDescriptor.build(); } private static class TripWithStopTimeAndArrivalNode { @@ -372,7 +376,10 @@ private NavigableMap findDepartureTimelineForPlatform(int plat } private int findPlatformEnter(GtfsStorage.PlatformDescriptor platformDescriptor) { - int stopNode = gtfsStorage.getStationNodes().get(new GtfsStorage.FeedIdWithStopId(platformDescriptor.feed_id, platformDescriptor.stop_id)); + Integer stopNode = gtfsStorage.getStationNodes().get(new GtfsStorage.FeedIdWithStopId(platformDescriptor.feed_id, platformDescriptor.stop_id)); + if (stopNode == null) { + return -1; + } for (PtGraph.PtEdge ptEdge : ptGraph.edgesAround(stopNode)) { if (ptEdge.getType() == GtfsStorage.EdgeType.ENTER_PT && platformDescriptor.equals(ptEdge.getAttrs().platformDescriptor)) { return ptEdge.getAdjNode(); @@ -382,7 +389,10 @@ private int findPlatformEnter(GtfsStorage.PlatformDescriptor platformDescriptor) } private int findPlatformExit(GtfsStorage.PlatformDescriptor platformDescriptor) { - int stopNode = gtfsStorage.getStationNodes().get(new GtfsStorage.FeedIdWithStopId(platformDescriptor.feed_id, platformDescriptor.stop_id)); + Integer stopNode = gtfsStorage.getStationNodes().get(new GtfsStorage.FeedIdWithStopId(platformDescriptor.feed_id, platformDescriptor.stop_id)); + if (stopNode == null) { + return -1; + } for (PtGraph.PtEdge ptEdge : ptGraph.backEdgesAround(stopNode)) { if (ptEdge.getType() == GtfsStorage.EdgeType.EXIT_PT && platformDescriptor.equals(ptEdge.getAttrs().platformDescriptor)) { return ptEdge.getAdjNode(); @@ -397,7 +407,7 @@ int addDelayedBoardEdge(ZoneId zoneId, GtfsRealtime.TripDescriptor tripDescripto StopTime stopTime = feed.stop_times.get(new Fun.Tuple2(tripDescriptor.getTripId(), stopSequence)); Map> departureTimelineNodesByRoute = departureTimelinesByStop.computeIfAbsent(stopTime.stop_id, s -> new HashMap<>()); NavigableMap departureTimelineNodes = departureTimelineNodesByRoute.computeIfAbsent(GtfsStorage.PlatformDescriptor.route(id, stopTime.stop_id, trip.route_id), s -> new TreeMap<>()); - int departureTimelineNode = departureTimelineNodes.computeIfAbsent(departureTime % (24 * 60 * 60), t -> ptGraph.createNode()); + int departureTimelineNode = departureTimelineNodes.computeIfAbsent(departureTime % (24 * 60 * 60), t -> out.createNode()); int dayShift = departureTime / (24 * 60 * 60); GtfsStorage.Validity validOn = new GtfsStorage.Validity(getValidOn(validOnDay, dayShift), zoneId, startDate); @@ -440,7 +450,7 @@ private void insertInboundBlockTransfers(List ar blockTransferValidity.or(validOn.validity); blockTransferValidity.and(accumulatorValidity); GtfsStorage.Validity blockTransferValidOn = new GtfsStorage.Validity(blockTransferValidity, zoneId, startDate); - int node = ptGraph.createNode(); + int node = out.createNode(); out.createEdge(lastTrip.arrivalNode, node, new PtEdgeAttributes(GtfsStorage.EdgeType.TRANSFER, dwellTime, null, -1, null, 0, -1, null, platform)); out.createEdge(node, departureNode, new PtEdgeAttributes(GtfsStorage.EdgeType.BOARD, 0, blockTransferValidOn, -1, null, 0, stopTime.stop_sequence, tripDescriptor, null)); accumulatorValidity.andNot(lastTrip.tripWithStopTimes.validOnDay); diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsStorage.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsStorage.java index 01f87aba735..286d2dfa66d 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsStorage.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GtfsStorage.java @@ -24,6 +24,13 @@ import com.carrotsearch.hppc.cursors.IntObjectCursor; import com.conveyal.gtfs.GTFSFeed; import com.conveyal.gtfs.model.Fare; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.MappingIterator; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SequenceWriter; +import com.google.common.collect.HashMultimap; import com.graphhopper.storage.Directory; import com.graphhopper.storage.index.LineIntIndex; import org.mapdb.DB; @@ -41,8 +48,12 @@ public class GtfsStorage { private static final Logger LOGGER = LoggerFactory.getLogger(GtfsStorage.class); + + static ObjectMapper ionMapper = new ObjectMapper(); + private LineIntIndex stopIndex; private PtGraph ptGraph; + public Trips tripTransfers; public void setStopIndex(LineIntIndex stopIndex) { this.stopIndex = stopIndex; @@ -88,8 +99,8 @@ public int hashCode() { } } - static class FeedIdWithTimezone implements Serializable { - final String feedId; + public static class FeedIdWithTimezone implements Serializable { + public final String feedId; final ZoneId zoneId; FeedIdWithTimezone(String feedId, ZoneId zoneId) { @@ -112,10 +123,15 @@ public int hashCode() { } public static class FeedIdWithStopId implements Serializable { + + @JsonProperty("feed_id") public final String feedId; + + @JsonProperty("stop_id") public final String stopId; - public FeedIdWithStopId(String feedId, String stopId) { + public FeedIdWithStopId(@JsonProperty("feed_id") String feedId, + @JsonProperty("stop_id") String stopId) { this.feedId = feedId; this.stopId = stopId; } @@ -158,9 +174,9 @@ public enum EdgeType { HIGHWAY, ENTER_TIME_EXPANDED_NETWORK, LEAVE_TIME_EXPANDED_NETWORK, ENTER_PT, EXIT_PT, HOP, DWELL, BOARD, ALIGHT, OVERNIGHT, TRANSFER, WAIT, WAIT_ARRIVAL } - private DB data; + public DB data; - GtfsStorage(Directory dir) { + public GtfsStorage(Directory dir) { this.dir = dir; } @@ -171,27 +187,43 @@ boolean loadExisting() { } this.data = DBMaker.newFileDB(file).transactionDisable().mmapFileEnable().readOnly().make(); init(); - for (String gtfsFeedId : this.gtfsFeedIds) { - File dbFile = new File(dir.getLocation() + "/" + gtfsFeedId); - - if (!dbFile.exists()) { - throw new RuntimeException(String.format("The mapping of the gtfsFeeds in the transit_schedule DB does not reflect the files in %s. " - + "dbFile %s is missing.", - dir.getLocation(), dbFile.getName())); - } - - GTFSFeed feed = new GTFSFeed(dbFile); - this.gtfsFeeds.put(gtfsFeedId, feed); - } - ptToStreet = deserialize("pt_to_street"); - streetToPt = deserialize("street_to_pt"); + for (int i = 0; i < gtfsFeedIds.size(); i++) { + String gtfsFeedId = "gtfs_" + i; + File dbFile = new File(dir.getLocation() + "/" + gtfsFeedId); + + if (!dbFile.exists()) { + throw new RuntimeException(String.format("The mapping of the gtfsFeeds in the transit_schedule DB does not reflect the files in %s. " + + "dbFile %s is missing.", + dir.getLocation(), dbFile.getName())); + } + + GTFSFeed feed = new GTFSFeed(dbFile); + this.gtfsFeeds.put(gtfsFeedId, feed); + } + ptToStreet = deserializeIntoIntIntHashMap("pt_to_street"); + streetToPt = deserializeIntoIntIntHashMap("street_to_pt"); skippedEdgesForTransfer = deserializeIntoIntObjectHashMap("skipped_edges_for_transfer"); - postInit(); + try (InputStream is = Files.newInputStream(Paths.get(dir.getLocation() + "interpolated_transfers"))) { + MappingIterator objectMappingIterator = ionMapper.reader(JsonNode.class).readValues(is); + objectMappingIterator.forEachRemaining(e -> { + try { + FeedIdWithStopId key = ionMapper.treeToValue(e.get(0), FeedIdWithStopId.class); + for (JsonNode jsonNode : e.get(1)) { + InterpolatedTransfer interpolatedTransfer = ionMapper.treeToValue(jsonNode, InterpolatedTransfer.class); + interpolatedTransfers.put(key, interpolatedTransfer); + } + } catch (JsonProcessingException ex) { + throw new RuntimeException(ex); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + postInit(); return true; } - - private IntIntHashMap deserialize(String filename) { + private IntIntHashMap deserializeIntoIntIntHashMap(String filename) { try (FileInputStream in = new FileInputStream(dir.getLocation() + filename)) { ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(in)); int size = ois.readInt(); @@ -205,16 +237,22 @@ private IntIntHashMap deserialize(String filename) { } } - private IntObjectHashMap deserializeIntoIntObjectHashMap(String filename) { + public IntObjectHashMap deserializeIntoIntObjectHashMap(String filename) { try (FileInputStream in = new FileInputStream(dir.getLocation() + filename)) { ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(in)); int size = ois.readInt(); - IntObjectHashMap result = new IntObjectHashMap<>(); + IntObjectHashMap result = new IntObjectHashMap<>(size); for (int i = 0; i < size; i++) { - result.put(ois.readInt(), ((int[]) ois.readObject())); + int key = ois.readInt(); + int n = ois.readInt(); + int[] ints = new int[n]; + for (int j = 0; j < n; j++) { + ints[j] = ois.readInt(); + } + result.put(key, ints); } return result; - } catch (IOException | ClassNotFoundException e) { + } catch (IOException e) { throw new RuntimeException(e); } } @@ -259,6 +297,7 @@ public void postInit() { LOGGER.info("Calendar range covered by all feeds: {} till {}", latestStartDate, earliestEndDate); faresByFeed = new HashMap<>(); this.gtfsFeeds.forEach((feed_id, feed) -> faresByFeed.put(feed_id, feed.fares)); + tripTransfers = new Trips(this); } public void close() { @@ -295,14 +334,65 @@ public void flush() { serialize("pt_to_street", ptToStreet); serialize("street_to_pt", streetToPt); serialize("skipped_edges_for_transfer", skippedEdgesForTransfer); + try (OutputStream os = Files.newOutputStream(Paths.get(dir.getLocation() + "interpolated_transfers"))) { + SequenceWriter sequenceWriter = ionMapper.writer().writeValuesAsArray(os); + for (Map.Entry> e : interpolatedTransfers.asMap().entrySet()) { + sequenceWriter.write(ionMapper.createArrayNode().addPOJO(e.getKey()).addPOJO(e.getValue())); + } + sequenceWriter.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void serializeTripTransfersMap(String filename, Map> data) { + try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(Files.newOutputStream(Paths.get(dir.getLocation() + filename))))) { + oos.writeInt(data.size()); + for (Map.Entry> entry : data.entrySet()) { + oos.writeInt(entry.getKey().tripIdx); + oos.writeInt(entry.getKey().stop_sequence); + oos.writeInt(entry.getValue().size()); + for (Trips.TripAtStopTime tripAtStopTime : entry.getValue()) { + oos.writeInt(tripAtStopTime.tripIdx); + oos.writeInt(tripAtStopTime.stop_sequence); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } } - private void serialize(String filename, IntObjectHashMap data) { + public Map> deserializeTripTransfersMap(String filename) { + try (FileInputStream in = new FileInputStream(dir.getLocation() + filename)) { + ObjectInputStream ois = new ObjectInputStream(new BufferedInputStream(in)); + int size = ois.readInt(); + Map> result = new TreeMap<>(); + for (int i = 0; i < size; i++) { + Trips.TripAtStopTime origin = new Trips.TripAtStopTime(ois.readInt(), ois.readInt()); + int nDestinations = ois.readInt(); + List destinations = new ArrayList<>(nDestinations); + for (int j = 0; j < nDestinations; j++) { + int tripIdxTo = ois.readInt(); + int stop_sequenceTo = ois.readInt(); + destinations.add(new Trips.TripAtStopTime(tripIdxTo, stop_sequenceTo)); + } + result.put(origin, destinations); + } + return result; + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void serialize(String filename, IntObjectHashMap data) { try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(Files.newOutputStream(Paths.get(dir.getLocation() + filename))))) { oos.writeInt(data.size()); for (IntObjectCursor e : data) { oos.writeInt(e.key); - oos.writeObject(e.value); + oos.writeInt(e.value.length); + for (int v : e.value) { + oos.writeInt(v); + } } } catch (IOException e) { throw new RuntimeException(e); @@ -410,4 +500,28 @@ public String toString() { '}'; } } + + public HashMultimap interpolatedTransfers = HashMultimap.create(); + + + public static class InterpolatedTransfer { + + @JsonProperty("to_stop") + public final FeedIdWithStopId toPlatformDescriptor; + + @JsonProperty("street_time") + public final int streetTime; + + @JsonProperty("skipped_edges") + public final int[] skippedEdgesForTransfer; + + public InterpolatedTransfer(@JsonProperty("to_stop") FeedIdWithStopId toPlatformDescriptor, + @JsonProperty("street_time") int streetTime, + @JsonProperty("skipped_edges") int[] skippedEdgesForTransfer) { + this.toPlatformDescriptor = toPlatformDescriptor; + this.streetTime = streetTime; + this.skippedEdgesForTransfer = skippedEdgesForTransfer; + } + } + } diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtEdgeAttributes.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtEdgeAttributes.java index 65137bedcbc..3f89fdc643b 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtEdgeAttributes.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtEdgeAttributes.java @@ -20,6 +20,7 @@ public String toString() { "type=" + type + ", time=" + time + ", transfers=" + transfers + + ", tripDescriptor=" + tripDescriptor + '}'; } diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterFreeWalkImpl.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterFreeWalkImpl.java index 96ddcd195ab..e54c9c86677 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterFreeWalkImpl.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterFreeWalkImpl.java @@ -39,7 +39,7 @@ import com.graphhopper.util.exceptions.ConnectionNotFoundException; import com.graphhopper.util.exceptions.MaximumNodesExceededException; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.time.Instant; import java.util.*; diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterImpl.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterImpl.java index 8e573cc7ff2..a2bceefd7b2 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterImpl.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterImpl.java @@ -39,7 +39,7 @@ import com.graphhopper.util.exceptions.ConnectionNotFoundException; import com.graphhopper.util.exceptions.MaximumNodesExceededException; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.time.Instant; import java.util.*; import java.util.function.Predicate; diff --git a/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterTripBasedImpl.java b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterTripBasedImpl.java new file mode 100644 index 00000000000..605d769a834 --- /dev/null +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/PtRouterTripBasedImpl.java @@ -0,0 +1,382 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.gtfs; + +import com.conveyal.gtfs.GTFSFeed; +import com.conveyal.gtfs.model.Stop; +import com.graphhopper.*; +import com.graphhopper.config.Profile; +import com.graphhopper.routing.DefaultWeightingFactory; +import com.graphhopper.routing.WeightingFactory; +import com.graphhopper.routing.ev.Subnetwork; +import com.graphhopper.routing.querygraph.QueryGraph; +import com.graphhopper.routing.util.DefaultSnapFilter; +import com.graphhopper.routing.util.EdgeFilter; +import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.index.LocationIndex; +import com.graphhopper.util.PMap; +import com.graphhopper.util.StopWatch; +import com.graphhopper.util.Translation; +import com.graphhopper.util.TranslationMap; +import com.graphhopper.util.details.PathDetailsBuilderFactory; +import com.graphhopper.util.exceptions.ConnectionNotFoundException; +import com.graphhopper.util.exceptions.MaximumNodesExceededException; +import org.locationtech.jts.geom.Coordinate; +import org.locationtech.jts.geom.GeometryFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import jakarta.inject.Inject; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +public final class PtRouterTripBasedImpl implements PtRouter { + + private static final Logger logger = LoggerFactory.getLogger(PtRouterTripBasedImpl.class); + + private final GraphHopperConfig config; + private final TranslationMap translationMap; + private final BaseGraph baseGraph; + private final EncodingManager encodingManager; + private final LocationIndex locationIndex; + private final GtfsStorage gtfsStorage; + private final PtGraph ptGraph; + private final PathDetailsBuilderFactory pathDetailsBuilderFactory; + private final WeightingFactory weightingFactory; + private final Map feedZoneIds = new ConcurrentHashMap<>(); // ad-hoc cache for timezone field of gtfs feed + private final GraphHopper graphHopper; + + @Inject + public PtRouterTripBasedImpl(GraphHopper graphHopper, GraphHopperConfig config, TranslationMap translationMap, BaseGraph baseGraph, EncodingManager encodingManager, LocationIndex locationIndex, GtfsStorage gtfsStorage, PathDetailsBuilderFactory pathDetailsBuilderFactory) { + this.graphHopper = graphHopper; + this.config = config; + this.weightingFactory = new DefaultWeightingFactory(baseGraph, encodingManager); + this.translationMap = translationMap; + this.baseGraph = baseGraph; + this.encodingManager = encodingManager; + this.locationIndex = locationIndex; + this.gtfsStorage = gtfsStorage; + this.ptGraph = gtfsStorage.getPtGraph(); + this.pathDetailsBuilderFactory = pathDetailsBuilderFactory; + } + + @Override + public GHResponse route(Request request) { + return new RequestHandler(request).route(); + } + + private class RequestHandler { + private final int maxVisitedNodesForRequest; + private final int limitSolutions; + private final Duration maxProfileDuration; + private final Instant initialTime; + private final boolean profileQuery; + private final boolean arriveBy; + private final boolean ignoreTransfers; + private final double betaTransfers; + private final double betaStreetTime; + private final double walkSpeedKmH; + private final int blockedRouteTypes; + private final Map transferPenaltiesByRouteType; + private final GHLocation enter; + private final GHLocation exit; + private final Translation translation; + private final List requestedPathDetails; + + private final GHResponse response = new GHResponse(); + private final long limitTripTime; + private final long limitStreetTime; + private final double betaAccessTime; + private final double betaEgressTime; + private QueryGraph queryGraph; + private int visitedNodes; + private final Profile accessProfile; + private final EdgeFilter accessSnapFilter; + private final Weighting accessWeighting; + private final Profile egressProfile; + private final EdgeFilter egressSnapFilter; + private final Weighting egressWeighting; + private TripFromLabel tripFromLabel; + private List

    * The number of headings must be zero (default), one (for the start point) or equal to the number of points * when sending the request. */ public GHRequest setHeadings(List headings) { - this.headings = headings; + this.headings = headings.stream() + .map(d -> d == null ? Double.NaN : d) + .collect(Collectors.toList()); return this; } @@ -204,12 +208,17 @@ public List getCurbsides() { return curbsides; } + public boolean hasSnapPreventions() { + return snapPreventions != null; + } + public GHRequest setSnapPreventions(List snapPreventions) { this.snapPreventions = snapPreventions; return this; } public List getSnapPreventions() { + if (snapPreventions == null) return Collections.EMPTY_LIST; return snapPreventions; } diff --git a/web-api/src/main/java/com/graphhopper/GHResponse.java b/web-api/src/main/java/com/graphhopper/GHResponse.java index ffd99461226..dd826a2409f 100644 --- a/web-api/src/main/java/com/graphhopper/GHResponse.java +++ b/web-api/src/main/java/com/graphhopper/GHResponse.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * Wrapper containing path and error output of GraphHopper. @@ -147,4 +148,11 @@ public void setHints(PMap hints) { public PMap getHints() { return hintsMap; } + + public String getHeader(String key, String defaultValue) { + Object val = hintsMap.getObject(key.toLowerCase(Locale.ROOT), null); + if (val instanceof List && !((List) val).isEmpty()) + return ((List) val).get(0).toString(); + return defaultValue; + } } diff --git a/web-api/src/main/java/com/graphhopper/ResponsePath.java b/web-api/src/main/java/com/graphhopper/ResponsePath.java index 8638efc73c7..2e8b2897483 100644 --- a/web-api/src/main/java/com/graphhopper/ResponsePath.java +++ b/web-api/src/main/java/com/graphhopper/ResponsePath.java @@ -267,8 +267,9 @@ public void addPathDetails(Map> details) { } for (Map.Entry> detailEntry : details.entrySet()) { String key = detailEntry.getKey(); - if (this.pathDetails.containsKey(key)) { - this.pathDetails.get(key).addAll(detailEntry.getValue()); + List pathDetails = this.pathDetails.get(key); + if (pathDetails != null) { + pathDetails.addAll(detailEntry.getValue()); } else { this.pathDetails.put(key, detailEntry.getValue()); } diff --git a/web-api/src/main/java/com/graphhopper/Trip.java b/web-api/src/main/java/com/graphhopper/Trip.java index 99eda350448..6b199b16c77 100644 --- a/web-api/src/main/java/com/graphhopper/Trip.java +++ b/web-api/src/main/java/com/graphhopper/Trip.java @@ -33,6 +33,7 @@ public double getDistance() { public static class Stop { public final String stop_id; + public final int stop_sequence; public final String stop_name; public final Point geometry; @@ -46,8 +47,9 @@ public static class Stop { public final Date predictedDepartureTime; public final boolean departureCancelled; - public Stop(String stop_id, String name, Point geometry, Date arrivalTime, Date plannedArrivalTime, Date predictedArrivalTime, boolean arrivalCancelled, Date departureTime, Date plannedDepartureTime, Date predictedDepartureTime, boolean departureCancelled) { + public Stop(String stop_id, int stop_sequence, String name, Point geometry, Date arrivalTime, Date plannedArrivalTime, Date predictedArrivalTime, boolean arrivalCancelled, Date departureTime, Date plannedDepartureTime, Date predictedDepartureTime, boolean departureCancelled) { this.stop_id = stop_id; + this.stop_sequence = stop_sequence; this.stop_name = name; this.geometry = geometry; this.arrivalTime = arrivalTime; @@ -64,6 +66,7 @@ public Stop(String stop_id, String name, Point geometry, Date arrivalTime, Date public String toString() { return "Stop{" + "stop_id='" + stop_id + '\'' + + ", stop_sequence=" + stop_sequence + ", arrivalTime=" + arrivalTime + ", departureTime=" + departureTime + '}'; @@ -93,6 +96,13 @@ public Date getArrivalTime() { return arrivalTime; } + @Override + public String toString() { + return "WalkLeg{" + + "departureTime=" + departureTime + + ", arrivalTime=" + arrivalTime + + '}'; + } } public static class PtLeg extends Leg { public final String feed_id; diff --git a/web-api/src/main/java/com/graphhopper/jackson/GHPointDeserializer.java b/web-api/src/main/java/com/graphhopper/jackson/GHPointDeserializer.java index 3bfa4d43fa6..3af7c5bca1f 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/GHPointDeserializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/GHPointDeserializer.java @@ -18,6 +18,7 @@ package com.graphhopper.jackson; import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import com.graphhopper.util.shapes.GHPoint; @@ -27,7 +28,11 @@ class GHPointDeserializer extends JsonDeserializer { @Override public GHPoint deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { - double[] bounds = jsonParser.readValueAs(double[].class); - return GHPoint.fromJson(bounds); + try { + double[] bounds = jsonParser.readValueAs(double[].class); + return GHPoint.fromJson(bounds); + } catch (JsonProcessingException ex) { + throw new IllegalArgumentException("point is invalid: " + ex.getMessage()); + } } } diff --git a/web-api/src/main/java/com/graphhopper/jackson/GHResponseDeserializer.java b/web-api/src/main/java/com/graphhopper/jackson/GHResponseDeserializer.java deleted file mode 100644 index 0488500fe9f..00000000000 --- a/web-api/src/main/java/com/graphhopper/jackson/GHResponseDeserializer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.graphhopper.jackson; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.graphhopper.GHResponse; -import com.graphhopper.ResponsePath; - -import java.io.IOException; - -public class GHResponseDeserializer extends JsonDeserializer { - @Override - public GHResponse deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - GHResponse ghResponse = new GHResponse(); - JsonNode treeNode = p.readValueAsTree(); - for (JsonNode path : treeNode.get("paths")) { - ResponsePath responsePath = ((ObjectMapper) p.getCodec()).convertValue(path, ResponsePath.class); - ghResponse.add(responsePath); - } - return ghResponse; - } -} diff --git a/web-api/src/main/java/com/graphhopper/jackson/GraphHopperModule.java b/web-api/src/main/java/com/graphhopper/jackson/GraphHopperModule.java index b6a6d3fc52d..d623a50d537 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/GraphHopperModule.java +++ b/web-api/src/main/java/com/graphhopper/jackson/GraphHopperModule.java @@ -18,8 +18,6 @@ package com.graphhopper.jackson; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.graphhopper.GHResponse; -import com.graphhopper.ResponsePath; import com.graphhopper.json.Statement; import com.graphhopper.util.InstructionList; import com.graphhopper.util.details.PathDetail; @@ -31,8 +29,6 @@ public class GraphHopperModule extends SimpleModule { public GraphHopperModule() { addDeserializer(Statement.class, new StatementDeserializer()); addSerializer(Statement.class, new StatementSerializer()); - addDeserializer(GHResponse.class, new GHResponseDeserializer()); - addDeserializer(ResponsePath.class, new ResponsePathDeserializer()); addDeserializer(Envelope.class, new JtsEnvelopeDeserializer()); addSerializer(Envelope.class, new JtsEnvelopeSerializer()); addDeserializer(GHPoint.class, new GHPointDeserializer()); diff --git a/web-api/src/main/java/com/graphhopper/jackson/InstructionListSerializer.java b/web-api/src/main/java/com/graphhopper/jackson/InstructionListSerializer.java index df66995d043..0e5dd644cd2 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/InstructionListSerializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/InstructionListSerializer.java @@ -17,13 +17,6 @@ */ package com.graphhopper.jackson; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; @@ -31,6 +24,9 @@ import com.graphhopper.util.Instruction; import com.graphhopper.util.InstructionList; +import java.io.IOException; +import java.util.*; + import static com.graphhopper.util.Parameters.Details.STREET_NAME; public class InstructionListSerializer extends JsonSerializer { diff --git a/web-api/src/main/java/com/graphhopper/jackson/Jackson.java b/web-api/src/main/java/com/graphhopper/jackson/Jackson.java index 0cc8b58c413..36dba40780e 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/Jackson.java +++ b/web-api/src/main/java/com/graphhopper/jackson/Jackson.java @@ -20,7 +20,7 @@ import com.bedatadriven.jackson.datatype.jts.JtsModule; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; public class Jackson { @@ -31,7 +31,7 @@ public static ObjectMapper newObjectMapper() { public static ObjectMapper initObjectMapper(ObjectMapper objectMapper) { objectMapper.registerModule(new GraphHopperModule()); objectMapper.registerModule(new JtsModule()); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); return objectMapper; } diff --git a/web-api/src/main/java/com/graphhopper/jackson/JtsEnvelopeDeserializer.java b/web-api/src/main/java/com/graphhopper/jackson/JtsEnvelopeDeserializer.java index b7e1e0ffe26..bd333daae6d 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/JtsEnvelopeDeserializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/JtsEnvelopeDeserializer.java @@ -18,7 +18,6 @@ package com.graphhopper.jackson; import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonDeserializer; import org.locationtech.jts.geom.Envelope; diff --git a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializer.java b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializerHelper.java similarity index 91% rename from web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializer.java rename to web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializerHelper.java index d8354190497..caeb9483124 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathDeserializerHelper.java @@ -17,10 +17,7 @@ */ package com.graphhopper.jackson; -import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.graphhopper.ResponsePath; @@ -29,14 +26,9 @@ import com.graphhopper.util.exceptions.*; import org.locationtech.jts.geom.LineString; -import java.io.IOException; import java.util.*; -public class ResponsePathDeserializer extends JsonDeserializer { - @Override - public ResponsePath deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - return createResponsePath((ObjectMapper) p.getCodec(), p.readValueAsTree(), false, true); - } +public class ResponsePathDeserializerHelper { public static ResponsePath createResponsePath(ObjectMapper objectMapper, JsonNode path, boolean hasElevation, boolean turnDescription) { ResponsePath responsePath = new ResponsePath(); @@ -44,9 +36,15 @@ public static ResponsePath createResponsePath(ObjectMapper objectMapper, JsonNod if (responsePath.hasErrors()) return responsePath; + // Read multiplier from JSON to properly decode the "points" and/or "snapped_waypoints" array. + // Note, in earlier versions the points_encoded was missing from the JSON for calc_points == false (although still required for snapped_waypoints). + double multiplier = 1e5; + if (path.has("points_encoded") && path.get("points_encoded").asBoolean() && path.has("points_encoded_multiplier")) + multiplier = path.get("points_encoded_multiplier").asDouble(); + if (path.has("snapped_waypoints")) { JsonNode snappedWaypoints = path.get("snapped_waypoints"); - PointList snappedPoints = deserializePointList(objectMapper, snappedWaypoints, hasElevation); + PointList snappedPoints = deserializePointList(objectMapper, snappedWaypoints, hasElevation, multiplier); responsePath.setWaypoints(snappedPoints); } @@ -73,7 +71,7 @@ public static ResponsePath createResponsePath(ObjectMapper objectMapper, JsonNod } if (path.has("points")) { - final PointList pointList = deserializePointList(objectMapper, path.get("points"), hasElevation); + final PointList pointList = deserializePointList(objectMapper, path.get("points"), hasElevation, multiplier); responsePath.setPoints(pointList); if (path.has("instructions")) { @@ -177,10 +175,10 @@ public static ResponsePath createResponsePath(ObjectMapper objectMapper, JsonNod return responsePath; } - private static PointList deserializePointList(ObjectMapper objectMapper, JsonNode jsonNode, boolean hasElevation) { + private static PointList deserializePointList(ObjectMapper objectMapper, JsonNode jsonNode, boolean hasElevation, double multiplier) { PointList snappedPoints; if (jsonNode.isTextual()) { - snappedPoints = decodePolyline(jsonNode.asText(), Math.max(10, jsonNode.asText().length() / 4), hasElevation); + snappedPoints = decodePolyline(jsonNode.asText(), Math.max(10, jsonNode.asText().length() / 4), hasElevation, multiplier); } else { LineString lineString = objectMapper.convertValue(jsonNode, LineString.class); snappedPoints = PointList.fromLineString(lineString); @@ -188,7 +186,10 @@ private static PointList deserializePointList(ObjectMapper objectMapper, JsonNod return snappedPoints; } - public static PointList decodePolyline(String encoded, int initCap, boolean is3D) { + public static PointList decodePolyline(String encoded, int initCap, boolean is3D, double multiplier) { + if (multiplier < 1) + throw new IllegalArgumentException("multiplier cannot be smaller than 1 but was " + multiplier + " for polyline " + encoded); + PointList poly = new PointList(initCap, is3D); int index = 0; int len = encoded.length(); @@ -226,9 +227,9 @@ public static PointList decodePolyline(String encoded, int initCap, boolean is3D } while (b >= 0x20); int deltaElevation = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1)); ele += deltaElevation; - poly.add((double) lat / 1e5, (double) lng / 1e5, (double) ele / 100); + poly.add((double) lat / multiplier, (double) lng / multiplier, (double) ele / 100); } else - poly.add((double) lat / 1e5, (double) lng / 1e5); + poly.add((double) lat / multiplier, (double) lng / multiplier); } return poly; } diff --git a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java index ca10633c9e1..73beb0143d6 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/ResponsePathSerializer.java @@ -27,7 +27,6 @@ import com.graphhopper.util.PointList; import java.text.NumberFormat; -import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -43,28 +42,24 @@ */ public class ResponsePathSerializer { - /** - * This includes the required attribution for OpenStreetMap. - * Do not hesitate to mention us and link us in your about page - * https://support.graphhopper.com/support/search/solutions?term=attribution - */ - public static final List COPYRIGHTS = Arrays.asList("GraphHopper", "OpenStreetMap contributors"); + public static String encodePolyline(PointList poly, boolean includeElevation, double multiplier) { + if (multiplier < 1) + throw new IllegalArgumentException("multiplier cannot be smaller than 1 but was " + multiplier + " for polyline"); - public static String encodePolyline(PointList poly, boolean includeElevation, double precision) { StringBuilder sb = new StringBuilder(Math.max(20, poly.size() * 3)); int size = poly.size(); int prevLat = 0; int prevLon = 0; int prevEle = 0; for (int i = 0; i < size; i++) { - int num = (int) Math.floor(poly.getLat(i) * precision); + int num = (int) Math.round(poly.getLat(i) * multiplier); encodeNumber(sb, num - prevLat); prevLat = num; - num = (int) Math.floor(poly.getLon(i) * precision); + num = (int) Math.round(poly.getLon(i) * multiplier); encodeNumber(sb, num - prevLon); prevLon = num; if (includeElevation) { - num = (int) Math.floor(poly.getEle(i) * 100); + num = (int) Math.round(poly.getEle(i) * 100); encodeNumber(sb, num - prevEle); prevEle = num; } @@ -86,16 +81,14 @@ private static void encodeNumber(StringBuilder sb, int num) { sb.append((char) (num)); } - public static ObjectNode jsonObject(GHResponse ghRsp, String osmDate, boolean enableInstructions, - boolean calcPoints, boolean enableElevation, boolean pointsEncoded, double took) { + public record Info(List copyrights, long took, String roadDataTimestamp) { + } + + public static ObjectNode jsonObject(GHResponse ghRsp, Info info, boolean enableInstructions, + boolean calcPoints, boolean enableElevation, boolean pointsEncoded, double pointsMultiplier) { ObjectNode json = JsonNodeFactory.instance.objectNode(); json.putPOJO("hints", ghRsp.getHints().toMap()); - final ObjectNode info = json.putObject("info"); - info.putPOJO("copyrights", COPYRIGHTS); - info.put("took", Math.round(took)); - if (!osmDate.isEmpty()) - info.put("road_data_timestamp", osmDate); - + json.putPOJO("info", info); ArrayNode jsonPathList = json.putArray("paths"); for (ResponsePath p : ghRsp.getAll()) { ObjectNode jsonPath = jsonPathList.addObject(); @@ -103,22 +96,26 @@ public static ObjectNode jsonObject(GHResponse ghRsp, String osmDate, boolean en jsonPath.put("weight", Helper.round6(p.getRouteWeight())); jsonPath.put("time", p.getTime()); jsonPath.put("transfers", p.getNumChanges()); + jsonPath.putPOJO("legs", p.getLegs()); if (!p.getDescription().isEmpty()) { jsonPath.putPOJO("description", p.getDescription()); } + + // for points and snapped_waypoints: + jsonPath.put("points_encoded", pointsEncoded); + if (pointsEncoded) jsonPath.put("points_encoded_multiplier", pointsMultiplier); + if (calcPoints) { - jsonPath.put("points_encoded", pointsEncoded); jsonPath.putPOJO("bbox", p.calcBBox2D()); - jsonPath.putPOJO("points", pointsEncoded ? encodePolyline(p.getPoints(), enableElevation, 1e5) : p.getPoints().toLineString(enableElevation)); + jsonPath.putPOJO("points", pointsEncoded ? encodePolyline(p.getPoints(), enableElevation, pointsMultiplier) : p.getPoints().toLineString(enableElevation)); if (enableInstructions) { jsonPath.putPOJO("instructions", p.getInstructions()); } - jsonPath.putPOJO("legs", p.getLegs()); jsonPath.putPOJO("details", p.getPathDetails()); jsonPath.put("ascend", p.getAscend()); jsonPath.put("descend", p.getDescend()); } - jsonPath.putPOJO("snapped_waypoints", pointsEncoded ? encodePolyline(p.getWaypoints(), enableElevation, 1e5) : p.getWaypoints().toLineString(enableElevation)); + jsonPath.putPOJO("snapped_waypoints", pointsEncoded ? encodePolyline(p.getWaypoints(), enableElevation, pointsMultiplier) : p.getWaypoints().toLineString(enableElevation)); if (p.getFare() != null) { jsonPath.put("fare", NumberFormat.getCurrencyInstance(Locale.ROOT).format(p.getFare())); } diff --git a/web-api/src/main/java/com/graphhopper/jackson/StatementDeserializer.java b/web-api/src/main/java/com/graphhopper/jackson/StatementDeserializer.java index 5821d16d93f..d1e8d1b4c3a 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/StatementDeserializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/StatementDeserializer.java @@ -1,3 +1,20 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.graphhopper.jackson; import com.fasterxml.jackson.core.JsonParser; @@ -7,44 +24,77 @@ import com.graphhopper.json.Statement; import java.io.IOException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.stream.Collectors; +import static com.graphhopper.json.Statement.*; import static com.graphhopper.json.Statement.Keyword.*; +import static com.graphhopper.json.Statement.Op.DO; class StatementDeserializer extends JsonDeserializer { + @Override public Statement deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - JsonNode treeNode = p.readValueAsTree(); - Statement.Op jsonOp = null; - String value = null; - if (treeNode.size() != 2) - throw new IllegalArgumentException("Statement expects two entries but was " + treeNode.size() + " for " + treeNode); - - for (Statement.Op op : Statement.Op.values()) { - if (treeNode.has(op.getName())) { - if (jsonOp != null) - throw new IllegalArgumentException("Multiple operations are not allowed. Statement: " + treeNode); - jsonOp = op; - value = treeNode.get(op.getName()).asText(); + return deserializeStatement(p.readValueAsTree()); + } + + static Statement deserializeStatement(JsonNode treeNode) { + if (treeNode.has(DO.getName())) { + if (treeNode.size() != 2) + throw new IllegalArgumentException("This block statement expects two entries but was " + treeNode.size() + " for " + treeNode); + + JsonNode doNode = treeNode.get(DO.getName()); + if (!doNode.isArray()) + throw new IllegalArgumentException("'do' block must be an array"); + List list = new ArrayList<>(); + for (JsonNode thenSt : doNode) { + list.add(deserializeStatement(thenSt)); } - } - if (jsonOp == null) - throw new IllegalArgumentException("Cannot find an operation in " + treeNode + ". Must be one of: " + Arrays.stream(Statement.Op.values()).map(Statement.Op::getName).collect(Collectors.joining(","))); - if (value == null) - throw new IllegalArgumentException("Cannot find a value in " + treeNode); - - if (treeNode.has(IF.getName())) - return Statement.If(treeNode.get(IF.getName()).asText(), jsonOp, value); - else if (treeNode.has(ELSEIF.getName())) - return Statement.ElseIf(treeNode.get(ELSEIF.getName()).asText(), jsonOp, value); - else if (treeNode.has(ELSE.getName())) { - JsonNode elseNode = treeNode.get(ELSE.getName()); - if (elseNode.isNull() || elseNode.isValueNode() && elseNode.asText().isEmpty()) - return Statement.Else(jsonOp, value); - throw new IllegalArgumentException("else cannot have expression but was " + treeNode.get(ELSE.getName())); - } + if (treeNode.has(IF.getName())) + return If(treeNode.get(IF.getName()).asText(), list); + else if (treeNode.has(ELSEIF.getName())) + return ElseIf(treeNode.get(ELSEIF.getName()).asText(), list); + else if (treeNode.has(ELSE.getName())) { + JsonNode elseNode = treeNode.get(ELSE.getName()); + if (elseNode.isNull() || elseNode.isValueNode() && elseNode.asText().isEmpty()) + return Else(list); + throw new IllegalArgumentException("else cannot have expression but was " + treeNode.get(ELSE.getName())); + } else + throw new IllegalArgumentException("invalid then block: " + treeNode.toPrettyString()); + + } else { + if (treeNode.size() != 2) + throw new IllegalArgumentException("This statement expects two entries but was " + treeNode.size() + " for " + treeNode); + Statement.Op jsonOp = null; + String value = null; + for (Statement.Op op : Statement.Op.values()) { + if (treeNode.has(op.getName())) { + if (jsonOp != null) + throw new IllegalArgumentException("Multiple operations are not allowed. Statement: " + treeNode); + jsonOp = op; + value = treeNode.get(op.getName()).asText(); + } + } + + if (jsonOp == null) + throw new IllegalArgumentException("Cannot find an operation in " + treeNode + ". Must be one of: " + Arrays.stream(Statement.Op.values()).map(Statement.Op::getName).collect(Collectors.joining(","))); + if (value == null) + throw new IllegalArgumentException("Cannot find a value in " + treeNode); + + if (treeNode.has(IF.getName())) + return If(treeNode.get(IF.getName()).asText(), jsonOp, value); + else if (treeNode.has(ELSEIF.getName())) + return ElseIf(treeNode.get(ELSEIF.getName()).asText(), jsonOp, value); + else if (treeNode.has(ELSE.getName())) { + JsonNode elseNode = treeNode.get(ELSE.getName()); + if (elseNode.isNull() || elseNode.isValueNode() && elseNode.asText().isEmpty()) + return Else(jsonOp, value); + throw new IllegalArgumentException("else cannot have expression but was " + treeNode.get(ELSE.getName())); + } + } throw new IllegalArgumentException("Cannot find if, else_if or else for " + treeNode); } } diff --git a/web-api/src/main/java/com/graphhopper/jackson/StatementSerializer.java b/web-api/src/main/java/com/graphhopper/jackson/StatementSerializer.java index cb1702ad704..4aafd176007 100644 --- a/web-api/src/main/java/com/graphhopper/jackson/StatementSerializer.java +++ b/web-api/src/main/java/com/graphhopper/jackson/StatementSerializer.java @@ -28,8 +28,16 @@ class StatementSerializer extends JsonSerializer { @Override public void serialize(Statement statement, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { jsonGenerator.writeStartObject(); - jsonGenerator.writeStringField(statement.getKeyword().getName(), statement.getCondition()); - jsonGenerator.writeStringField(statement.getOperation().getName(), statement.getValue()); + jsonGenerator.writeStringField(statement.keyword().getName(), statement.condition()); + if (statement.isBlock()) { + jsonGenerator.writeArrayFieldStart("do"); + for (Statement s : statement.doBlock()) { + serialize(s, jsonGenerator, serializerProvider); + } + jsonGenerator.writeEndArray(); + } else { + jsonGenerator.writeStringField(statement.operation().getName(), statement.value()); + } jsonGenerator.writeEndObject(); } } diff --git a/web-api/src/main/java/com/graphhopper/json/Statement.java b/web-api/src/main/java/com/graphhopper/json/Statement.java index 42e01b71b88..7e3218a2e02 100644 --- a/web-api/src/main/java/com/graphhopper/json/Statement.java +++ b/web-api/src/main/java/com/graphhopper/json/Statement.java @@ -17,39 +17,70 @@ */ package com.graphhopper.json; -public class Statement { - private final Keyword keyword; - private final String condition; - private final Op operation; - private final String value; - - private Statement(Keyword keyword, String condition, Op operation, String value) { - this.keyword = keyword; - this.condition = condition; - this.value = value; - this.operation = operation; +import com.graphhopper.util.Helper; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +public record Statement(Keyword keyword, String condition, Op operation, String value, + List doBlock) { + + public Statement { + if (condition == null) + throw new IllegalArgumentException("'condition' cannot be null"); + if (doBlock != null && operation != Op.DO) + throw new IllegalArgumentException("For 'doBlock' you have to use Op.DO"); + if (doBlock != null && value != null) + throw new IllegalArgumentException("'doBlock' or 'value' cannot be both non-null"); + if (doBlock == null && Helper.isEmpty(value)) + throw new IllegalArgumentException("a leaf statement must have a non-empty 'value'"); + if (condition.isEmpty() && keyword != Keyword.ELSE) + throw new IllegalArgumentException("All statements (except 'else') have to use a non-empty 'condition'"); + if (!condition.isEmpty() && keyword == Keyword.ELSE) + throw new IllegalArgumentException("For the 'else' statement you have to use an empty 'condition'"); } - public Keyword getKeyword() { - return keyword; + public boolean isBlock() { + return doBlock != null; } - public String getCondition() { - return condition; + @Override + public String value() { + if (isBlock()) + throw new UnsupportedOperationException("'value' is not supported for block statement."); + return value; } - public Op getOperation() { - return operation; + @Override + public List doBlock() { + if (!isBlock()) + throw new UnsupportedOperationException("'doBlock' is not supported for leaf statement."); + return doBlock; } - public String getValue() { - return value; + public String toPrettyString() { + if (isBlock()) + return "{\"" + keyword.getName() + "\": \"" + condition + "\",\n" + + " \"do\": [\n" + + doBlock.stream().map(Objects::toString).collect(Collectors.joining(",\n ")) + + " ]\n" + + "}"; + else return toString(); + } + + @Override + public String toString() { + if (isBlock()) + return "{\"" + keyword.getName() + "\": \"" + condition + "\", \"do\": " + doBlock + " }"; + else + return "{\"" + keyword.getName() + "\": \"" + condition + "\", \"" + operation.getName() + "\": \"" + value + "\"}"; } public enum Keyword { IF("if"), ELSEIF("else_if"), ELSE("else"); - String name; + private final String name; Keyword(String name) { this.name = name; @@ -61,9 +92,9 @@ public String getName() { } public enum Op { - MULTIPLY("multiply_by"), LIMIT("limit_to"); + MULTIPLY("multiply_by"), LIMIT("limit_to"), DO("do"), ADD("add"); - String name; + private final String name; Op(String name) { this.name = name; @@ -79,6 +110,8 @@ public String build(String value) { return "value *= " + value; case LIMIT: return "value = Math.min(value," + value + ")"; + case ADD: + return "value += " + (value.equals("Infinity") ? "Double.POSITIVE_INFINITY" : value); default: throw new IllegalArgumentException(); } @@ -90,30 +123,35 @@ public MinMax apply(MinMax minMax1, MinMax minMax2) { return new MinMax(minMax1.min * minMax2.min, minMax1.max * minMax2.max); case LIMIT: return new MinMax(Math.min(minMax1.min, minMax2.min), Math.min(minMax1.max, minMax2.max)); + case ADD: + return new MinMax(minMax1.min + minMax2.min, minMax1.max + minMax2.max); default: throw new IllegalArgumentException(); } } } - @Override - public String toString() { - return "{" + str(keyword.getName()) + ": " + str(condition) + ", " + str(operation.getName()) + ": " + value + "}"; + public static Statement If(String expression, List doBlock) { + return new Statement(Keyword.IF, expression, Op.DO, null, doBlock); } - private String str(String str) { - return "\"" + str + "\""; + public static Statement If(String expression, Op op, String value) { + return new Statement(Keyword.IF, expression, op, value, null); } - public static Statement If(String expression, Op op, String value) { - return new Statement(Keyword.IF, expression, op, value); + public static Statement ElseIf(String expression, List doBlock) { + return new Statement(Keyword.ELSEIF, expression, Op.DO, null, doBlock); } public static Statement ElseIf(String expression, Op op, String value) { - return new Statement(Keyword.ELSEIF, expression, op, value); + return new Statement(Keyword.ELSEIF, expression, op, value, null); + } + + public static Statement Else(List doBlock) { + return new Statement(Keyword.ELSE, "", Op.DO, null, doBlock); } public static Statement Else(Op op, String value) { - return new Statement(Keyword.ELSE, null, op, value); + return new Statement(Keyword.ELSE, "", op, value, null); } } diff --git a/web-api/src/main/java/com/graphhopper/util/CustomModel.java b/web-api/src/main/java/com/graphhopper/util/CustomModel.java index 843cf94e933..b5c36751862 100644 --- a/web-api/src/main/java/com/graphhopper/util/CustomModel.java +++ b/web-api/src/main/java/com/graphhopper/util/CustomModel.java @@ -17,11 +17,14 @@ */ package com.graphhopper.util; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.graphhopper.jackson.CustomModelAreasDeserializer; import com.graphhopper.json.Statement; import java.util.*; +import java.util.function.Function; import java.util.stream.Collectors; public class CustomModel { @@ -31,32 +34,36 @@ public class CustomModel { // 'Double' instead of 'double' is required to know if it was 0 or not specified in the request. private Double distanceInfluence; private Double headingPenalty; + @JsonIgnore private boolean internal; private List speedStatements = new ArrayList<>(); private List priorityStatements = new ArrayList<>(); + private List turnPenaltyStatements = new ArrayList<>(); + private JsonFeatureCollection areas = new JsonFeatureCollection(); public CustomModel() { } public CustomModel(CustomModel toCopy) { + this.internal = false; // true only when explicitly set this.headingPenalty = toCopy.headingPenalty; this.distanceInfluence = toCopy.distanceInfluence; // do not copy "internal" boolean speedStatements = deepCopy(toCopy.getSpeed()); priorityStatements = deepCopy(toCopy.getPriority()); + turnPenaltyStatements = deepCopy(toCopy.getTurnPenalty()); addAreas(toCopy.getAreas()); } public static Map getAreasAsMap(JsonFeatureCollection areas) { - Map map = new HashMap<>(areas.getFeatures().size()); - areas.getFeatures().forEach(f -> { - if (map.put(f.getId(), f) != null) - throw new IllegalArgumentException("Cannot handle duplicate area " + f.getId()); - }); - return map; + return areas.getFeatures().stream().collect(Collectors.toMap(JsonFeature::getId, + Function.identity(), (existing, duplicate) -> { + throw new IllegalArgumentException("Cannot handle duplicate area " + duplicate.getId()); + } + )); } public void addAreas(JsonFeatureCollection externalAreas) { @@ -123,6 +130,16 @@ public CustomModel addToPriority(Statement st) { return this; } + @JsonProperty("turn_penalty") + public List getTurnPenalty() { + return turnPenaltyStatements; + } + + public CustomModel addToTurnPenalty(Statement st) { + getTurnPenalty().add(st); + return this; + } + @JsonDeserialize(using = CustomModelAreasDeserializer.class) public CustomModel setAreas(JsonFeatureCollection areas) { this.areas = areas; @@ -159,7 +176,9 @@ public String toString() { private String createContentString() { // used to check against stored custom models, see #2026 return "distanceInfluence=" + distanceInfluence + "|headingPenalty=" + headingPenalty - + "|speedStatements=" + speedStatements + "|priorityStatements=" + priorityStatements + "|areas=" + areas; + + "|speedStatements=" + speedStatements + "|priorityStatements=" + priorityStatements + + "|turnPenaltyStatements=" + turnPenaltyStatements + + "|areas=" + areas; } /** @@ -178,8 +197,9 @@ public static CustomModel merge(CustomModel baseModel, CustomModel queryModel) { mergedCM.headingPenalty = queryModel.headingPenalty; mergedCM.speedStatements.addAll(queryModel.getSpeed()); mergedCM.priorityStatements.addAll(queryModel.getPriority()); - mergedCM.addAreas(queryModel.getAreas()); + mergedCM.turnPenaltyStatements.addAll(queryModel.getTurnPenalty()); + mergedCM.addAreas(queryModel.getAreas()); return mergedCM; } } diff --git a/web-api/src/main/java/com/graphhopper/util/Helper.java b/web-api/src/main/java/com/graphhopper/util/Helper.java index ed91970b7a8..4e00a4f9bdb 100644 --- a/web-api/src/main/java/com/graphhopper/util/Helper.java +++ b/web-api/src/main/java/com/graphhopper/util/Helper.java @@ -26,7 +26,6 @@ import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.*; -import java.util.Map.Entry; /** * @author Peter Karich @@ -37,8 +36,13 @@ public class Helper { public static final long MB = 1L << 20; // we keep the first seven decimal places of lat/lon coordinates. this corresponds to ~1cm precision ('pointing to waldo on a page') private static final float DEGREE_FACTOR = 10_000_000; - // milli meter is a bit extreme but we have integers + /** + * Marker value indicating that elevation has not been looked up yet (deferred elevation). + */ + public static final double ELE_UNKNOWN = Double.MAX_VALUE; + // milli meter is a bit extreme, but we have 3 bytes private static final float ELE_FACTOR = 1000f; + private static final int MAX_ELE_UINT = (int) ((10_000 + 1000) * ELE_FACTOR); private Helper() { } @@ -64,20 +68,6 @@ public static String toUpperCase(String string) { return string.toUpperCase(Locale.ROOT); } - public static void saveProperties(Map map, Writer tmpWriter) throws IOException { - BufferedWriter writer = new BufferedWriter(tmpWriter); - try { - for (Entry e : map.entrySet()) { - writer.append(e.getKey()); - writer.append('='); - writer.append(e.getValue()); - writer.append('\n'); - } - } finally { - writer.close(); - } - } - public static String readJSONFileWithoutComments(String file) throws IOException { return Helper.readFile(file).stream(). filter(line -> !line.trim().startsWith("//")). @@ -257,7 +247,7 @@ public static int degreeToInt(double deg) { return Integer.MAX_VALUE; if (deg <= -Double.MAX_VALUE) return -Integer.MAX_VALUE; - return (int) (deg * DEGREE_FACTOR); + return (int) Math.round(deg * DEGREE_FACTOR); } /** @@ -276,20 +266,21 @@ public static double intToDegree(int storedInt) { /** * Converts elevation value (in meters) into integer for storage. */ - public static int eleToInt(double ele) { - if (ele >= Integer.MAX_VALUE) - return Integer.MAX_VALUE; - return (int) (ele * ELE_FACTOR); + public static int eleToUInt(double ele) { + if (Double.isNaN(ele)) throw new IllegalArgumentException("elevation cannot be NaN"); + if (ele < -1000) return 0; + if (ele >= Integer.MAX_VALUE / ELE_FACTOR - 1000) return MAX_ELE_UINT; + return (int) Math.round((ele + 1000) * ELE_FACTOR); // enough for smallest value is -414m } /** * Converts the integer value retrieved from storage into elevation (in meters). Do not expect * more precision than meters although it currently is! */ - public static double intToEle(int integEle) { - if (integEle == Integer.MAX_VALUE) - return Double.MAX_VALUE; - return integEle / ELE_FACTOR; + public static double uIntToEle(int integEle) { + if (integEle >= MAX_ELE_UINT) + return ELE_UNKNOWN; + return integEle / ELE_FACTOR - 1000; } public static String nf(long no) { @@ -445,4 +436,5 @@ public static int staticHashCode(String str) { } return val; } + } diff --git a/web-api/src/main/java/com/graphhopper/util/Instruction.java b/web-api/src/main/java/com/graphhopper/util/Instruction.java index 1e3ad721bcf..1638c71bfd6 100644 --- a/web-api/src/main/java/com/graphhopper/util/Instruction.java +++ b/web-api/src/main/java/com/graphhopper/util/Instruction.java @@ -41,6 +41,8 @@ public class Instruction { public static final int IGNORE = Integer.MIN_VALUE; public static final int KEEP_RIGHT = 7; public static final int U_TURN_RIGHT = 8; + + public static final int FERRY = 9; public static final int PT_START_TRIP = 101; public static final int PT_TRANSFER = 102; public static final int PT_END_TRIP = 103; @@ -169,18 +171,24 @@ public String getTurnDescription(Translation tr) { String str; String streetName = _getName(); - int indi = getSign(); - if (indi == Instruction.CONTINUE_ON_STREET) { + String ferryStr = (String) extraInfo.get("ferry"); + + int sign = getSign(); + if (sign == Instruction.CONTINUE_ON_STREET) { str = Helper.isEmpty(streetName) ? tr.tr("continue") : tr.tr("continue_onto", streetName); - } else if (indi == Instruction.PT_START_TRIP) { + } else if (sign == Instruction.FERRY) { + if (ferryStr == null) + throw new RuntimeException("no ferry information provided but sign is FERRY"); + str = tr.tr(ferryStr, streetName); // pick name only + } else if (sign == Instruction.PT_START_TRIP) { str = tr.tr("pt_start_trip", streetName); - } else if (indi == Instruction.PT_TRANSFER) { + } else if (sign == Instruction.PT_TRANSFER) { str = tr.tr("pt_transfer_to", streetName); - } else if (indi == Instruction.PT_END_TRIP) { + } else if (sign == Instruction.PT_END_TRIP) { str = tr.tr("pt_end_trip", streetName); } else { String dir = null; - switch (indi) { + switch (sign) { case Instruction.U_TURN_UNKNOWN: dir = tr.tr("u_turn"); break; @@ -216,12 +224,17 @@ public String getTurnDescription(Translation tr) { break; } if (dir == null) - str = tr.tr("unknown", indi); + str = tr.tr("unknown", sign); else str = streetName.isEmpty() ? dir : tr.tr("turn_onto", dir, streetName); } + + if ("leave_ferry".equals(ferryStr)) + return tr.tr(ferryStr, str); + String dest = (String) extraInfo.get(STREET_DESTINATION); String destRef = (String) extraInfo.get(STREET_DESTINATION_REF); + if (dest != null) { if (destRef != null) return tr.tr("toward_destination_with_ref", str, destRef, dest); diff --git a/web-api/src/main/java/com/graphhopper/util/JsonFeature.java b/web-api/src/main/java/com/graphhopper/util/JsonFeature.java index 9615827656d..32cef476c8c 100644 --- a/web-api/src/main/java/com/graphhopper/util/JsonFeature.java +++ b/web-api/src/main/java/com/graphhopper/util/JsonFeature.java @@ -91,11 +91,13 @@ public void setProperties(Map properties) { @Override public String toString() { - return "id:" + getId(); + int pointCount = getGeometry() == null ? 0 : getGeometry().getCoordinates().length; + return "id:" + getId() + " with " + pointCount + " points: " + getGeometry(); } public static boolean isValidId(String name) { - if (name.length() <= 3 || !name.startsWith("in_") || SourceVersion.isKeyword(name)) return false; + if (name.length() <= 3 || !name.startsWith("in_") || SourceVersion.isKeyword(name)) + return false; int underscoreCount = 0; for (int i = 1; i < name.length(); i++) { diff --git a/web-api/src/main/java/com/graphhopper/util/PMap.java b/web-api/src/main/java/com/graphhopper/util/PMap.java index fb1ad903cd8..241864571cc 100644 --- a/web-api/src/main/java/com/graphhopper/util/PMap.java +++ b/web-api/src/main/java/com/graphhopper/util/PMap.java @@ -17,8 +17,11 @@ */ package com.graphhopper.util; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; /** * A properties map (String to Object) with convenient methods to access the content. @@ -140,6 +143,10 @@ public PMap putObject(String key, Object object) { return this; } + public static Set toSet(String value) { + return Arrays.stream(value.split(";")).filter(s -> !s.isEmpty()).collect(Collectors.toSet()); + } + /** * This method copies the underlying structure into a new Map object */ diff --git a/web-api/src/main/java/com/graphhopper/util/Parameters.java b/web-api/src/main/java/com/graphhopper/util/Parameters.java index 2755cabef29..2e4388ca12d 100644 --- a/web-api/src/main/java/com/graphhopper/util/Parameters.java +++ b/web-api/src/main/java/com/graphhopper/util/Parameters.java @@ -122,7 +122,7 @@ public static final class Routing { public static final String PASS_THROUGH = "pass_through"; public static final String POINT_HINT = "point_hint"; public static final String CURBSIDE = "curbside"; - public static final String FORCE_CURBSIDE = "force_curbside"; + public static final String CURBSIDE_STRICTNESS = "curbside_strictness"; public static final String SNAP_PREVENTION = "snap_prevention"; /** * default heading penalty in seconds @@ -138,6 +138,11 @@ public static final class Curbsides { public static final String CURBSIDE_LEFT = "left"; public static final String CURBSIDE_RIGHT = "right"; public static final String CURBSIDE_ANY = "any"; + /** + * This option automatically avoids crossing the street for bigger roads (PRIMARY, SECONDARY) + * i.e. forces 'right' for right-hand traffic. + */ + public static final String CURBSIDE_AUTO = "auto"; } /** @@ -199,6 +204,7 @@ public static final class Details { public static final String STREET_REF = "street_ref"; public static final String STREET_DESTINATION = "street_destination"; public static final String STREET_DESTINATION_REF = "street_destination_ref"; + public static final String MOTORWAY_JUNCTION = "motorway_junction"; public static final String AVERAGE_SPEED = "average_speed"; public static final String EDGE_ID = "edge_id"; @@ -206,6 +212,7 @@ public static final class Details { public static final String TIME = "time"; public static final String WEIGHT = "weight"; public static final String DISTANCE = "distance"; + public static final String CHANGE_ANGLE = "change_angle"; public static final String INTERSECTION = "intersection"; public static final String LEG_TIME = "leg_time"; diff --git a/web-api/src/main/java/com/graphhopper/util/PointList.java b/web-api/src/main/java/com/graphhopper/util/PointList.java index 1823a5ee40d..68d9ce166ed 100644 --- a/web-api/src/main/java/com/graphhopper/util/PointList.java +++ b/web-api/src/main/java/com/graphhopper/util/PointList.java @@ -401,7 +401,7 @@ public boolean equals(Object obj) { if (!equalsEps(this.getLon(i), other.getLon(i))) return false; - if (this.is3D() && !equalsEps(this.getEle(i), other.getEle(i))) + if (this.is3D() && !equalsEps(this.getEle(i), other.getEle(i), 0.01)) return false; } return true; diff --git a/web-api/src/main/java/com/graphhopper/util/RoundaboutInstruction.java b/web-api/src/main/java/com/graphhopper/util/RoundaboutInstruction.java index 486e40cec73..f953622fb14 100644 --- a/web-api/src/main/java/com/graphhopper/util/RoundaboutInstruction.java +++ b/web-api/src/main/java/com/graphhopper/util/RoundaboutInstruction.java @@ -61,9 +61,8 @@ public boolean isExited() { } public int getExitNumber() { - if (exited && exitNumber == 0) { - throw new IllegalStateException("RoundaboutInstruction must contain exitNumber>0"); - } + if (exited && exitNumber == 0) + return 1; // special case: we leave at a way without car_access return exitNumber; } diff --git a/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java b/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java new file mode 100644 index 00000000000..837267998e4 --- /dev/null +++ b/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java @@ -0,0 +1,101 @@ +package com.graphhopper.util; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +public class TurnCostsConfig { + public static final int INFINITE_U_TURN_COSTS = -1; + + private int uTurnCosts = INFINITE_U_TURN_COSTS; + private List vehicleTypes; + private boolean allowTurnPenaltyInRequest; + + // ensure that no typos can occur like motor_car vs motorcar or bike vs bicycle + private static final Set ALL_SUPPORTED = Set.of( + "agricultural", "atv", "auto_rickshaw", + "bdouble", "bicycle", "bus", "caravan", "carpool", "coach", "delivery", "destination", + "emergency", "foot", "golf_cart", "goods", "hazmat", "hgv", "hgv:trailer", "hov", + "minibus", "mofa", "moped", "motorcar", "motorcycle", "motor_vehicle", "motorhome", + "nev", "ohv", "psv", "residents", + "share_taxi", "small_electric_vehicle", "speed_pedelec", + "taxi", "trailer", "tourist_bus"); + + public static TurnCostsConfig car() { + return new TurnCostsConfig(List.of("motorcar", "motor_vehicle")); + } + + public static TurnCostsConfig bike() { + return new TurnCostsConfig(List.of("bicycle")); + } + + public TurnCostsConfig() { + } + + public TurnCostsConfig(TurnCostsConfig copy) { + uTurnCosts = copy.uTurnCosts; + if (copy.vehicleTypes != null) + vehicleTypes = new ArrayList<>(copy.vehicleTypes); + } + + public TurnCostsConfig(List vehicleTypes) { + this.vehicleTypes = check(vehicleTypes); + } + + public TurnCostsConfig(List vehicleTypes, int uTurnCost) { + this.vehicleTypes = check(vehicleTypes); + this.uTurnCosts = uTurnCost; + } + + List check(List restrictions) { + if (restrictions == null || restrictions.isEmpty()) + throw new IllegalArgumentException("turn_costs cannot have empty vehicle_types"); + for (String r : restrictions) { + if (!ALL_SUPPORTED.contains(r)) + throw new IllegalArgumentException("Currently we do not support the restriction: " + r); + } + return restrictions; + } + + public TurnCostsConfig setVehicleTypes(List vehicleTypes) { + this.vehicleTypes = check(vehicleTypes); + return this; + } + + @JsonProperty("vehicle_types") + public List getVehicleTypes() { + check(vehicleTypes); + return vehicleTypes; + } + + /** + * @param uTurnCosts the costs of an u-turn in seconds, for {@link TurnCostsConfig#INFINITE_U_TURN_COSTS} + * the u-turn costs will be infinite + */ + public TurnCostsConfig setUTurnCosts(int uTurnCosts) { + this.uTurnCosts = uTurnCosts; + return this; + } + + @JsonProperty("allow_turn_penalty_in_request") + public boolean isAllowTurnPenaltyInRequest() { + return allowTurnPenaltyInRequest; + } + + public TurnCostsConfig setAllowTurnPenaltyInRequest(boolean allowTurnPenaltyInRequest) { + this.allowTurnPenaltyInRequest = allowTurnPenaltyInRequest; + return this; + } + + @JsonProperty("u_turn_costs") + public int getUTurnCosts() { + return uTurnCosts; + } + + @Override + public String toString() { + return "uTurnCosts=" + uTurnCosts + ", vehicleTypes=" + vehicleTypes; + } +} diff --git a/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java b/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java index a301731bb2f..80b206f28ed 100644 --- a/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java +++ b/web-api/src/main/java/com/graphhopper/util/shapes/GHPoint3D.java @@ -53,7 +53,7 @@ public boolean equals(Object obj) { return NumHelper.equalsEps(lat, other.lat) && NumHelper.equalsEps(lon, other.lon); else return NumHelper.equalsEps(lat, other.lat) && NumHelper.equalsEps(lon, other.lon) - && NumHelper.equalsEps(ele, other.ele); + && NumHelper.equalsEps(ele, other.ele, 0.01); } @Override diff --git a/web-api/src/test/java/com/graphhopper/jackson/PathDetailDeserializerTest.java b/web-api/src/test/java/com/graphhopper/jackson/PathDetailDeserializerTest.java index 7cf953db2b1..a61b8173de9 100644 --- a/web-api/src/test/java/com/graphhopper/jackson/PathDetailDeserializerTest.java +++ b/web-api/src/test/java/com/graphhopper/jackson/PathDetailDeserializerTest.java @@ -1,20 +1,19 @@ package com.graphhopper.jackson; -import static org.junit.jupiter.api.Assertions.assertNotNull; - -import org.junit.jupiter.api.Test; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.graphhopper.GHResponse; +import com.graphhopper.ResponsePath; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNull; class PathDetailDeserializerTest { - @Test - public void else_null_detail_average_speed() throws JsonProcessingException { - final ObjectMapper objectMapper = Jackson.newObjectMapper(); - final String result = "{\"hints\":{\"visited_nodes.sum\":90,\"visited_nodes.average\":90},\"info\":{\"copyrights\":[\"GraphHopper\",\"OpenStreetMap contributors\"],\"took\":1},\"paths\":[{\"distance\":9863.287,\"weight\":888.630785,\"time\":740658,\"transfers\":0,\"points_encoded\":false,\"bbox\":[-58.476658,-34.650384,-58.429018,-34.628717],\"points\":{\"type\":\"LineString\",\"coordinates\":[[-58.465985,-34.650384],[-58.465985,-34.650384],[-58.46555,-34.649904],[-58.465416,-34.649686],[-58.464935,-34.649033],[-58.464199,-34.648446],[-58.464014,-34.648262],[-58.463675,-34.647783],[-58.462575,-34.645549],[-58.462426,-34.645311],[-58.462031,-34.644079],[-58.461681,-34.643299],[-58.461561,-34.643099],[-58.461258,-34.642675],[-58.460623,-34.642013],[-58.460372,-34.641799],[-58.458082,-34.640102],[-58.456072,-34.638467],[-58.45555,-34.638104],[-58.453825,-34.637045],[-58.453518,-34.636894],[-58.453287,-34.636799],[-58.452854,-34.636647],[-58.452399,-34.636541],[-58.451783,-34.636477],[-58.451437,-34.636461],[-58.45096,-34.636455],[-58.449346,-34.636492],[-58.448718,-34.636475],[-58.448065,-34.636381],[-58.447576,-34.636252],[-58.447071,-34.636088],[-58.446624,-34.635893],[-58.445336,-34.635105],[-58.443767,-34.633999],[-58.441687,-34.632678],[-58.440299,-34.631866],[-58.439234,-34.631214],[-58.43877,-34.630985],[-58.437805,-34.630657],[-58.436974,-34.630468],[-58.434133,-34.630075],[-58.432415,-34.629923],[-58.430902,-34.629722],[-58.430437,-34.62966],[-58.430367,-34.629977],[-58.429018,-34.629782],[-58.429325,-34.628717],[-58.430635,-34.628917],[-58.430561,-34.62922],[-58.432879,-34.629618],[-58.434549,-34.629933],[-58.436946,-34.630326],[-58.437878,-34.630532],[-58.438762,-34.630822],[-58.439319,-34.63109],[-58.440885,-34.632045],[-58.442483,-34.632958],[-58.443484,-34.633603],[-58.445463,-34.634973],[-58.446519,-34.635643],[-58.446771,-34.635791],[-58.447069,-34.635926],[-58.447557,-34.636096],[-58.448078,-34.636254],[-58.44873,-34.636348],[-58.451367,-34.636297],[-58.452221,-34.636358],[-58.452712,-34.636448],[-58.453267,-34.636623],[-58.453552,-34.63673],[-58.453854,-34.636872],[-58.454214,-34.637075],[-58.455065,-34.637593],[-58.456306,-34.638427],[-58.457424,-34.639367],[-58.45823,-34.640018],[-58.460244,-34.641438],[-58.460783,-34.641844],[-58.461038,-34.642077],[-58.461295,-34.642353],[-58.461474,-34.64258],[-58.461901,-34.643196],[-58.462073,-34.643528],[-58.462269,-34.644017],[-58.462687,-34.645211],[-58.463101,-34.645982],[-58.463506,-34.646562],[-58.463782,-34.646898],[-58.464636,-34.647832],[-58.465174,-34.648395],[-58.4654,-34.648587],[-58.465807,-34.648882],[-58.466103,-34.649053],[-58.466435,-34.6492],[-58.466725,-34.649293],[-58.467056,-34.649351],[-58.467634,-34.649388],[-58.469992,-34.64935],[-58.47232,-34.64918],[-58.473264,-34.649091],[-58.474569,-34.648922],[-58.475036,-34.648843],[-58.475346,-34.648771],[-58.475799,-34.648596],[-58.476658,-34.648084]]},\"instructions\":[{\"distance\":3923.699,\"heading\":90,\"sign\":0,\"interval\":[0,41],\"text\":\"Continue na Autopista Teniente General Luis Dellepiane\",\"time\":228978,\"street_name\":\"Autopista Teniente General Luis Dellepiane\"},{\"distance\":341.449,\"sign\":7,\"interval\":[41,44],\"text\":\"Mantenha-se à direita\",\"time\":52395,\"street_name\":\"\"},{\"distance\":35.813,\"sign\":2,\"interval\":[44,45],\"text\":\"Vire à direita na Viel\",\"time\":7162,\"street_name\":\"Viel\"},{\"distance\":125.343,\"sign\":-2,\"interval\":[45,46],\"text\":\"Vire à esquerda na Tejedor\",\"time\":25068,\"street_name\":\"Tejedor\"},{\"distance\":121.839,\"sign\":-2,\"interval\":[46,47],\"text\":\"Vire à esquerda na Doblas\",\"time\":31329,\"street_name\":\"Doblas\"},{\"distance\":121.976,\"sign\":-2,\"interval\":[47,48],\"text\":\"Vire à esquerda na Zuviría\",\"time\":24395,\"street_name\":\"Zuviría\"},{\"distance\":34.367,\"sign\":-2,\"interval\":[48,49],\"text\":\"Vire à esquerda na Viel\",\"time\":6873,\"street_name\":\"Viel\"},{\"distance\":3623.263,\"sign\":2,\"interval\":[49,85],\"text\":\"Vire à direita\",\"time\":234503,\"street_name\":\"\"},{\"distance\":1535.538,\"sign\":7,\"interval\":[85,105],\"text\":\"Mantenha-se à direita\",\"time\":129955,\"street_name\":\"\"},{\"distance\":0,\"sign\":4,\"last_heading\":305.9709217557622,\"interval\":[105,105],\"text\":\"Destino alcançado!\",\"time\":0,\"street_name\":\"\"}],\"legs\":[],\"details\":{\"average_speed\":[[0,1,null],[1,2,16],[2,7,78],[7,41,64],[41,43,26],[43,44,14],[44,46,18],[46,47,14],[47,49,18],[49,51,26],[51,85,64],[85,96,40],[96,103,48],[103,105,32]]},\"ascend\":40.208499908447266,\"descend\":36.19799995422363,\"snapped_waypoints\":{\"type\":\"LineString\",\"coordinates\":[[-58.465985,-34.650384],[-58.476658,-34.648084]]}}]}"; - final GHResponse ghResponse = objectMapper.readValue(result, GHResponse.class); - assertNotNull(ghResponse); - } + @Test + public void else_null_detail_average_speed() throws JsonProcessingException { + final ObjectMapper objectMapper = Jackson.newObjectMapper(); + final String result = "{\"distance\":9863.287,\"weight\":888.630785,\"time\":740658,\"transfers\":0,\"points_encoded\":false,\"bbox\":[-58.476658,-34.650384,-58.429018,-34.628717],\"points\":{\"type\":\"LineString\",\"coordinates\":[[-58.465985,-34.650384],[-58.465985,-34.650384],[-58.46555,-34.649904],[-58.465416,-34.649686],[-58.464935,-34.649033],[-58.464199,-34.648446],[-58.464014,-34.648262],[-58.463675,-34.647783],[-58.462575,-34.645549],[-58.462426,-34.645311],[-58.462031,-34.644079],[-58.461681,-34.643299],[-58.461561,-34.643099],[-58.461258,-34.642675],[-58.460623,-34.642013],[-58.460372,-34.641799],[-58.458082,-34.640102],[-58.456072,-34.638467],[-58.45555,-34.638104],[-58.453825,-34.637045],[-58.453518,-34.636894],[-58.453287,-34.636799],[-58.452854,-34.636647],[-58.452399,-34.636541],[-58.451783,-34.636477],[-58.451437,-34.636461],[-58.45096,-34.636455],[-58.449346,-34.636492],[-58.448718,-34.636475],[-58.448065,-34.636381],[-58.447576,-34.636252],[-58.447071,-34.636088],[-58.446624,-34.635893],[-58.445336,-34.635105],[-58.443767,-34.633999],[-58.441687,-34.632678],[-58.440299,-34.631866],[-58.439234,-34.631214],[-58.43877,-34.630985],[-58.437805,-34.630657],[-58.436974,-34.630468],[-58.434133,-34.630075],[-58.432415,-34.629923],[-58.430902,-34.629722],[-58.430437,-34.62966],[-58.430367,-34.629977],[-58.429018,-34.629782],[-58.429325,-34.628717],[-58.430635,-34.628917],[-58.430561,-34.62922],[-58.432879,-34.629618],[-58.434549,-34.629933],[-58.436946,-34.630326],[-58.437878,-34.630532],[-58.438762,-34.630822],[-58.439319,-34.63109],[-58.440885,-34.632045],[-58.442483,-34.632958],[-58.443484,-34.633603],[-58.445463,-34.634973],[-58.446519,-34.635643],[-58.446771,-34.635791],[-58.447069,-34.635926],[-58.447557,-34.636096],[-58.448078,-34.636254],[-58.44873,-34.636348],[-58.451367,-34.636297],[-58.452221,-34.636358],[-58.452712,-34.636448],[-58.453267,-34.636623],[-58.453552,-34.63673],[-58.453854,-34.636872],[-58.454214,-34.637075],[-58.455065,-34.637593],[-58.456306,-34.638427],[-58.457424,-34.639367],[-58.45823,-34.640018],[-58.460244,-34.641438],[-58.460783,-34.641844],[-58.461038,-34.642077],[-58.461295,-34.642353],[-58.461474,-34.64258],[-58.461901,-34.643196],[-58.462073,-34.643528],[-58.462269,-34.644017],[-58.462687,-34.645211],[-58.463101,-34.645982],[-58.463506,-34.646562],[-58.463782,-34.646898],[-58.464636,-34.647832],[-58.465174,-34.648395],[-58.4654,-34.648587],[-58.465807,-34.648882],[-58.466103,-34.649053],[-58.466435,-34.6492],[-58.466725,-34.649293],[-58.467056,-34.649351],[-58.467634,-34.649388],[-58.469992,-34.64935],[-58.47232,-34.64918],[-58.473264,-34.649091],[-58.474569,-34.648922],[-58.475036,-34.648843],[-58.475346,-34.648771],[-58.475799,-34.648596],[-58.476658,-34.648084]]},\"instructions\":[{\"distance\":3923.699,\"heading\":90,\"sign\":0,\"interval\":[0,41],\"text\":\"Continue na Autopista Teniente General Luis Dellepiane\",\"time\":228978,\"street_name\":\"Autopista Teniente General Luis Dellepiane\"},{\"distance\":341.449,\"sign\":7,\"interval\":[41,44],\"text\":\"Mantenha-se à direita\",\"time\":52395,\"street_name\":\"\"},{\"distance\":35.813,\"sign\":2,\"interval\":[44,45],\"text\":\"Vire à direita na Viel\",\"time\":7162,\"street_name\":\"Viel\"},{\"distance\":125.343,\"sign\":-2,\"interval\":[45,46],\"text\":\"Vire à esquerda na Tejedor\",\"time\":25068,\"street_name\":\"Tejedor\"},{\"distance\":121.839,\"sign\":-2,\"interval\":[46,47],\"text\":\"Vire à esquerda na Doblas\",\"time\":31329,\"street_name\":\"Doblas\"},{\"distance\":121.976,\"sign\":-2,\"interval\":[47,48],\"text\":\"Vire à esquerda na Zuviría\",\"time\":24395,\"street_name\":\"Zuviría\"},{\"distance\":34.367,\"sign\":-2,\"interval\":[48,49],\"text\":\"Vire à esquerda na Viel\",\"time\":6873,\"street_name\":\"Viel\"},{\"distance\":3623.263,\"sign\":2,\"interval\":[49,85],\"text\":\"Vire à direita\",\"time\":234503,\"street_name\":\"\"},{\"distance\":1535.538,\"sign\":7,\"interval\":[85,105],\"text\":\"Mantenha-se à direita\",\"time\":129955,\"street_name\":\"\"},{\"distance\":0,\"sign\":4,\"last_heading\":305.9709217557622,\"interval\":[105,105],\"text\":\"Destino alcançado!\",\"time\":0,\"street_name\":\"\"}],\"legs\":[],\"details\":{\"average_speed\":[[0,1,null],[1,2,16],[2,7,78],[7,41,64],[41,43,26],[43,44,14],[44,46,18],[46,47,14],[47,49,18],[49,51,26],[51,85,64],[85,96,40],[96,103,48],[103,105,32]]}}"; + final ResponsePath ghResponse = ResponsePathDeserializerHelper.createResponsePath(objectMapper, objectMapper.readTree(result), false, false); + assertNull(ghResponse.getPathDetails().get("average_speed").get(0).getValue()); + } } diff --git a/web-api/src/test/java/com/graphhopper/jackson/ResponsePathRepresentationTest.java b/web-api/src/test/java/com/graphhopper/jackson/ResponsePathRepresentationTest.java index 5201f50f606..a73b76f39b5 100644 --- a/web-api/src/test/java/com/graphhopper/jackson/ResponsePathRepresentationTest.java +++ b/web-api/src/test/java/com/graphhopper/jackson/ResponsePathRepresentationTest.java @@ -29,10 +29,10 @@ public class ResponsePathRepresentationTest { @Test public void testDecode() { - PointList list = ResponsePathDeserializer.decodePolyline("_p~iF~ps|U", 1, false); + PointList list = ResponsePathDeserializerHelper.decodePolyline("_p~iF~ps|U", 1, false, 1e5); assertEquals(Helper.createPointList(38.5, -120.2), list); - list = ResponsePathDeserializer.decodePolyline("_p~iF~ps|U_ulLnnqC_mqNvxq`@", 3, false); + list = ResponsePathDeserializerHelper.decodePolyline("_p~iF~ps|U_ulLnnqC_mqNvxq`@", 3, false, 1e5); assertEquals(Helper.createPointList(38.5, -120.2, 40.7, -120.95, 43.252, -126.453), list); } @@ -50,20 +50,20 @@ public void testBoth() { PointList list = Helper.createPointList(38.5, -120.2, 43.252, -126.453, 40.7, -120.95, 50.3139, 10.61279, 50.04303, 9.49768); String str = ResponsePathSerializer.encodePolyline(list, list.is3D(), 1e5); - assertEquals(list, ResponsePathDeserializer.decodePolyline(str, list.size(), false)); + assertEquals(list, ResponsePathDeserializerHelper.decodePolyline(str, list.size(), false, 1e5)); list = Helper.createPointList(38.5, -120.2, 43.252, -126.453, 40.7, -120.95, 40.70001, -120.95001); str = ResponsePathSerializer.encodePolyline(list, list.is3D(), 1e5); - assertEquals(list, ResponsePathDeserializer.decodePolyline(str, list.size(), false)); + assertEquals(list, ResponsePathDeserializerHelper.decodePolyline(str, list.size(), false, 1e5)); } @Test public void testDecode3D() { - PointList list = ResponsePathDeserializer.decodePolyline("_p~iF~ps|Uo}@", 1, true); + PointList list = ResponsePathDeserializerHelper.decodePolyline("_p~iF~ps|Uo}@", 1, true, 1e5); assertEquals(Helper.createPointList3D(38.5, -120.2, 10), list); - list = ResponsePathDeserializer.decodePolyline("_p~iF~ps|Uo}@_ulLnnqC_anF_mqNvxq`@?", 3, true); + list = ResponsePathDeserializerHelper.decodePolyline("_p~iF~ps|Uo}@_ulLnnqC_anF_mqNvxq`@?", 3, true, 1e5); assertEquals(Helper.createPointList3D(38.5, -120.2, 10, 40.7, -120.95, 1234, 43.252, -126.453, 1234), list); } diff --git a/web-api/src/test/java/com/graphhopper/jackson/RouteResourceRepresentationTest.java b/web-api/src/test/java/com/graphhopper/jackson/RouteResourceRepresentationTest.java index 12869f9bfba..e7aec0e154f 100644 --- a/web-api/src/test/java/com/graphhopper/jackson/RouteResourceRepresentationTest.java +++ b/web-api/src/test/java/com/graphhopper/jackson/RouteResourceRepresentationTest.java @@ -34,7 +34,7 @@ public void testUnknownInstructionSign() throws IOException { // Modified the sign though ObjectMapper objectMapper = Jackson.newObjectMapper(); JsonNode json = objectMapper.readTree("{\"instructions\":[{\"distance\":1.073,\"sign\":741,\"interval\":[0,1],\"text\":\"Continue onto A 81\",\"time\":32,\"street_name\":\"A 81\"},{\"distance\":0,\"sign\":4,\"interval\":[1,1],\"text\":\"Finish!\",\"time\":0,\"street_name\":\"\"}],\"descend\":0,\"ascend\":0,\"distance\":1.073,\"bbox\":[8.676286,48.354446,8.676297,48.354453],\"weight\":0.032179,\"time\":32,\"points_encoded\":true,\"points\":\"gfcfHwq}s@}c~AAA?\",\"snapped_waypoints\":\"gfcfHwq}s@}c~AAA?\"}"); - ResponsePath responsePath = ResponsePathDeserializer.createResponsePath(objectMapper, json, true, true); + ResponsePath responsePath = ResponsePathDeserializerHelper.createResponsePath(objectMapper, json, true, true); assertEquals(741, responsePath.getInstructions().get(0).getSign()); assertEquals("Continue onto A 81", responsePath.getInstructions().get(0).getName()); diff --git a/web-api/src/test/java/com/graphhopper/jackson/StatementDeserializerTest.java b/web-api/src/test/java/com/graphhopper/jackson/StatementDeserializerTest.java index ef930cca70a..48096608b6b 100644 --- a/web-api/src/test/java/com/graphhopper/jackson/StatementDeserializerTest.java +++ b/web-api/src/test/java/com/graphhopper/jackson/StatementDeserializerTest.java @@ -22,39 +22,54 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.graphhopper.json.Statement; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; class StatementDeserializerTest { - @Test - void conditionAsBoolean_expressionAsNumber() throws JsonProcessingException { + ObjectMapper mapper; + + @BeforeEach + public void setup() { SimpleModule module = new SimpleModule(); module.addDeserializer(Statement.class, new StatementDeserializer()); - ObjectMapper objectMapper = new ObjectMapper().registerModule(module); + mapper = new ObjectMapper().registerModule(module); + } + + @Test + void conditionAsBoolean_expressionAsNumber() throws JsonProcessingException { // true instead of "true" or 100 instead of "100" also work because they are parsed to strings // We probably need to accept numbers instead of strings for legacy support, but maybe we should reject true/false - Statement statement = objectMapper.readValue("{\"if\":true,\"limit_to\":100}", Statement.class); - assertEquals(Statement.Keyword.IF, statement.getKeyword()); - assertEquals("true", statement.getCondition()); - assertEquals(Statement.Op.LIMIT, statement.getOperation()); - assertEquals("100", statement.getValue()); + Statement statement = mapper.readValue("{\"if\":true,\"limit_to\":100}", Statement.class); + assertEquals(Statement.Keyword.IF, statement.keyword()); + assertEquals("true", statement.condition()); + assertEquals(Statement.Op.LIMIT, statement.operation()); + assertEquals("100", statement.value()); } @Test void else_null() throws JsonProcessingException { - SimpleModule module = new SimpleModule(); - module.addDeserializer(Statement.class, new StatementDeserializer()); - ObjectMapper objectMapper = new ObjectMapper().registerModule(module); // There is no error for `"else": null` currently, even though there is no real reason to support this. // The value will actually be null, but the way we use it at the moment this is not a problem. - Statement statement = objectMapper.readValue("{\"else\":null,\"limit_to\":\"abc\"}", Statement.class); - assertEquals(Statement.Keyword.ELSE, statement.getKeyword()); - assertNull(statement.getCondition()); - assertEquals(Statement.Op.LIMIT, statement.getOperation()); - assertEquals("abc", statement.getValue()); + Statement statement = mapper.readValue("{\"else\":null,\"limit_to\":\"abc\"}", Statement.class); + assertEquals(Statement.Keyword.ELSE, statement.keyword()); + assertTrue(statement.condition().isEmpty()); + assertEquals(Statement.Op.LIMIT, statement.operation()); + assertEquals("abc", statement.value()); } -} \ No newline at end of file + @Test + void block() throws JsonProcessingException { + Statement statement = mapper.readValue("{\"if\":\"country == DEU\"," + + "\"do\": [{ \"if\":\"road_class == PRIMARY\", \"limit_to\": \"123\" }] }", Statement.class); + assertEquals(Statement.Keyword.IF, statement.keyword()); + assertEquals(Statement.Op.DO, statement.operation()); + assertEquals("country == DEU", statement.condition()); + assertTrue(statement.isBlock()); + assertEquals(1, statement.doBlock().size()); + assertEquals("road_class == PRIMARY", statement.doBlock().get(0).condition()); + } +} diff --git a/web-api/src/test/java/com/graphhopper/util/CustomModelTest.java b/web-api/src/test/java/com/graphhopper/util/CustomModelTest.java index 89269202821..a5786a069c6 100644 --- a/web-api/src/test/java/com/graphhopper/util/CustomModelTest.java +++ b/web-api/src/test/java/com/graphhopper/util/CustomModelTest.java @@ -23,8 +23,7 @@ import java.util.Iterator; -import static com.graphhopper.json.Statement.ElseIf; -import static com.graphhopper.json.Statement.If; +import static com.graphhopper.json.Statement.*; import static com.graphhopper.json.Statement.Op.MULTIPLY; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -65,11 +64,11 @@ public void testMergeEmptyModel() { car.addToPriority(ElseIf("road_class==tertiary", MULTIPLY, "0.8")); Iterator iter = CustomModel.merge(emptyCar, car).getPriority().iterator(); - assertEquals("0.5", iter.next().getValue()); - assertEquals("0.8", iter.next().getValue()); + assertEquals("0.5", iter.next().value()); + assertEquals("0.8", iter.next().value()); iter = CustomModel.merge(car, emptyCar).getPriority().iterator(); - assertEquals("0.5", iter.next().getValue()); - assertEquals("0.8", iter.next().getValue()); + assertEquals("0.5", iter.next().value()); + assertEquals("0.8", iter.next().value()); } -} \ No newline at end of file +} diff --git a/web-api/src/test/java/com/graphhopper/util/HelperTest.java b/web-api/src/test/java/com/graphhopper/util/HelperTest.java index 408116fc531..2e3b0ddcaa9 100644 --- a/web-api/src/test/java/com/graphhopper/util/HelperTest.java +++ b/web-api/src/test/java/com/graphhopper/util/HelperTest.java @@ -19,18 +19,32 @@ import org.junit.jupiter.api.Test; -import java.util.Arrays; import java.util.Locale; import static com.graphhopper.util.Helper.UTF_CS; -import static org.junit.jupiter.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * @author Peter Karich */ public class HelperTest { + @Test + public void testElevation() { + assertEquals(9034.1, Helper.uIntToEle(Helper.eleToUInt(9034.1)), .1); + assertEquals(1234.5, Helper.uIntToEle(Helper.eleToUInt(1234.5)), .1); + assertEquals(0, Helper.uIntToEle(Helper.eleToUInt(0)), .1); + assertEquals(-432.3, Helper.uIntToEle(Helper.eleToUInt(-432.3)), .1); + + assertEquals(Helper.ELE_UNKNOWN, Helper.uIntToEle(Helper.eleToUInt(11_000))); + assertEquals(Helper.ELE_UNKNOWN, Helper.uIntToEle(Helper.eleToUInt(Helper.ELE_UNKNOWN))); + + assertEquals(0, Helper.eleToUInt(-1100)); + + assertThrows(IllegalArgumentException.class, () -> Helper.eleToUInt(Double.NaN)); + } + @Test public void testGetLocale() { assertEquals(Locale.GERMAN, Helper.getLocale("de")); @@ -92,4 +106,22 @@ public void testIssue2609() { bytes[0] = -25; assertEquals(3, new String(bytes, 0, 1, UTF_CS).getBytes(UTF_CS).length); } + + @Test + void degreeToInt() { + int storedInt = 444_494_395; + double lat = Helper.intToDegree(storedInt); + assertEquals(44.4494395, lat); + assertEquals(storedInt, Helper.degreeToInt(lat)); + } + + @Test + void eleToInt() { + int storedInt = 1145636; + double ele = Helper.uIntToEle(storedInt); + // converting to double is imprecise + assertEquals(145.635986, ele, 1.e-6); + // ... but converting back to int should yield the same value we started with! + assertEquals(storedInt, Helper.eleToUInt(ele)); + } } diff --git a/web-bundle/pom.xml b/web-bundle/pom.xml index 5a830f680bf..5a5c7d5043b 100644 --- a/web-bundle/pom.xml +++ b/web-bundle/pom.xml @@ -5,9 +5,10 @@ 4.0.0 graphhopper-web-bundle jar - 9.0-SNAPSHOT + 12.0-SNAPSHOT - 0.0.0-56cdfc78acf88b477457f8846c8715058b5375db + 0.0.0-a156c15 + GraphHopper Dropwizard Bundle Use the GraphHopper routing engine as a web-service @@ -15,7 +16,7 @@ com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT @@ -40,17 +41,6 @@ ${project.parent.version} - - - javax.xml.ws - jaxws-api - 2.3.1 - - - - com.google.guava - guava - io.dropwizard dropwizard-core @@ -68,11 +58,10 @@ org.locationtech.jts jts-core - 1.19.0 - com.fasterxml.jackson.jaxrs - jackson-jaxrs-xml-provider + com.fasterxml.jackson.jakarta.rs + jackson-jakarta-rs-xml-provider @@ -140,8 +129,8 @@ install-node-and-npm - v16.17.0 - 8.15.0 + v20.14.0 + 10.7.0 diff --git a/web-bundle/src/main/java/com/graphhopper/http/CORSFilter.java b/web-bundle/src/main/java/com/graphhopper/http/CORSFilter.java index 702b10c94c9..97b55bfb45f 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/CORSFilter.java +++ b/web-bundle/src/main/java/com/graphhopper/http/CORSFilter.java @@ -17,8 +17,8 @@ */ package com.graphhopper.http; -import javax.servlet.*; -import javax.servlet.http.HttpServletResponse; +import jakarta.servlet.*; +import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; /** diff --git a/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java b/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java index 2b26499d1f6..51640857f3a 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java @@ -20,20 +20,19 @@ import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; import java.time.Duration; public class DurationParam extends AbstractParam { - public DurationParam(@Nullable String input) { + public DurationParam(String input) { super(input); } - public DurationParam(@Nullable String input, String parameterName) { + public DurationParam(String input, String parameterName) { super(input, parameterName); } @Override - protected Duration parse(@Nullable String input) throws Exception { + protected Duration parse(String input) throws Exception { if (input == null) return null; return Duration.parse(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/FeedConfiguration.java b/web-bundle/src/main/java/com/graphhopper/http/FeedConfiguration.java index 5f859fc9aa6..011e38f1972 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/FeedConfiguration.java +++ b/web-bundle/src/main/java/com/graphhopper/http/FeedConfiguration.java @@ -19,9 +19,7 @@ package com.graphhopper.http; import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.transit.realtime.GtfsRealtime; -import java.io.IOException; import java.net.URL; public class FeedConfiguration { diff --git a/web-bundle/src/main/java/com/graphhopper/http/GHJerseyViolationExceptionMapper.java b/web-bundle/src/main/java/com/graphhopper/http/GHJerseyViolationExceptionMapper.java index 1a2fef6b886..ec4f535ca0e 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GHJerseyViolationExceptionMapper.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GHJerseyViolationExceptionMapper.java @@ -25,11 +25,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.validation.ConstraintViolation; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; +import jakarta.validation.ConstraintViolation; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; import java.util.List; import java.util.Set; import java.util.stream.Collectors; diff --git a/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java b/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java index 29b11022978..8cfdfcdda9a 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java @@ -21,20 +21,18 @@ import com.graphhopper.gtfs.GHLocation; import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; - public class GHLocationParam extends AbstractParam { - public GHLocationParam(@Nullable String input) { + public GHLocationParam(String input) { super(input); } - public GHLocationParam(@Nullable String input, String parameterName) { + public GHLocationParam(String input, String parameterName) { super(input, parameterName); } @Override - protected GHLocation parse(@Nullable String input) throws Exception { + protected GHLocation parse(String input) throws Exception { if (input == null) return null; return GHLocation.fromString(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java b/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java index b0607dc047f..c3850984d53 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java @@ -21,7 +21,6 @@ import com.graphhopper.util.shapes.GHPoint; import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; /** * This is a glue type, used to plug GHPoint as a custom web resource parameter type into Dropwizard, @@ -38,16 +37,16 @@ */ public class GHPointParam extends AbstractParam { - public GHPointParam(@Nullable String input) { + public GHPointParam(String input) { super(input); } - public GHPointParam(@Nullable String input, String parameterName) { + public GHPointParam(String input, String parameterName) { super(input, parameterName); } @Override - protected GHPoint parse(@Nullable String input) throws Exception { + protected GHPoint parse(String input) { if (input == null) return null; return GHPoint.fromString(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java index 05991a1cb7c..1fdbb67513e 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java @@ -33,17 +33,18 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.index.LocationIndex; -import com.graphhopper.util.PMap; import com.graphhopper.util.TranslationMap; import com.graphhopper.util.details.PathDetailsBuilderFactory; -import io.dropwizard.ConfiguredBundle; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.client.HttpClientBuilder; +import io.dropwizard.core.ConfiguredBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import org.apache.hc.client5.http.classic.HttpClient; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.utilities.binding.AbstractBinder; -import javax.inject.Inject; - public class GraphHopperBundle implements ConfiguredBundle { static class TranslationMapFactory implements Factory { @@ -175,12 +176,7 @@ static class MapMatchingRouterFactoryFactory implements Factory MapMatching.routerFromGraphHopper(graphHopper, hints); } @Override @@ -205,6 +201,26 @@ public void dispose(Boolean instance) { } } + private static class EmptyRealtimeFeedFactory implements Factory { + + private final GtfsStorage staticGtfs; + + @Inject + EmptyRealtimeFeedFactory(GtfsStorage staticGtfs) { + this.staticGtfs = staticGtfs; + } + + @Override + public RealtimeFeed provide() { + return RealtimeFeed.empty(); + } + + @Override + public void dispose(RealtimeFeed realtimeFeed) { + + } + } + @Override public void initialize(Bootstrap bootstrap) { // See #1440: avoids warning regarding com.fasterxml.jackson.module.afterburner.util.MyClassLoader @@ -214,8 +230,10 @@ public void initialize(Bootstrap bootstrap) { Jackson.initObjectMapper(bootstrap.getObjectMapper()); bootstrap.getObjectMapper().setDateFormat(new StdDateFormat()); + + // Make snake_case work for server_log too ... still required for dropwizard 4.0.15 // See https://github.com/dropwizard/dropwizard/issues/1558 - bootstrap.getObjectMapper().enable(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING); + bootstrap.getObjectMapper().setConfig(bootstrap.getObjectMapper().getDeserializationConfig().with(MapperFeature.ALLOW_EXPLICIT_PROPERTY_RENAMING)); } @Override @@ -282,6 +300,7 @@ protected void configure() { environment.jersey().register(RouteResource.class); environment.jersey().register(IsochroneResource.class); environment.jersey().register(MapMatchingResource.class); + if (configuration.getGraphHopperConfiguration().has("gtfs.file")) { // These are pt-specific implementations of /route and /isochrone, but the same API. // We serve them under different paths (/route-pt and /isochrone-pt), and forward @@ -291,9 +310,14 @@ protected void configure() { protected void configure() { if (configuration.getGraphHopperConfiguration().getBool("gtfs.free_walk", false)) { bind(PtRouterFreeWalkImpl.class).to(PtRouter.class); + } else if (configuration.getGraphHopperConfiguration().getBool("gtfs.trip_based", false)) { + bind(PtRouterTripBasedImpl.class).to(PtRouter.class); } else { bind(PtRouterImpl.class).to(PtRouter.class); } + bind(PtRouterImpl.class).to(PtRouter.class).named("classic"); + bind(PtRouterFreeWalkImpl.class).to(PtRouter.class).named("free_walk"); + bind(PtRouterTripBasedImpl.class).to(PtRouter.class).named("trip_based"); } }); environment.jersey().register(PtRouteResource.class); @@ -307,5 +331,27 @@ protected void configure() { environment.healthChecks().register("graphhopper", new GraphHopperHealthCheck(graphHopper)); environment.jersey().register(environment.healthChecks()); environment.jersey().register(HealthCheckResource.class); + + if (configuration.gtfsrealtime().getFeeds().isEmpty()) { + environment.jersey().register(new AbstractBinder() { + @Override + protected void configure() { + bindFactory(EmptyRealtimeFeedFactory.class).to(RealtimeFeed.class).in(Singleton.class); + } + }); + } else { + final HttpClient httpClient = new HttpClientBuilder(environment) + .using(configuration.gtfsrealtime().getHttpClientConfiguration()) + .build("gtfs-realtime-feed-loader"); + RealtimeFeedLoadingCache realtimeFeedLoadingCache = new RealtimeFeedLoadingCache(((GraphHopperGtfs) graphHopper), httpClient, configuration); + environment.lifecycle().manage(realtimeFeedLoadingCache); + environment.jersey().register(new AbstractBinder() { + @Override + protected void configure() { + bind(httpClient).to(HttpClient.class); + bindFactory(realtimeFeedLoadingCache).to(RealtimeFeed.class); + } + }); + } } } diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundleConfiguration.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundleConfiguration.java index b99a1f58dbe..cbf35919950 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundleConfiguration.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundleConfiguration.java @@ -24,4 +24,6 @@ public interface GraphHopperBundleConfiguration { GraphHopperConfig getGraphHopperConfiguration(); + RealtimeConfiguration gtfsrealtime(); + } diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java index f1ec72e30f7..b3a23a093c0 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperManaged.java @@ -42,10 +42,10 @@ public GraphHopperManaged(GraphHopperConfig configuration) { @Override public void start() { graphHopper.importOrLoad(); - logger.info("loaded graph at:{}, data_reader_file:{}, encoded values:{}, {} ints for edge flags, {}", + logger.info("loaded graph at:{}, data_reader_file:{}, encoded values:{}, {} bytes for edge flags, {}", graphHopper.getGraphHopperLocation(), graphHopper.getOSMFile(), graphHopper.getEncodingManager().toEncodedValuesAsString(), - graphHopper.getEncodingManager().getIntsForFlags(), + graphHopper.getEncodingManager().getBytesForFlags(), graphHopper.getBaseGraph().toDetailsString()); } diff --git a/web-bundle/src/main/java/com/graphhopper/http/IllegalArgumentExceptionMapper.java b/web-bundle/src/main/java/com/graphhopper/http/IllegalArgumentExceptionMapper.java index 6999801d264..20f71f4d87a 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/IllegalArgumentExceptionMapper.java +++ b/web-bundle/src/main/java/com/graphhopper/http/IllegalArgumentExceptionMapper.java @@ -5,9 +5,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; @Provider public class IllegalArgumentExceptionMapper implements ExceptionMapper { diff --git a/web-bundle/src/main/java/com/graphhopper/http/JsonErrorEntity.java b/web-bundle/src/main/java/com/graphhopper/http/JsonErrorEntity.java index 0a2e4092a8c..27d01622666 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/JsonErrorEntity.java +++ b/web-bundle/src/main/java/com/graphhopper/http/JsonErrorEntity.java @@ -22,7 +22,6 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; -import com.graphhopper.util.exceptions.GHException; import java.util.List; diff --git a/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionGPXMessageBodyWriter.java b/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionGPXMessageBodyWriter.java index 9525322910f..17e99a8a89c 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionGPXMessageBodyWriter.java +++ b/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionGPXMessageBodyWriter.java @@ -22,12 +22,11 @@ import org.w3c.dom.Document; import org.w3c.dom.Element; -import javax.ws.rs.Produces; -import javax.ws.rs.WebApplicationException; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.ext.MessageBodyWriter; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.MultivaluedMap; +import jakarta.ws.rs.ext.MessageBodyWriter; +import jakarta.ws.rs.ext.Provider; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; @@ -36,7 +35,6 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; -import java.io.IOException; import java.io.OutputStream; import java.lang.annotation.Annotation; import java.lang.reflect.Type; @@ -56,7 +54,7 @@ public long getSize(MultiException e, Class type, Type genericType, Annotatio } @Override - public void writeTo(MultiException e, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { + public void writeTo(MultiException e, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) { if (e.getErrors().isEmpty()) throw new RuntimeException("errorsToXML should not be called with an empty list"); diff --git a/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionMapper.java b/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionMapper.java index 8d0d593b78a..75879e42818 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionMapper.java +++ b/web-bundle/src/main/java/com/graphhopper/http/MultiExceptionMapper.java @@ -23,9 +23,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.Response; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.ext.ExceptionMapper; +import jakarta.ws.rs.ext.Provider; @Provider public class MultiExceptionMapper implements ExceptionMapper { diff --git a/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java b/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java index 840d92de5da..2fdf9da2e65 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java @@ -20,16 +20,14 @@ import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; import java.time.OffsetDateTime; -import java.time.ZonedDateTime; public class OffsetDateTimeParam extends AbstractParam { - public OffsetDateTimeParam(@Nullable String input) { + public OffsetDateTimeParam(String input) { super(input); } - public OffsetDateTimeParam(@Nullable String input, String parameterName) { + public OffsetDateTimeParam(String input, String parameterName) { super(input, parameterName); } @@ -39,7 +37,7 @@ protected String errorMessage(Exception e) { } @Override - protected OffsetDateTime parse(@Nullable String input) throws Exception { + protected OffsetDateTime parse(String input) { if (input == null) return null; return OffsetDateTime.parse(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/PtRedirectFilter.java b/web-bundle/src/main/java/com/graphhopper/http/PtRedirectFilter.java index 729709e57a9..c538b65a7c8 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/PtRedirectFilter.java +++ b/web-bundle/src/main/java/com/graphhopper/http/PtRedirectFilter.java @@ -18,9 +18,9 @@ package com.graphhopper.http; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.PreMatching; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; import java.net.URI; @PreMatching diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java b/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java deleted file mode 100644 index 1ecfc74b9f4..00000000000 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Licensed to GraphHopper GmbH under one or more contributor - * license agreements. See the NOTICE file distributed with this work for - * additional information regarding copyright ownership. - * - * GraphHopper GmbH licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except in - * compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.graphhopper.http; - -import com.graphhopper.gtfs.GtfsStorage; -import com.graphhopper.gtfs.RealtimeFeed; -import io.dropwizard.ConfiguredBundle; -import io.dropwizard.client.HttpClientBuilder; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; -import org.apache.http.client.HttpClient; -import org.glassfish.hk2.api.Factory; -import org.glassfish.hk2.utilities.binding.AbstractBinder; - -import javax.inject.Inject; -import javax.inject.Singleton; - -public class RealtimeBundle implements ConfiguredBundle { - - @Override - public void initialize(Bootstrap bootstrap) { - } - - @Override - public void run(RealtimeBundleConfiguration configuration, Environment environment) { - if (configuration.gtfsrealtime().getFeeds().isEmpty()) { - environment.jersey().register(new AbstractBinder() { - @Override - protected void configure() { - bindFactory(EmptyRealtimeFeedFactory.class).to(RealtimeFeed.class).in(Singleton.class); - } - }); - } else { - final HttpClient httpClient = new HttpClientBuilder(environment) - .using(configuration.gtfsrealtime().getHttpClientConfiguration()) - .build("gtfs-realtime-feed-loader"); - environment.jersey().register(new AbstractBinder() { - @Override - protected void configure() { - bind(httpClient).to(HttpClient.class); - bind(configuration).to(RealtimeBundleConfiguration.class); - bindFactory(RealtimeFeedLoadingCache.class, Singleton.class).to(RealtimeFeed.class); - } - }); - } - } - - private static class EmptyRealtimeFeedFactory implements Factory { - - private final GtfsStorage staticGtfs; - - @Inject - EmptyRealtimeFeedFactory(GtfsStorage staticGtfs) { - this.staticGtfs = staticGtfs; - } - - @Override - public RealtimeFeed provide() { - return RealtimeFeed.empty(); - } - - @Override - public void dispose(RealtimeFeed realtimeFeed) { - - } - } -} diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeConfiguration.java b/web-bundle/src/main/java/com/graphhopper/http/RealtimeConfiguration.java index 01551800a43..e1521123f82 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeConfiguration.java +++ b/web-bundle/src/main/java/com/graphhopper/http/RealtimeConfiguration.java @@ -21,8 +21,8 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.dropwizard.client.HttpClientConfiguration; -import javax.validation.Valid; -import javax.validation.constraints.NotNull; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java b/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java index e563dc135f1..dd3f1c9ba56 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java +++ b/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java @@ -25,19 +25,19 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFutureTask; import com.google.transit.realtime.GtfsRealtime; -import com.graphhopper.gtfs.GtfsStorage; -import com.graphhopper.gtfs.RealtimeFeed; -import com.graphhopper.gtfs.Transfers; -import com.graphhopper.routing.util.EncodingManager; -import com.graphhopper.storage.BaseGraph; +import com.graphhopper.config.Profile; +import com.graphhopper.gtfs.*; +import com.graphhopper.routing.weighting.Weighting; +import com.graphhopper.util.PMap; import io.dropwizard.lifecycle.Managed; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpGet; import org.glassfish.hk2.api.Factory; -import javax.inject.Inject; +import jakarta.inject.Inject; import java.io.IOException; import java.net.URISyntaxException; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -48,19 +48,15 @@ public class RealtimeFeedLoadingCache implements Factory, Managed { private final HttpClient httpClient; - private final BaseGraph baseGraph; - private final EncodingManager encodingManager; - private final GtfsStorage gtfsStorage; - private final RealtimeBundleConfiguration bundleConfiguration; + private final GraphHopperGtfs graphHopper; + private final GraphHopperBundleConfiguration bundleConfiguration; private ExecutorService executor; private LoadingCache cache; private Map transfers; @Inject - RealtimeFeedLoadingCache(BaseGraph baseGraph, EncodingManager encodingManager, GtfsStorage gtfsStorage, HttpClient httpClient, RealtimeBundleConfiguration bundleConfiguration) { - this.baseGraph = baseGraph; - this.encodingManager = encodingManager; - this.gtfsStorage = gtfsStorage; + RealtimeFeedLoadingCache(GraphHopperGtfs graphHopper, HttpClient httpClient, GraphHopperBundleConfiguration bundleConfiguration) { + this.graphHopper = graphHopper; this.bundleConfiguration = bundleConfiguration; this.httpClient = httpClient; } @@ -68,7 +64,7 @@ public class RealtimeFeedLoadingCache implements Factory, Managed @Override public void start() { this.transfers = new HashMap<>(); - for (Map.Entry entry : this.gtfsStorage.getGtfsFeeds().entrySet()) { + for (Map.Entry entry : this.graphHopper.getGtfsStorage().getGtfsFeeds().entrySet()) { this.transfers.put(entry.getKey(), new Transfers(entry.getValue())); } this.executor = Executors.newSingleThreadExecutor(); @@ -112,13 +108,40 @@ private RealtimeFeed fetchFeedsAndCreateGraph() { Map feedMessageMap = new HashMap<>(); for (FeedConfiguration configuration : bundleConfiguration.gtfsrealtime().getFeeds()) { try { - GtfsRealtime.FeedMessage feedMessage = GtfsRealtime.FeedMessage.parseFrom(httpClient.execute(new HttpGet(configuration.getUrl().toURI())).getEntity().getContent()); - feedMessageMap.put(configuration.getFeedId(), feedMessage); + switch (configuration.getUrl().getProtocol()) { + case "http": { + GtfsRealtime.FeedMessage feedMessage = httpClient.execute(new HttpGet(configuration.getUrl().toURI()), + response -> GtfsRealtime.FeedMessage.parseFrom(response.getEntity().getContent())); + feedMessageMap.put(configuration.getFeedId(), feedMessage); + break; + } + case "file": { + GtfsRealtime.FeedMessage feedMessage = GtfsRealtime.FeedMessage.parseFrom(configuration.getUrl().openStream()); + feedMessageMap.put(configuration.getFeedId(), feedMessage); + break; + } + } } catch (IOException | URISyntaxException e) { throw new RuntimeException(e); } } - return RealtimeFeed.fromProtobuf(gtfsStorage, this.transfers, feedMessageMap); + return RealtimeFeed.fromProtobuf(graphHopper.getGtfsStorage(), this.transfers, feedMessageMap); + } + + private void validate(RealtimeFeed realtimeFeed) { + Profile foot = graphHopper.getProfile("foot"); + Weighting weighting = graphHopper.createWeighting(foot, new PMap(), false); + GraphExplorer graphExplorer = new GraphExplorer(graphHopper.getBaseGraph(), graphHopper.getGtfsStorage().getPtGraph(), weighting, graphHopper.getGtfsStorage(), realtimeFeed, false, false, false, 6.0, true, 0); + EnumSet edgeTypes = EnumSet.noneOf(GtfsStorage.EdgeType.class); + for (int streetNode = 0; streetNode < graphHopper.getBaseGraph().getNodes(); streetNode++) { + int ptNode = graphHopper.getGtfsStorage().getStreetToPt().getOrDefault(streetNode, -1); + if (ptNode != -1) { + for (GraphExplorer.MultiModalEdge multiModalEdge : graphExplorer.ptEdgeStream(ptNode, 0)) { + edgeTypes.add(multiModalEdge.getType()); + } + } + } + System.out.println(edgeTypes); } } diff --git a/web-bundle/src/main/java/com/graphhopper/http/TypeGPXFilter.java b/web-bundle/src/main/java/com/graphhopper/http/TypeGPXFilter.java index 7c38043c3df..bf5e827d719 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/TypeGPXFilter.java +++ b/web-bundle/src/main/java/com/graphhopper/http/TypeGPXFilter.java @@ -18,12 +18,12 @@ package com.graphhopper.http; -import javax.annotation.Priority; -import javax.ws.rs.Priorities; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerRequestFilter; -import javax.ws.rs.container.PreMatching; -import javax.ws.rs.core.HttpHeaders; +import jakarta.annotation.Priority; +import jakarta.ws.rs.Priorities; +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.container.PreMatching; +import jakarta.ws.rs.core.HttpHeaders; @PreMatching @Priority(Priorities.HEADER_DECORATOR) @@ -36,5 +36,4 @@ public void filter(ContainerRequestContext rc) { rc.getHeaders().putSingle(HttpHeaders.ACCEPT, "application/gpx+xml"); } } - } diff --git a/web-bundle/src/main/java/com/graphhopper/resources/HealthCheckResource.java b/web-bundle/src/main/java/com/graphhopper/resources/HealthCheckResource.java index 1d2bb53ed23..7009646a30e 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/HealthCheckResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/HealthCheckResource.java @@ -20,10 +20,10 @@ import com.codahale.metrics.health.HealthCheck; import com.codahale.metrics.health.HealthCheckRegistry; -import javax.inject.Inject; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.core.Response; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; import java.util.SortedMap; /** diff --git a/web-bundle/src/main/java/com/graphhopper/resources/I18NResource.java b/web-bundle/src/main/java/com/graphhopper/resources/I18NResource.java index 095f0660fff..0da97523aec 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/I18NResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/I18NResource.java @@ -21,9 +21,9 @@ import com.graphhopper.util.Translation; import com.graphhopper.util.TranslationMap; -import javax.inject.Inject; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; import java.util.Locale; import java.util.Map; diff --git a/web-bundle/src/main/java/com/graphhopper/resources/InfoResource.java b/web-bundle/src/main/java/com/graphhopper/resources/InfoResource.java index ccf5afd8faa..6ed621cc140 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/InfoResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/InfoResource.java @@ -27,12 +27,12 @@ import com.graphhopper.util.Constants; import org.locationtech.jts.geom.Envelope; -import javax.inject.Inject; -import javax.inject.Named; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; import java.util.*; /** diff --git a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java index 629dd17a77f..b0d9ec8fb2f 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/IsochroneResource.java @@ -3,13 +3,13 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.graphhopper.GraphHopper; +import com.graphhopper.GraphHopperConfig; import com.graphhopper.config.Profile; import com.graphhopper.http.GHPointParam; import com.graphhopper.http.ProfileResolver; import com.graphhopper.isochrone.algorithm.ContourBuilder; import com.graphhopper.isochrone.algorithm.ShortestPathTree; import com.graphhopper.isochrone.algorithm.Triangulator; -import com.graphhopper.jackson.ResponsePathSerializer; import com.graphhopper.routing.ev.BooleanEncodedValue; import com.graphhopper.routing.ev.Subnetwork; import com.graphhopper.routing.querygraph.QueryGraph; @@ -25,13 +25,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import java.util.ArrayList; import java.util.HashMap; import java.util.OptionalInt; @@ -48,13 +48,15 @@ public class IsochroneResource { private static final Logger logger = LoggerFactory.getLogger(IsochroneResource.class); + private final GraphHopperConfig config; private final GraphHopper graphHopper; private final Triangulator triangulator; private final ProfileResolver profileResolver; private final String osmDate; @Inject - public IsochroneResource(GraphHopper graphHopper, Triangulator triangulator, ProfileResolver profileResolver) { + public IsochroneResource(GraphHopperConfig config, GraphHopper graphHopper, Triangulator triangulator, ProfileResolver profileResolver) { + this.config = config; this.graphHopper = graphHopper; this.triangulator = triangulator; this.profileResolver = profileResolver; @@ -99,14 +101,14 @@ public Response doGet( if (!snap.isValid()) throw new IllegalArgumentException("Point not found:" + point); QueryGraph queryGraph = QueryGraph.create(graph, snap); - TraversalMode traversalMode = profile.isTurnCosts() ? EDGE_BASED : NODE_BASED; + TraversalMode traversalMode = profile.hasTurnCosts() ? EDGE_BASED : NODE_BASED; ShortestPathTree shortestPathTree = new ShortestPathTree(queryGraph, queryGraph.wrapWeighting(weighting), reverseFlow, traversalMode); double limit; ToDoubleFunction fz; if (weightLimit.orElseThrow(() -> new IllegalArgumentException("query param weight_limit is not a number.")) > 0) { limit = weightLimit.getAsLong(); - shortestPathTree.setWeightLimit(limit + Math.max(limit * 0.14, 200)); + shortestPathTree.setWeightLimit(limit + Math.max(limit * 0.14, 2000)); fz = l -> l.weight; } else if (distanceLimitInMeter.orElseThrow(() -> new IllegalArgumentException("query param distance_limit is not a number.")) > 0) { limit = distanceLimitInMeter.getAsLong(); @@ -143,7 +145,7 @@ public Response doGet( HashMap properties = new HashMap<>(); properties.put("bucket", features.size()); if (respType == geojson) { - properties.put("copyrights", ResponsePathSerializer.COPYRIGHTS); + properties.put("copyrights", config.getCopyrights()); } feature.setProperties(properties); feature.setGeometry(isochrone); @@ -160,7 +162,7 @@ public Response doGet( } else { json.putPOJO("polygons", features); final ObjectNode info = json.putObject("info"); - info.putPOJO("copyrights", ResponsePathSerializer.COPYRIGHTS); + info.putPOJO("copyrights", config.getCopyrights()); info.put("took", Math.round((float) sw.getMillis())); if (!osmDate.isEmpty()) info.put("road_data_timestamp", osmDate); finalJson = json; diff --git a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java index 2e816951887..08ec649dbda 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/MVTResource.java @@ -3,6 +3,7 @@ import com.graphhopper.GraphHopper; import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.search.KVStorage; import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.index.LocationIndexTree; import com.graphhopper.util.EdgeIteratorState; @@ -19,13 +20,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -118,9 +119,9 @@ public Response doGetXyz( edgeCounter.incrementAndGet(); Map map = new LinkedHashMap<>(); - edge.getKeyValues().forEach( - entry -> map.put(entry.key, entry.value) - ); + for (Map.Entry e : edge.getKeyValues().entrySet()) { + map.put(e.getKey(), e.getValue().toString()); + } map.put("edge_id", edge.getEdge()); map.put("edge_key", edge.getEdgeKey()); map.put("base_node", edge.getBaseNode()); @@ -133,6 +134,8 @@ else if (ev instanceof DecimalEncodedValue) map.put(ev.getName(), edge.get((DecimalEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((DecimalEncodedValue) ev) : "")); else if (ev instanceof BooleanEncodedValue) map.put(ev.getName(), edge.get((BooleanEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((BooleanEncodedValue) ev) : "")); + else if (ev instanceof StringEncodedValue) + map.put(ev.getName(), edge.get((StringEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((StringEncodedValue) ev) : "")); else if (ev instanceof IntEncodedValue) map.put(ev.getName(), edge.get((IntEncodedValue) ev) + (ev.isStoreTwoDirections() ? " | " + edge.getReverse((IntEncodedValue) ev) : "")); }); diff --git a/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java b/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java index debe35df563..6503d19c2bd 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java @@ -24,6 +24,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.graphhopper.GHResponse; import com.graphhopper.GraphHopper; +import com.graphhopper.GraphHopperConfig; import com.graphhopper.ResponsePath; import com.graphhopper.gpx.GpxConversions; import com.graphhopper.http.ProfileResolver; @@ -36,12 +37,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import java.util.*; import static com.graphhopper.resources.RouteResource.removeLegacyParameters; @@ -53,7 +55,7 @@ * * @author Peter Karich */ -@javax.ws.rs.Path("match") +@jakarta.ws.rs.Path("match") public class MapMatchingResource { public interface MapMatchingRouterFactory { @@ -62,6 +64,7 @@ public interface MapMatchingRouterFactory { private static final Logger logger = LoggerFactory.getLogger(MapMatchingResource.class); + private final GraphHopperConfig config; private final GraphHopper graphHopper; private final ProfileResolver profileResolver; private final TranslationMap trMap; @@ -70,34 +73,35 @@ public interface MapMatchingRouterFactory { private final String osmDate; @Inject - public MapMatchingResource(GraphHopper graphHopper, ProfileResolver profileResolver, TranslationMap trMap, MapMatchingRouterFactory mapMatchingRouterFactory) { + public MapMatchingResource(GraphHopperConfig config, GraphHopper graphHopper, ProfileResolver profileResolver, TranslationMap trMap, MapMatchingRouterFactory mapMatchingRouterFactory) { + this.config = config; this.graphHopper = graphHopper; this.profileResolver = profileResolver; this.trMap = trMap; this.mapMatchingRouterFactory = mapMatchingRouterFactory; - this.osmDate = graphHopper.getProperties().get("datareader.data.date"); + this.osmDate = graphHopper.getProperties().getAll().get("datareader.data.date"); } @POST @Consumes({MediaType.APPLICATION_XML, "application/gpx+xml"}) @Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, "application/gpx+xml"}) public Response match( - Gpx gpx, + @NotNull Gpx gpx, @Context UriInfo uriInfo, - @QueryParam(WAY_POINT_MAX_DISTANCE) @DefaultValue("1") double minPathPrecision, + @QueryParam(WAY_POINT_MAX_DISTANCE) @DefaultValue("0.5") double minPathPrecision, @QueryParam("type") @DefaultValue("json") String outType, @QueryParam(INSTRUCTIONS) @DefaultValue("true") boolean instructions, @QueryParam(CALC_POINTS) @DefaultValue("true") boolean calcPoints, @QueryParam("elevation") @DefaultValue("false") boolean enableElevation, @QueryParam("points_encoded") @DefaultValue("true") boolean pointsEncoded, + @QueryParam("points_encoded_multiplier") @DefaultValue("1e5") double pointsEncodedMultiplier, @QueryParam("locale") @DefaultValue("en") String localeStr, @QueryParam("profile") String profile, @QueryParam(PATH_DETAILS) List pathDetails, @QueryParam("gpx.route") @DefaultValue("true") boolean withRoute, @QueryParam("gpx.track") @DefaultValue("true") boolean withTrack, @QueryParam("traversal_keys") @DefaultValue("false") boolean enableTraversalKeys, - @QueryParam("gps_accuracy") @DefaultValue("40") double gpsAccuracy) { - + @QueryParam("gps_accuracy") @DefaultValue("10") double gpsAccuracy) { boolean writeGPX = "gpx".equalsIgnoreCase(outType); if (gpx.trk.isEmpty()) { throw new IllegalArgumentException("No tracks found in GPX document. Are you using waypoints or routes instead?"); @@ -122,8 +126,10 @@ public Response match( hints.putObject("profile", profile); removeLegacyParameters(hints); + boolean debugMode = "debug".equals(outType); MapMatching matching = new MapMatching(graphHopper.getBaseGraph(), (LocationIndexTree) graphHopper.getLocationIndex(), mapMatchingRouterFactory.createMapMatchingRouter(hints)); matching.setMeasurementErrorSigma(gpsAccuracy); + matching.setCollectDebugInfo(debugMode); List measurements = GpxConversions.getEntries(gpx.trk.get(0)); MatchResult matchResult = matching.match(measurements); @@ -135,8 +141,12 @@ public Response match( .put("observations", measurements.size()) .putPOJO("mapmatching", matching.getStatistics()).toString()); - if ("extended_json".equals(outType)) { - return Response.ok(convertToTree(matchResult, enableElevation, pointsEncoded)). + if (debugMode) { + return Response.ok(matching.getDebugInfo()). + header("X-GH-Took", "" + Math.round(sw.getMillisDouble())). + build(); + } else if ("extended_json".equals(outType)) { + return Response.ok(convertToTree(matchResult, enableElevation, pointsEncoded, pointsEncodedMultiplier)). header("X-GH-Took", "" + Math.round(sw.getMillisDouble())). build(); } else { @@ -164,8 +174,8 @@ public Response match( header("X-GH-Took", "" + Math.round(sw.getMillisDouble())). build(); } else { - ObjectNode map = ResponsePathSerializer.jsonObject(rsp, osmDate, instructions, - calcPoints, enableElevation, pointsEncoded, sw.getMillisDouble()); + ObjectNode map = ResponsePathSerializer.jsonObject(rsp, new ResponsePathSerializer.Info(config.getCopyrights(), Math.round(sw.getMillisDouble()), osmDate), instructions, + calcPoints, enableElevation, pointsEncoded, pointsEncodedMultiplier); Map matchStatistics = new HashMap<>(); matchStatistics.put("distance", matchResult.getMatchLength()); @@ -189,7 +199,7 @@ public Response match( } } - public static JsonNode convertToTree(MatchResult result, boolean elevation, boolean pointsEncoded) { + public static JsonNode convertToTree(MatchResult result, boolean elevation, boolean pointsEncoded, double pointsEncodedMultiplier) { ObjectNode root = JsonNodeFactory.instance.objectNode(); ObjectNode diary = root.putObject("diary"); ArrayNode entries = diary.putArray("entries"); @@ -201,10 +211,10 @@ public static JsonNode convertToTree(MatchResult result, boolean elevation, bool PointList pointList = edgeMatch.getEdgeState().fetchWayGeometry(emIndex == 0 ? FetchMode.ALL : FetchMode.PILLAR_AND_ADJ); final ObjectNode geometry = link.putObject("geometry"); if (pointList.size() < 2) { - geometry.putPOJO("coordinates", pointsEncoded ? ResponsePathSerializer.encodePolyline(pointList, elevation, 1e5) : pointList.toLineString(elevation)); + geometry.putPOJO("coordinates", pointsEncoded ? ResponsePathSerializer.encodePolyline(pointList, elevation, pointsEncodedMultiplier) : pointList.toLineString(elevation)); geometry.put("type", "Point"); } else { - geometry.putPOJO("coordinates", pointsEncoded ? ResponsePathSerializer.encodePolyline(pointList, elevation, 1e5) : pointList.toLineString(elevation)); + geometry.putPOJO("coordinates", pointsEncoded ? ResponsePathSerializer.encodePolyline(pointList, elevation, pointsEncodedMultiplier) : pointList.toLineString(elevation)); geometry.put("type", "LineString"); } link.put("id", edgeMatch.getEdgeState().getEdge()); diff --git a/web-bundle/src/main/java/com/graphhopper/resources/NearestResource.java b/web-bundle/src/main/java/com/graphhopper/resources/NearestResource.java index 22d3f0db150..816b02b147d 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/NearestResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/NearestResource.java @@ -19,18 +19,21 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.graphhopper.jackson.MultiException; import com.graphhopper.routing.util.EdgeFilter; import com.graphhopper.storage.index.LocationIndex; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.DistanceCalc; import com.graphhopper.util.DistanceCalcEarth; +import com.graphhopper.util.exceptions.PointNotFoundException; import com.graphhopper.util.shapes.GHPoint; import com.graphhopper.util.shapes.GHPoint3D; -import javax.inject.Inject; -import javax.inject.Named; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; +import java.util.List; /** * @author svantulden @@ -70,7 +73,7 @@ public Response doGet(@QueryParam("point") GHPoint point, @QueryParam("elevation double[] coordinates = hasElevation && elevation ? new double[]{snappedPoint.lon, snappedPoint.lat, snappedPoint.ele} : new double[]{snappedPoint.lon, snappedPoint.lat}; return new Response(coordinates, calc.calcDist(point.lat, point.lon, snappedPoint.lat, snappedPoint.lon)); } else { - throw new WebApplicationException("Nearest point cannot be found!"); + throw new MultiException(List.of(new PointNotFoundException("Point " + point + " is either out of bounds or cannot be found", 0))); } } diff --git a/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java b/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java index 929694a5952..5b1386263c6 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/PtIsochroneResource.java @@ -19,20 +19,25 @@ package com.graphhopper.resources; import com.conveyal.gtfs.model.Stop; +import com.graphhopper.GraphHopperConfig; import com.graphhopper.gtfs.*; import com.graphhopper.http.GHLocationParam; import com.graphhopper.http.OffsetDateTimeParam; import com.graphhopper.isochrone.algorithm.ContourBuilder; import com.graphhopper.isochrone.algorithm.ReadableTriangulation; -import com.graphhopper.jackson.ResponsePathSerializer; -import com.graphhopper.routing.ev.*; +import com.graphhopper.json.Statement; +import com.graphhopper.routing.ev.Subnetwork; +import com.graphhopper.routing.ev.VehicleAccess; +import com.graphhopper.routing.ev.VehicleSpeed; import com.graphhopper.routing.util.DefaultSnapFilter; import com.graphhopper.routing.util.EncodingManager; +import com.graphhopper.routing.weighting.TurnCostProvider; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.routing.weighting.custom.CustomModelParser; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.NodeAccess; import com.graphhopper.storage.index.LocationIndex; +import com.graphhopper.util.CustomModel; import com.graphhopper.util.EdgeIteratorState; import com.graphhopper.util.JsonFeature; import com.graphhopper.util.shapes.BBox; @@ -44,25 +49,29 @@ import org.locationtech.jts.triangulate.quadedge.QuadEdgeSubdivision; import org.locationtech.jts.triangulate.quadedge.Vertex; -import javax.inject.Inject; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; import java.time.Instant; import java.util.*; +import static com.graphhopper.json.Statement.If; + @Path("isochrone-pt") public class PtIsochroneResource { private static final double JTS_TOLERANCE = 0.00001; + private final GraphHopperConfig config; private final GtfsStorage gtfsStorage; private final EncodingManager encodingManager; private final BaseGraph baseGraph; private final LocationIndex locationIndex; @Inject - public PtIsochroneResource(GtfsStorage gtfsStorage, EncodingManager encodingManager, BaseGraph baseGraph, LocationIndex locationIndex) { + public PtIsochroneResource(GraphHopperConfig config, GtfsStorage gtfsStorage, EncodingManager encodingManager, BaseGraph baseGraph, LocationIndex locationIndex) { + this.config = config; this.gtfsStorage = gtfsStorage; this.encodingManager = encodingManager; this.baseGraph = baseGraph; @@ -93,9 +102,10 @@ public Response doGet( double targetZ = seconds * 1000; GeometryFactory geometryFactory = new GeometryFactory(); - BooleanEncodedValue accessEnc = encodingManager.getBooleanEncodedValue(VehicleAccess.key("foot")); - DecimalEncodedValue speedEnc = encodingManager.getDecimalEncodedValue(VehicleSpeed.key("foot")); - final Weighting weighting = CustomModelParser.createFastestWeighting(accessEnc, speedEnc, encodingManager); + CustomModel customModel = new CustomModel() + .addToPriority(If("!" + VehicleAccess.key("foot"), Statement.Op.MULTIPLY, "0")) + .addToSpeed(If("true", Statement.Op.LIMIT, VehicleSpeed.key("foot"))); + final Weighting weighting = CustomModelParser.createWeighting(encodingManager, TurnCostProvider.NO_TURN_COST_PROVIDER, customModel); DefaultSnapFilter snapFilter = new DefaultSnapFilter(weighting, encodingManager.getBooleanEncodedValue(Subnetwork.key("foot"))); PtLocationSnapper.Result snapResult = new PtLocationSnapper(baseGraph, locationIndex, gtfsStorage).snapAll(Arrays.asList(location), Arrays.asList(snapFilter)); @@ -189,7 +199,7 @@ public Response doGet( properties.put("z", targetZ); feature.setProperties(properties); response.polygons.add(feature); - response.info.copyrights.addAll(ResponsePathSerializer.COPYRIGHTS); + response.info.copyrights.addAll(config.getCopyrights()); return response; } else { return wrap(isoline); @@ -207,7 +217,7 @@ private Response wrap(Geometry isoline) { Response response = new Response(); response.polygons.add(feature); - response.info.copyrights.addAll(ResponsePathSerializer.COPYRIGHTS); + response.info.copyrights.addAll(config.getCopyrights()); return response; } diff --git a/web-bundle/src/main/java/com/graphhopper/resources/PtMVTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/PtMVTResource.java index 2b392702ebf..7d48d625681 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/PtMVTResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/PtMVTResource.java @@ -14,16 +14,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.inject.Singleton; -import javax.servlet.http.HttpServletRequest; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.inject.Singleton; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriInfo; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/web-bundle/src/main/java/com/graphhopper/resources/PtRouteResource.java b/web-bundle/src/main/java/com/graphhopper/resources/PtRouteResource.java index cea74c48313..3ff069d6a0a 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/PtRouteResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/PtRouteResource.java @@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import com.graphhopper.GHResponse; +import com.graphhopper.GraphHopperConfig; import com.graphhopper.gtfs.GHLocation; import com.graphhopper.gtfs.PtRouter; import com.graphhopper.gtfs.Request; @@ -30,12 +31,13 @@ import com.graphhopper.util.Helper; import com.graphhopper.util.StopWatch; import io.dropwizard.jersey.params.AbstractParam; +import org.glassfish.hk2.api.ServiceLocator; -import javax.inject.Inject; -import javax.validation.constraints.NotNull; -import javax.validation.constraints.Size; -import javax.ws.rs.*; -import javax.ws.rs.core.MediaType; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.MediaType; import java.time.Instant; import java.util.List; import java.util.Optional; @@ -45,10 +47,15 @@ @Path("route-pt") public class PtRouteResource { + private final GraphHopperConfig config; private final PtRouter ptRouter; @Inject - public PtRouteResource(PtRouter ptRouter) { + ServiceLocator serviceLocator; + + @Inject + public PtRouteResource(GraphHopperConfig config, PtRouter ptRouter) { + this.config = config; this.ptRouter = ptRouter; } @@ -67,7 +74,10 @@ public ObjectNode route(@QueryParam("point") @Size(min=2,max=2) List points = requestPoints.stream().map(AbstractParam::get).collect(toList()); Instant departureTime = departureTimeParam.get().toInstant(); @@ -87,7 +97,7 @@ public ObjectNode route(@QueryParam("point") @Size(min=2,max=2) List snapPreventionsDefault; @Inject - public RouteResource(GraphHopper graphHopper, ProfileResolver profileResolver, GHRequestTransformer ghRequestTransformer, @Named("hasElevation") Boolean hasElevation) { + public RouteResource(GraphHopperConfig config, GraphHopper graphHopper, ProfileResolver profileResolver, GHRequestTransformer ghRequestTransformer, @Named("hasElevation") Boolean hasElevation) { + this.config = config; this.graphHopper = graphHopper; this.profileResolver = profileResolver; this.ghRequestTransformer = ghRequestTransformer; this.hasElevation = hasElevation; - this.osmDate = graphHopper.getProperties().get("datareader.data.date"); + this.osmDate = graphHopper.getProperties().getAll().get("datareader.data.date"); + this.snapPreventionsDefault = Arrays.stream(config.getString("routing.snap_preventions_default", "") + .split(",")).map(String::trim).filter(s -> !s.isEmpty()).toList(); } @GET @@ -77,7 +84,7 @@ public RouteResource(GraphHopper graphHopper, ProfileResolver profileResolver, G public Response doGet( @Context HttpServletRequest httpReq, @Context UriInfo uriInfo, - @QueryParam(WAY_POINT_MAX_DISTANCE) @DefaultValue("1") double minPathPrecision, + @QueryParam(WAY_POINT_MAX_DISTANCE) @DefaultValue("0.5") double minPathPrecision, @QueryParam(ELEVATION_WAY_POINT_MAX_DISTANCE) Double minPathElevationPrecision, @QueryParam("point") @NotNull List pointParams, @QueryParam("type") @DefaultValue("json") String type, @@ -85,6 +92,7 @@ public Response doGet( @QueryParam(CALC_POINTS) @DefaultValue("true") boolean calcPoints, @QueryParam("elevation") @DefaultValue("false") boolean enableElevation, @QueryParam("points_encoded") @DefaultValue("true") boolean pointsEncoded, + @QueryParam("points_encoded_multiplier") @DefaultValue("1e5") double pointsEncodedMultiplier, @QueryParam("profile") String profileName, @QueryParam(ALGORITHM) @DefaultValue("") String algoStr, @QueryParam("locale") @DefaultValue("en") String localeStr, @@ -118,13 +126,22 @@ public Response doGet( setHeadings(headings). setPointHints(pointHints). setCurbsides(curbsides). - setSnapPreventions(snapPreventions). setPathDetails(pathDetails). getHints(). putObject(CALC_POINTS, calcPoints). putObject(INSTRUCTIONS, instructions). putObject(WAY_POINT_MAX_DISTANCE, minPathPrecision); + if (uriInfo.getQueryParameters().containsKey(SNAP_PREVENTION)) { + if (snapPreventions.size() == 1 && snapPreventions.contains("")) + request.setSnapPreventions(List.of()); // e.g. "&snap_prevention=&" to force empty list + else + request.setSnapPreventions(snapPreventions); + } else { + // no "snap_prevention" was specified + request.setSnapPreventions(snapPreventionsDefault); + } + request = ghRequestTransformer.transformRequest(request); PMap profileResolverHints = new PMap(request.getHints()); @@ -157,7 +174,7 @@ public Response doGet( header("X-GH-Took", "" + Math.round(took)). build() : - Response.ok(ResponsePathSerializer.jsonObject(ghResponse, osmDate, instructions, calcPoints, enableElevation, pointsEncoded, took)). + Response.ok(ResponsePathSerializer.jsonObject(ghResponse, new ResponsePathSerializer.Info(config.getCopyrights(), Math.round(took), osmDate), instructions, calcPoints, enableElevation, pointsEncoded, pointsEncodedMultiplier)). header("X-GH-Took", "" + Math.round(took)). type(MediaType.APPLICATION_JSON). build(); @@ -168,6 +185,9 @@ public Response doGet( @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response doPost(@NotNull GHRequest request, @Context HttpServletRequest httpReq) { + if (!request.hasSnapPreventions()) + request.setSnapPreventions(snapPreventionsDefault); + StopWatch sw = new StopWatch().start(); request = ghRequestTransformer.transformRequest(request); @@ -186,6 +206,7 @@ public Response doPost(@NotNull GHRequest request, @Context HttpServletRequest h boolean enableElevation = request.getHints().getBool("elevation", false); boolean calcPoints = request.getHints().getBool(CALC_POINTS, true); boolean pointsEncoded = request.getHints().getBool("points_encoded", true); + double pointsEncodedMultiplier = request.getHints().getDouble("points_encoded_multiplier", 1e5); double took = sw.stop().getMillisDouble(); String infoStr = httpReq.getRemoteAddr() + " " + httpReq.getLocale() + " " + httpReq.getHeader("User-Agent"); @@ -202,7 +223,7 @@ public Response doPost(@NotNull GHRequest request, @Context HttpServletRequest h + ", time0: " + Math.round(ghResponse.getBest().getTime() / 60000f) + "min" + ", points0: " + ghResponse.getBest().getPoints().size() + ", debugInfo: " + ghResponse.getDebugInfo()); - return Response.ok(ResponsePathSerializer.jsonObject(ghResponse, osmDate, instructions, calcPoints, enableElevation, pointsEncoded, took)). + return Response.ok(ResponsePathSerializer.jsonObject(ghResponse, new ResponsePathSerializer.Info(config.getCopyrights(), Math.round(took), osmDate), instructions, calcPoints, enableElevation, pointsEncoded, pointsEncodedMultiplier)). header("X-GH-Took", "" + Math.round(took)). type(MediaType.APPLICATION_JSON). build(); diff --git a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java index a7a3cc947d2..66ebf50bdad 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/SPTResource.java @@ -20,13 +20,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.validation.constraints.NotNull; -import javax.ws.rs.*; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.StreamingOutput; -import javax.ws.rs.core.UriInfo; +import jakarta.inject.Inject; +import jakarta.validation.constraints.NotNull; +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.StreamingOutput; +import jakarta.ws.rs.core.UriInfo; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; @@ -100,7 +100,7 @@ public Response doGet( throw new IllegalArgumentException("Point not found:" + point); QueryGraph queryGraph = QueryGraph.create(graph, snap); NodeAccess nodeAccess = queryGraph.getNodeAccess(); - TraversalMode traversalMode = profile.isTurnCosts() ? EDGE_BASED : NODE_BASED; + TraversalMode traversalMode = profile.hasTurnCosts() ? EDGE_BASED : NODE_BASED; ShortestPathTree shortestPathTree = new ShortestPathTree(queryGraph, queryGraph.wrapWeighting(weighting), reverseFlow, traversalMode); if (distanceInMeter.orElseThrow(() -> new IllegalArgumentException("query param distance_limit is not a number.")) > 0) { diff --git a/web-bundle/src/main/java/no/ecc/vectortile/VectorTileDecoder.java b/web-bundle/src/main/java/no/ecc/vectortile/VectorTileDecoder.java index 152d11bd024..6a1953113d5 100644 --- a/web-bundle/src/main/java/no/ecc/vectortile/VectorTileDecoder.java +++ b/web-bundle/src/main/java/no/ecc/vectortile/VectorTileDecoder.java @@ -18,30 +18,15 @@ ****************************************************************/ package no.ecc.vectortile; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Set; - import org.locationtech.jts.algorithm.Area; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.LinearRing; -import org.locationtech.jts.geom.Polygon; - +import org.locationtech.jts.geom.*; import vector_tile.VectorTile; import vector_tile.VectorTile.Tile.GeomType; import vector_tile.VectorTile.Tile.Layer; +import java.io.IOException; +import java.util.*; + public class VectorTileDecoder { private boolean autoScale = true; diff --git a/web-bundle/src/main/java/no/ecc/vectortile/VectorTileEncoder.java b/web-bundle/src/main/java/no/ecc/vectortile/VectorTileEncoder.java index 63464b273fe..d28a7a5e621 100644 --- a/web-bundle/src/main/java/no/ecc/vectortile/VectorTileEncoder.java +++ b/web-bundle/src/main/java/no/ecc/vectortile/VectorTileEncoder.java @@ -18,37 +18,20 @@ ****************************************************************/ package no.ecc.vectortile; -import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - import org.locationtech.jts.algorithm.Area; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Envelope; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.GeometryCollection; -import org.locationtech.jts.geom.GeometryFactory; -import org.locationtech.jts.geom.LineString; -import org.locationtech.jts.geom.LinearRing; -import org.locationtech.jts.geom.MultiLineString; -import org.locationtech.jts.geom.MultiPoint; -import org.locationtech.jts.geom.MultiPolygon; -import org.locationtech.jts.geom.Point; -import org.locationtech.jts.geom.Polygon; -import org.locationtech.jts.geom.TopologyException; +import org.locationtech.jts.geom.*; import org.locationtech.jts.geom.prep.PreparedGeometry; import org.locationtech.jts.geom.prep.PreparedGeometryFactory; import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; import org.locationtech.jts.simplify.DouglasPeuckerSimplifier; import org.locationtech.jts.simplify.TopologyPreservingSimplifier; - import vector_tile.VectorTile; import vector_tile.VectorTile.Tile.GeomType; +import java.math.BigDecimal; +import java.util.*; + /** * This is a copy of https://github.com/ElectronicChartCentre/java-vector-tile/commit/15e2e9b127729a00c52ced3a11fd1e9a45b462b1 * We use this copy because we want to avoid the non-standard no.ecc Maven repository diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/config.js b/web-bundle/src/main/resources/com/graphhopper/maps/config.js index 3caed0352ce..5de049ac33b 100644 --- a/web-bundle/src/main/resources/com/graphhopper/maps/config.js +++ b/web-bundle/src/main/resources/com/graphhopper/maps/config.js @@ -19,4 +19,5 @@ const config = { ], snapPreventions: ['ferry'], }, -} \ No newline at end of file + profile_group_mapping: {}, +} diff --git a/web-bundle/src/main/resources/com/graphhopper/maps/pt/view/map/Map.js b/web-bundle/src/main/resources/com/graphhopper/maps/pt/view/map/Map.js index 9db42ed6207..f9aa9f15a9d 100644 --- a/web-bundle/src/main/resources/com/graphhopper/maps/pt/view/map/Map.js +++ b/web-bundle/src/main/resources/com/graphhopper/maps/pt/view/map/Map.js @@ -49,7 +49,7 @@ class LeafletComponent extends React.Component { 'sources': { 'raster-tiles-source': { 'type': 'raster', - 'tiles': ['https://b.tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=2ff19cdf28f249e2ba8e14bc6c083b39'] + 'tiles': ['https://tile.thunderforest.com/transport/{z}/{x}/{y}.png?apikey=4f283c77a8644d0b8ead1a3c3faf04ba'] }, 'gh-mvt': { 'type': 'vector', @@ -254,4 +254,4 @@ class LeafletComponent extends React.Component { }); return features; } -} \ No newline at end of file +} diff --git a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java index 8aa517dbe92..105820827dc 100644 --- a/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java +++ b/web-bundle/src/test/java/com/graphhopper/gpx/GpxConversionsTest.java @@ -21,13 +21,10 @@ import com.graphhopper.routing.Dijkstra; import com.graphhopper.routing.InstructionsFromEdges; import com.graphhopper.routing.Path; -import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValueImpl; -import com.graphhopper.routing.ev.SimpleBooleanEncodedValue; +import com.graphhopper.routing.ev.*; import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.routing.util.TraversalMode; -import com.graphhopper.routing.weighting.ShortestWeighting; +import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; import com.graphhopper.storage.NodeAccess; @@ -46,23 +43,24 @@ import java.util.Date; import java.util.List; import java.util.Locale; +import java.util.Map; -import static com.graphhopper.search.KVStorage.KeyValue.STREET_NAME; -import static com.graphhopper.search.KVStorage.KeyValue.createKV; +import static com.graphhopper.search.KVStorage.KValue; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.*; public class GpxConversionsTest { - private BooleanEncodedValue accessEnc; private DecimalEncodedValue speedEnc; private EncodingManager carManager; private TranslationMap trMap; @BeforeEach public void setUp() { - accessEnc = new SimpleBooleanEncodedValue("access", true); speedEnc = new DecimalEncodedValueImpl("speed", 5, 5, false); - carManager = EncodingManager.start().add(accessEnc).add(speedEnc).build(); + carManager = EncodingManager.start().add(speedEnc).add(VehicleAccess.create("car")). + add(Roundabout.create()).add(RoadClass.create()).add(RoadClassLink.create()). + add(RoadEnvironment.create()).add(MaxSpeed.create()).build(); trMap = new TranslationMap().doImport(); } @@ -83,14 +81,14 @@ public void testInstructionsWithTimeAndPlace() { na.setNode(6, 15.1, 10.1); na.setNode(7, 15.1, 9.8); - GHUtility.setSpeed(63, true, true, accessEnc, speedEnc, g.edge(1, 2).setDistance(7000).setKeyValues(createKV(STREET_NAME, "1-2"))); - GHUtility.setSpeed(72, true, true, accessEnc, speedEnc, g.edge(2, 3).setDistance(8000).setKeyValues(createKV(STREET_NAME, "2-3"))); - GHUtility.setSpeed(9, true, true, accessEnc, speedEnc, g.edge(2, 6).setDistance(10000).setKeyValues(createKV(STREET_NAME, "2-6"))); - GHUtility.setSpeed(81, true, true, accessEnc, speedEnc, g.edge(3, 4).setDistance(9000).setKeyValues(createKV(STREET_NAME, "3-4"))); - GHUtility.setSpeed(9, true, true, accessEnc, speedEnc, g.edge(3, 7).setDistance(10000).setKeyValues(createKV(STREET_NAME, "3-7"))); - GHUtility.setSpeed(90, true, true, accessEnc, speedEnc, g.edge(4, 5).setDistance(10000).setKeyValues(createKV(STREET_NAME, "4-5"))); + g.edge(1, 2).set(speedEnc, 63).setDistance(7000).setKeyValues(Map.of(STREET_NAME, new KValue("1-2"))); + g.edge(2, 3).set(speedEnc, 72).setDistance(8000).setKeyValues(Map.of(STREET_NAME, new KValue("2-3"))); + g.edge(2, 6).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("2-6"))); + g.edge(3, 4).set(speedEnc, 81).setDistance(9000).setKeyValues(Map.of(STREET_NAME, new KValue("3-4"))); + g.edge(3, 7).set(speedEnc, 9).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("3-7"))); + g.edge(4, 5).set(speedEnc, 90).setDistance(10000).setKeyValues(Map.of(STREET_NAME, new KValue("4-5"))); - Weighting weighting = new ShortestWeighting(accessEnc, speedEnc); + Weighting weighting = new SpeedWeighting(speedEnc); Path p = new Dijkstra(g, weighting, TraversalMode.NODE_BASED).calcPath(1, 5); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, g, weighting, carManager, trMap.getWithFallBack(Locale.US)); PointList points = p.calcPoints(); @@ -99,7 +97,7 @@ public void testInstructionsWithTimeAndPlace() { assertEquals(34000, p.getDistance(), 1e-1); assertEquals(34000, sumDistances(wayList), 1e-1); assertEquals(5, points.size()); - assertEquals(1604121, p.getTime()); + assertEquals(445588, p.getTime()); assertEquals(Instruction.CONTINUE_ON_STREET, wayList.get(0).getSign()); assertEquals("1-2", wayList.get(0).getName()); diff --git a/web-bundle/src/test/java/com/graphhopper/jackson/GraphHopperConfigModuleTest.java b/web-bundle/src/test/java/com/graphhopper/jackson/GraphHopperConfigModuleTest.java index b276cb6baa1..0c381d2b31a 100644 --- a/web-bundle/src/test/java/com/graphhopper/jackson/GraphHopperConfigModuleTest.java +++ b/web-bundle/src/test/java/com/graphhopper/jackson/GraphHopperConfigModuleTest.java @@ -19,7 +19,7 @@ package com.graphhopper.jackson; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.graphhopper.GraphHopperConfig; import org.junit.jupiter.api.Assertions; @@ -32,7 +32,7 @@ public class GraphHopperConfigModuleTest { @Test public void testDeserializeConfig() throws IOException { ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory()); - objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + objectMapper.setPropertyNamingStrategy(PropertyNamingStrategies.SNAKE_CASE); GraphHopperConfig graphHopperConfig = objectMapper.readValue(getClass().getResourceAsStream("config.yml"), GraphHopperConfig.class); // The dot in the key is no special symbol in YAML. It's just part of the string. Assertions.assertEquals(graphHopperConfig.getInt("index.max_region_search", 0), 100); diff --git a/web-bundle/src/test/resources/com/graphhopper/jackson/config.yml b/web-bundle/src/test/resources/com/graphhopper/jackson/config.yml index 71d80e1816a..ce66a1c94bc 100644 --- a/web-bundle/src/test/resources/com/graphhopper/jackson/config.yml +++ b/web-bundle/src/test/resources/com/graphhopper/jackson/config.yml @@ -1,11 +1,9 @@ datareader.file: map-matching/files/leipzig_germany.osm.pbf graph.location: graphs/leipzig-regular/graph -graph.vehicles: car profiles: - name: car - vehicle: car - weighting: fastest + weighting: custom index.max_region_search: 100 index: - pups: 200 \ No newline at end of file + pups: 200 diff --git a/web/nb-configuration.xml b/web/nb-configuration.xml deleted file mode 100644 index a40fe01b7d9..00000000000 --- a/web/nb-configuration.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - all - - diff --git a/web/pom.xml b/web/pom.xml index 6ac8fc286ae..0540d234c3e 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-web jar - 9.0-SNAPSHOT + 12.0-SNAPSHOT GraphHopper Web Use the GraphHopper routing engine as a web-service com.graphhopper graphhopper-parent - 9.0-SNAPSHOT + 12.0-SNAPSHOT package diff --git a/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java b/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java index 22bf4b12e5a..b551f3fe606 100644 --- a/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java +++ b/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java @@ -22,14 +22,13 @@ import com.graphhopper.application.resources.RootResource; import com.graphhopper.http.CORSFilter; import com.graphhopper.http.GraphHopperBundle; -import com.graphhopper.http.RealtimeBundle; import com.graphhopper.navigation.NavigateResource; -import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.Application; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; -import javax.servlet.DispatcherType; +import jakarta.servlet.DispatcherType; import java.util.EnumSet; public final class GraphHopperApplication extends Application { @@ -41,7 +40,6 @@ public static void main(String[] args) throws Exception { @Override public void initialize(Bootstrap bootstrap) { bootstrap.addBundle(new GraphHopperBundle()); - bootstrap.addBundle(new RealtimeBundle()); bootstrap.addCommand(new ImportCommand()); bootstrap.addCommand(new MatchCommand()); bootstrap.addBundle(new AssetsBundle("/com/graphhopper/maps/", "/maps/", "index.html")); diff --git a/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java b/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java index b9b8641256f..9ff8c3f4bc9 100644 --- a/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java +++ b/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java @@ -20,13 +20,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.graphhopper.GraphHopperConfig; import com.graphhopper.http.GraphHopperBundleConfiguration; -import com.graphhopper.http.RealtimeBundleConfiguration; import com.graphhopper.http.RealtimeConfiguration; -import io.dropwizard.Configuration; +import io.dropwizard.core.Configuration; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; -public class GraphHopperServerConfiguration extends Configuration implements GraphHopperBundleConfiguration, RealtimeBundleConfiguration { +public class GraphHopperServerConfiguration extends Configuration implements GraphHopperBundleConfiguration { @NotNull @JsonProperty diff --git a/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java b/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java index e47186086c7..7deff397be7 100644 --- a/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java +++ b/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java @@ -20,8 +20,8 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.http.GraphHopperManaged; -import io.dropwizard.cli.ConfiguredCommand; -import io.dropwizard.setup.Bootstrap; +import io.dropwizard.core.cli.ConfiguredCommand; +import io.dropwizard.core.setup.Bootstrap; import net.sourceforge.argparse4j.inf.Namespace; public class ImportCommand extends ConfiguredCommand { diff --git a/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java b/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java index f20ea56dcdb..a6718066c67 100644 --- a/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java +++ b/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java @@ -29,8 +29,8 @@ import com.graphhopper.matching.MatchResult; import com.graphhopper.matching.Observation; import com.graphhopper.util.*; -import io.dropwizard.cli.ConfiguredCommand; -import io.dropwizard.setup.Bootstrap; +import io.dropwizard.core.cli.ConfiguredCommand; +import io.dropwizard.core.setup.Bootstrap; import net.sourceforge.argparse4j.inf.Argument; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; diff --git a/web/src/main/java/com/graphhopper/application/resources/RootResource.java b/web/src/main/java/com/graphhopper/application/resources/RootResource.java index a2ec507e977..4c16415bdd9 100644 --- a/web/src/main/java/com/graphhopper/application/resources/RootResource.java +++ b/web/src/main/java/com/graphhopper/application/resources/RootResource.java @@ -18,10 +18,10 @@ package com.graphhopper.application.resources; -import javax.ws.rs.GET; -import javax.ws.rs.Path; -import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriBuilder; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Response; +import jakarta.ws.rs.core.UriBuilder; @Path("/") public class RootResource { diff --git a/web/src/test/java/com/graphhopper/application/GraphHopperLandmarksTest.java b/web/src/test/java/com/graphhopper/application/GraphHopperLandmarksTest.java index e3e5303dfbe..7e6333e2e3a 100644 --- a/web/src/test/java/com/graphhopper/application/GraphHopperLandmarksTest.java +++ b/web/src/test/java/com/graphhopper/application/GraphHopperLandmarksTest.java @@ -21,7 +21,8 @@ import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -30,10 +31,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.core.Response; import java.io.File; import java.util.Collections; +import static com.graphhopper.application.resources.Util.getWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -51,15 +52,15 @@ public class GraphHopperLandmarksTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car"). putObject("datareader.file", "../core/files/belarus-east.osm.gz"). + putObject("graph.encoded_values", "car_access, car_average_speed"). putObject("prepare.min_network_size", 0). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR) // force landmark creation even for tiny networks .putObject("prepare.lm.min_network_size", 2) .setProfiles(Collections.singletonList( - new Profile("car_profile").setVehicle("car") + TestProfiles.accessAndSpeed("car_profile", "car") )) .setCHProfiles(Collections.singletonList( new CHProfile("car_profile") @@ -78,17 +79,14 @@ public static void cleanUp() { @Test public void testQueries() { - Response response = clientTarget(app, "/route?profile=car_profile&" + - "point=55.99022,29.129734&point=56.001069,29.150848").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=car_profile&" + + "point=55.99022,29.129734&point=56.001069,29.150848").request().get(JsonNode.class); JsonNode path = json.get("paths").get(0); double distance = path.get("distance").asDouble(); assertEquals(1870, distance, 100, "distance wasn't correct:" + distance); - response = clientTarget(app, "/route?profile=car_profile&" + - "point=55.99022,29.129734&point=56.001069,29.150848&ch.disable=true").request().buildGet().invoke(); - json = response.readEntity(JsonNode.class); + json = clientTarget(app, "/route?profile=car_profile&" + + "point=55.99022,29.129734&point=56.001069,29.150848&ch.disable=true").request().get(JsonNode.class); distance = json.get("paths").get(0).get("distance").asDouble(); assertEquals(1870, distance, 100, "distance wasn't correct:" + distance); } @@ -97,17 +95,17 @@ public void testQueries() { public void testLandmarkDisconnect() { // if one algorithm is disabled then the following chain is executed: CH -> LM -> flexible // disconnected for landmarks - Response response = clientTarget(app, "/route?profile=car_profile&" + - "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=car_profile&" + + "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertTrue(json.get("message").toString().contains("Different subnetworks")); // without landmarks it should work - response = clientTarget(app, "/route?profile=car_profile&" + - "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true&lm.disable=true").request().buildGet().invoke(); + response = getWithStatus(clientTarget(app, "/route?profile=car_profile&" + + "point=55.99022,29.129734&point=56.007787,29.208355&ch.disable=true&lm.disable=true")); assertEquals(200, response.getStatus()); - json = response.readEntity(JsonNode.class); + json = response.getBody(); double distance = json.get("paths").get(0).get("distance").asDouble(); assertEquals(5790, distance, 100, "distance wasn't correct:" + distance); } diff --git a/web/src/test/java/com/graphhopper/application/MapMatching2Test.java b/web/src/test/java/com/graphhopper/application/MapMatching2Test.java index c566f2a8524..950bbdc9dc2 100644 --- a/web/src/test/java/com/graphhopper/application/MapMatching2Test.java +++ b/web/src/test/java/com/graphhopper/application/MapMatching2Test.java @@ -20,13 +20,13 @@ import com.fasterxml.jackson.dataformat.xml.XmlMapper; import com.graphhopper.GraphHopper; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; import com.graphhopper.gpx.GpxConversions; import com.graphhopper.jackson.Gpx; import com.graphhopper.matching.EdgeMatch; import com.graphhopper.matching.MapMatching; import com.graphhopper.matching.MatchResult; import com.graphhopper.matching.State; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.storage.index.Snap; import com.graphhopper.util.Helper; import com.graphhopper.util.PMap; @@ -59,7 +59,8 @@ public void testIssue13() throws IOException { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile("../map-matching/files/map-issue13.osm.gz"); hopper.setGraphHopperLocation(GH_LOCATION); - hopper.setProfiles(new Profile("my_profile").setVehicle("car")); + hopper.setEncodedValuesString("car_access, car_average_speed"); + hopper.setProfiles(TestProfiles.accessAndSpeed("my_profile", "car")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile")); hopper.importOrLoad(); @@ -84,7 +85,8 @@ public void testIssue70() throws IOException { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile("../map-matching/files/issue-70.osm.gz"); hopper.setGraphHopperLocation(GH_LOCATION); - hopper.setProfiles(new Profile("my_profile").setVehicle("car")); + hopper.setEncodedValuesString("car_access, car_average_speed"); + hopper.setProfiles(TestProfiles.accessAndSpeed("my_profile", "car")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile")); hopper.importOrLoad(); @@ -104,7 +106,8 @@ public void testIssue127() throws IOException { GraphHopper hopper = new GraphHopper(); hopper.setOSMFile("../map-matching/files/map-issue13.osm.gz"); hopper.setGraphHopperLocation(GH_LOCATION); - hopper.setProfiles(new Profile("my_profile").setVehicle("car")); + hopper.setEncodedValuesString("car_access, car_average_speed"); + hopper.setProfiles(TestProfiles.accessAndSpeed("my_profile", "car")); hopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile")); hopper.importOrLoad(); diff --git a/web/src/test/java/com/graphhopper/application/MapMatchingTest.java b/web/src/test/java/com/graphhopper/application/MapMatchingTest.java index 6eb4b53fac8..d37f0759587 100644 --- a/web/src/test/java/com/graphhopper/application/MapMatchingTest.java +++ b/web/src/test/java/com/graphhopper/application/MapMatchingTest.java @@ -22,17 +22,15 @@ import com.graphhopper.GraphHopper; import com.graphhopper.ResponsePath; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; import com.graphhopper.gpx.GpxConversions; import com.graphhopper.jackson.Gpx; -import com.graphhopper.matching.EdgeMatch; -import com.graphhopper.matching.MapMatching; -import com.graphhopper.matching.MatchResult; -import com.graphhopper.matching.Observation; +import com.graphhopper.matching.*; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.*; import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -47,8 +45,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.graphhopper.json.Statement.If; -import static com.graphhopper.json.Statement.Op.MULTIPLY; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.lessThan; import static org.hamcrest.core.Is.is; @@ -71,10 +67,8 @@ public static void setup() { graphHopper = new GraphHopper(); graphHopper.setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"); graphHopper.setGraphHopperLocation(GH_LOCATION); - graphHopper.setProfiles(new Profile("my_profile"). - setCustomModel(new CustomModel(). - addToPriority(If("road_access == DESTINATION", MULTIPLY, "0.1"))). - setVehicle("car")); + graphHopper.setEncodedValuesString("car_access, car_average_speed"); + graphHopper.setProfiles(TestProfiles.accessAndSpeed("my_profile", "car")); graphHopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile")); graphHopper.importOrLoad(); } @@ -146,7 +140,7 @@ public void testDoWork(PMap hints) { assertEquals(route.getDistance(), mr.getMatchLength(), 0.5); // GraphHopper travel times aren't exactly additive assertThat(Math.abs(route.getTime() - mr.getMatchMillis()), is(lessThan(1000L))); - assertEquals(208, mr.getEdgeMatches().size()); + assertEquals(202, mr.getEdgeMatches().size()); } @ParameterizedTest @@ -173,7 +167,7 @@ public void testLongTrackWithTwoPoints(PMap hints) { new Observation(new GHPoint(51.23, 12.18)), new Observation(new GHPoint(51.45, 12.59))); MatchResult mr = mapMatching.match(inputGPXEntries); - assertEquals(57553.0, mr.getMatchLength(), 1.0); + assertEquals(57651, mr.getMatchLength(), 1.0); } @ParameterizedTest @@ -285,10 +279,6 @@ public void testLoop2(PMap hints) throws IOException { @ParameterizedTest @ArgumentsSource(FixtureProvider.class) public void testUTurns(PMap hints) throws IOException { - hints = new PMap(hints) - // Reduce penalty to allow U-turns - .putObject(Parameters.Routing.HEADING_PENALTY, 50); - MapMatching mapMatching = MapMatching.fromGraphHopper(graphHopper, hints); Gpx gpx = xmlMapper.readValue(getClass().getResourceAsStream("/tour4-with-uturn.gpx"), Gpx.class); @@ -296,11 +286,12 @@ public void testUTurns(PMap hints) throws IOException { mapMatching.setMeasurementErrorSigma(50); MatchResult mr = mapMatching.match(GpxConversions.getEntries(gpx.trk.get(0))); assertEquals(Arrays.asList("Gustav-Adolf-Straße", "Funkenburgstraße"), fetchStreets(mr.getEdgeMatches())); - + assertEquals(385.0, mr.getMatchLength(), 1.0); // with small measurement error, we expect the U-turn mapMatching.setMeasurementErrorSigma(10); mr = mapMatching.match(GpxConversions.getEntries(gpx.trk.get(0))); assertEquals(Arrays.asList("Gustav-Adolf-Straße", "Funkenburgstraße"), fetchStreets(mr.getEdgeMatches())); + assertEquals(445.0, mr.getMatchLength(), 1.0); } static List fetchStreets(List emList) { @@ -326,6 +317,24 @@ static List fetchStreets(List emList) { return list; } + @Test + public void testDebugOutput() throws IOException { + PMap hints = new PMap().putObject(Parameters.Landmark.DISABLE, true).putObject("profile", "my_profile"); + Gpx gpx = xmlMapper.readValue(getClass().getResourceAsStream("/tour4-with-uturn.gpx"), Gpx.class); + List observations = GpxConversions.getEntries(gpx.trk.get(0)); + + MapMatching mapMatching = MapMatching.fromGraphHopper(graphHopper, hints); + mapMatching.setCollectDebugInfo(true); + mapMatching.setMeasurementErrorSigma(10); + mapMatching.match(observations); + + MatchDebugInfo debugInfo = mapMatching.getDebugInfo(); + assertNotNull(debugInfo); + assertFalse(debugInfo.timeSteps.isEmpty()); + assertFalse(debugInfo.transitions.isEmpty()); + assertTrue(debugInfo.transitions.stream().anyMatch(t -> t.chosen)); + } + /** * This method does not in fact create random observations. It creates observations at nodes on a route. * This method _should_ be replaced by one that creates random observations along a route, diff --git a/web/src/test/java/com/graphhopper/application/RoutingAdditivityTest.java b/web/src/test/java/com/graphhopper/application/RoutingAdditivityTest.java index 2228b9bc1fc..b5e2510d723 100644 --- a/web/src/test/java/com/graphhopper/application/RoutingAdditivityTest.java +++ b/web/src/test/java/com/graphhopper/application/RoutingAdditivityTest.java @@ -22,7 +22,7 @@ import com.graphhopper.GraphHopper; import com.graphhopper.ResponsePath; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.AfterAll; @@ -48,7 +48,8 @@ public static void setup() { graphHopper = new GraphHopper(); graphHopper.setOSMFile("../map-matching/files/leipzig_germany.osm.pbf"); graphHopper.setGraphHopperLocation(GH_LOCATION); - graphHopper.setProfiles(new Profile("my_profile").setVehicle("car")); + graphHopper.setEncodedValuesString("car_access, car_average_speed"); + graphHopper.setProfiles(TestProfiles.accessAndSpeed("my_profile", "car")); graphHopper.getLMPreparationHandler().setLMProfiles(new LMProfile("my_profile")); graphHopper.importOrLoad(); } diff --git a/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java b/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java index 5607313bff3..3fbeef17da2 100644 --- a/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/ExtendedJsonResponseTest.java @@ -36,14 +36,16 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; +import static com.graphhopper.util.Parameters.Details.STREET_NAME; import static org.junit.jupiter.api.Assertions.assertEquals; public class ExtendedJsonResponseTest { @Test public void shouldCreateBasicStructure() { - JsonNode jsonObject = MapMatchingResource.convertToTree(new MatchResult(getEdgeMatch()), false, false); + JsonNode jsonObject = MapMatchingResource.convertToTree(new MatchResult(getEdgeMatch()), false, false, -1); JsonNode route = jsonObject.get("diary").get("entries").get(0); JsonNode link = route.get("links").get(0); JsonNode geometry = link.get("geometry"); @@ -88,7 +90,7 @@ private EdgeIteratorState getEdgeIterator() { pointList.add(-3.4445, -38.9990); pointList.add(-3.5550, -38.7990); return new VirtualEdgeIteratorState(0, 0, 0, 1, 10, new IntsRef(1), - KVStorage.KeyValue.createKV(KVStorage.KeyValue.STREET_NAME, "test of iterator"), pointList, false); + Map.of(STREET_NAME, new KVStorage.KValue("test of iterator")), pointList, false); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/GpxTravelTimeConsistencyTest.java b/web/src/test/java/com/graphhopper/application/resources/GpxTravelTimeConsistencyTest.java index 0dddb530475..faee9707474 100644 --- a/web/src/test/java/com/graphhopper/application/resources/GpxTravelTimeConsistencyTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/GpxTravelTimeConsistencyTest.java @@ -21,8 +21,8 @@ import com.graphhopper.GHRequest; import com.graphhopper.GraphHopper; import com.graphhopper.ResponsePath; -import com.graphhopper.config.Profile; import com.graphhopper.gpx.GpxConversions; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import com.graphhopper.util.shapes.GHPoint; import org.junit.jupiter.api.BeforeAll; @@ -46,7 +46,7 @@ public static void beforeClass() { Helper.removeDir(new File(graphFileFoot)); hopper = new GraphHopper(). setOSMFile(osmFile). - setProfiles(new Profile("profile").setVehicle("foot")). + setProfiles(TestProfiles.constantSpeed("profile")). setStoreOnFlush(true). setGraphHopperLocation(graphFileFoot). importOrLoad(); diff --git a/web/src/test/java/com/graphhopper/application/resources/I18nResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/I18nResourceTest.java index c2d764a41b6..c01e0a0bf9e 100644 --- a/web/src/test/java/com/graphhopper/application/resources/I18nResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/I18nResourceTest.java @@ -21,7 +21,7 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -30,12 +30,10 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.core.Response; import java.io.File; -import java.util.Collections; +import java.util.List; import static com.graphhopper.application.util.TestUtils.clientTarget; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @ExtendWith(DropwizardExtensionsSupport.class) @@ -46,11 +44,10 @@ public class I18nResourceTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car"). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("graph.location", DIR). putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList(new Profile("car").setVehicle("car"))); + setProfiles(List.of(TestProfiles.constantSpeed("car"))); return config; } @@ -62,14 +59,10 @@ public static void cleanUp() { @Test public void requestI18n() { - Response response = clientTarget(app, "/i18n").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - String str = response.readEntity(String.class); + String str = clientTarget(app, "/i18n").request().get(String.class); assertTrue(str.contains("\"en\":") && str.contains("\"locale\":\"\""), str); - response = clientTarget(app, "/i18n/de").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - str = response.readEntity(String.class); + str = clientTarget(app, "/i18n/de").request().get(String.class); assertTrue(str.contains("\"default\":") && str.contains("\"locale\":\"de\""), str); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/IsochroneResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/IsochroneResourceTest.java index 47f28709a1b..943b0ef768b 100644 --- a/web/src/test/java/com/graphhopper/application/resources/IsochroneResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/IsochroneResourceTest.java @@ -22,10 +22,11 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; -import com.graphhopper.util.CustomModel; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import com.graphhopper.util.JsonFeatureCollection; +import com.graphhopper.util.TurnCostsConfig; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import org.junit.jupiter.api.AfterAll; @@ -38,11 +39,11 @@ import org.locationtech.jts.geom.GeometryFactory; import org.locationtech.jts.geom.Polygon; -import javax.ws.rs.client.WebTarget; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.client.WebTarget; import java.io.File; import java.util.Arrays; +import static com.graphhopper.application.resources.Util.getWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.*; @@ -54,14 +55,14 @@ public class IsochroneResourceTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car|turn_costs=true"). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR). + putObject("graph.encoded_values", "car_access, car_average_speed"). setProfiles(Arrays.asList( - new Profile("fast_car").setVehicle("car").setTurnCosts(true), - new Profile("short_car").setCustomModel(new CustomModel().setDistanceInfluence(1_000d)).setVehicle("car").setTurnCosts(true), - new Profile("fast_car_no_turn_restrictions").setVehicle("car").setTurnCosts(false) + TestProfiles.accessAndSpeed("fast_car", "car").setTurnCostsConfig(TurnCostsConfig.car()), + TestProfiles.constantSpeed("short_car", 35).setTurnCostsConfig(TurnCostsConfig.car()), + TestProfiles.accessAndSpeed("fast_car_no_turn_restrictions", "car") )); return config; } @@ -76,14 +77,13 @@ public static void cleanUp() { @Test public void requestByTimeLimit() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "fast_car") .queryParam("point", "42.531073,1.573792") .queryParam("time_limit", 5 * 60) .queryParam("buckets", 2) .queryParam("type", "geojson") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(2, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -98,14 +98,13 @@ public void requestByTimeLimit() { @Test public void requestByTimeLimitNoTurnRestrictions() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "fast_car_no_turn_restrictions") .queryParam("point", "42.531073,1.573792") .queryParam("time_limit", 5 * 60) .queryParam("buckets", 2) .queryParam("type", "geojson") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(2, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -120,14 +119,13 @@ public void requestByTimeLimitNoTurnRestrictions() { @Test public void requestByDistanceLimit() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "fast_car") .queryParam("point", "42.531073,1.573792") .queryParam("distance_limit", 3_000) .queryParam("buckets", 2) .queryParam("type", "geojson") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(2, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -147,35 +145,30 @@ public void requestByWeightLimit() { .queryParam("point", "42.531073,1.573792") .queryParam("type", "geojson"); - long limit = 10 * 60; + JsonFeatureCollection timeLimitFeatureCollection = commonTarget + .queryParam("time_limit", 600) + .request().get(JsonFeatureCollection.class); + Geometry timeLimitPolygon = timeLimitFeatureCollection.getFeatures().get(0).getGeometry(); - Response distanceLimitRsp = commonTarget - .queryParam("time_limit", limit) - .request().buildGet().invoke(); - JsonFeatureCollection distanceLimitFeatureCollection = distanceLimitRsp.readEntity(JsonFeatureCollection.class); - Geometry distanceLimitPolygon = distanceLimitFeatureCollection.getFeatures().get(0).getGeometry(); - - Response weightLimitRsp = commonTarget - .queryParam("weight_limit", limit) - .request().buildGet().invoke(); - JsonFeatureCollection weightLimitFeatureCollection = weightLimitRsp.readEntity(JsonFeatureCollection.class); + JsonFeatureCollection weightLimitFeatureCollection = commonTarget + .queryParam("weight_limit", 6000) + .request().get(JsonFeatureCollection.class); Geometry weightLimitPolygon = weightLimitFeatureCollection.getFeatures().get(0).getGeometry(); - assertEquals(distanceLimitPolygon.getNumPoints(), weightLimitPolygon.getNumPoints()); - assertTrue(weightLimitPolygon.equalsTopo(distanceLimitPolygon)); + assertEquals(timeLimitPolygon.getNumPoints(), weightLimitPolygon.getNumPoints()); + assertTrue(weightLimitPolygon.equalsTopo(timeLimitPolygon)); } @Test public void requestReverseFlow() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "fast_car") .queryParam("point", "42.531073,1.573792") .queryParam("reverse_flow", true) .queryParam("time_limit", 5 * 60) .queryParam("buckets", 2) .queryParam("type", "geojson") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(2, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -193,9 +186,9 @@ public void requestReverseFlow() { @Test public void requestBadRequest() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=-1.816719,51.557148").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/isochrone?profile=fast_car&point=-1.816719,51.557148")); assertEquals(400, response.getStatus()); - String error = response.readEntity(String.class); + String error = response.getBody().toString(); assertTrue(error.contains("Point not found:-1.816719,51.557148"), error); } @@ -207,29 +200,28 @@ public void profileWithLegacyParametersNotAllowed() { @Test public void missingPoint() { - Response rsp = clientTarget(app, "/isochrone").request().buildGet().invoke(); + BodyAndStatus rsp = getWithStatus(clientTarget(app, "/isochrone")); assertEquals(400, rsp.getStatus()); - JsonNode json = rsp.readEntity(JsonNode.class); + JsonNode json = rsp.getBody(); assertTrue(json.get("message").toString().contains("query param point must not be null"), json.toString()); } private void assertNotAllowed(String hint, String error) { - Response rsp = clientTarget(app, "/isochrone?point=42.531073,1.573792" + hint).request().buildGet().invoke(); + BodyAndStatus rsp = getWithStatus(clientTarget(app, "/isochrone?point=42.531073,1.573792" + hint)); assertEquals(400, rsp.getStatus()); - JsonNode json = rsp.readEntity(JsonNode.class); + JsonNode json = rsp.getBody(); assertTrue(json.get("message").toString().contains(error), json.toString()); } @Test public void requestWithShortest() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "short_car") .queryParam("point", "42.509644,1.540554") .queryParam("time_limit", 130) .queryParam("buckets", 1) .queryParam("type", "geojson") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(1, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -245,10 +237,9 @@ private static void assertIs2D(Geometry geometry) { @Test public void requestBadType() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=xml") - .request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=xml")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); String message = json.path("message").asText(); assertEquals("query param type must be one of [json, geojson]", message); @@ -256,18 +247,16 @@ public void requestBadType() { @Test public void testTypeIsCaseInsensitive() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=GEOJSON") - .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=GEOJSON") + .request().get(JsonFeatureCollection.class); + assertFalse(featureCollection.getFeatures().isEmpty()); } @Test public void requestNotANumber() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=wurst") - .request().buildGet().invoke(); - + BodyAndStatus response = getWithStatus(clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=wurst")); assertEquals(404, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); String message = json.path("message").asText(); assertEquals("HTTP 404 Not Found", message); @@ -276,15 +265,14 @@ public void requestNotANumber() { @Disabled("block_area is no longer supported and to use custom models we'd need a POST endpoint for isochrones") @Test public void requestWithBlockArea() { - Response rsp = clientTarget(app, "/isochrone") + JsonFeatureCollection featureCollection = clientTarget(app, "/isochrone") .queryParam("profile", "fast_car") .queryParam("point", "42.531073,1.573792") .queryParam("time_limit", 5 * 60) .queryParam("buckets", 2) .queryParam("type", "geojson") .queryParam("block_area", "42.558067,1.589429,100") - .request().buildGet().invoke(); - JsonFeatureCollection featureCollection = rsp.readEntity(JsonFeatureCollection.class); + .request().get(JsonFeatureCollection.class); assertEquals(2, featureCollection.getFeatures().size()); Geometry polygon0 = featureCollection.getFeatures().get(0).getGeometry(); @@ -301,27 +289,24 @@ public void requestWithBlockArea() { @Test public void requestJsonWithType() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=json") - .request().buildGet().invoke(); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=json") + .request().get(JsonNode.class); assertTrue(json.has("polygons")); assertTrue(json.has("info")); } @Test public void requestJsonNoType() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130") - .request().buildGet().invoke(); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130") + .request().get(JsonNode.class); assertTrue(json.has("polygons")); assertTrue(json.has("info")); } @Test public void requestGeoJsonPolygons() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=geojson") - .request().buildGet().invoke(); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=geojson") + .request().get(JsonNode.class); assertFalse(json.has("polygons")); assertFalse(json.has("info")); @@ -343,9 +328,8 @@ public void requestGeoJsonPolygons() { @Test public void requestGeoJsonPolygonsBuckets() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=geojson&buckets=3") - .request().buildGet().invoke(); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/isochrone?profile=fast_car&point=42.531073,1.573792&time_limit=130&type=geojson&buckets=3") + .request().get(JsonNode.class); JsonNode features = json.path("features"); JsonNode firstFeature = features.path(0); @@ -360,15 +344,14 @@ public void requestGeoJsonPolygonsBuckets() { @Test public void requestTenBucketsIssue2094() { - Response response = clientTarget(app, "/isochrone?profile=fast_car&point=42.510008,1.530018&time_limit=400&type=geojson&buckets=10") - .request().buildGet().invoke(); - JsonFeatureCollection collection = response.readEntity(JsonFeatureCollection.class); + JsonFeatureCollection collection = clientTarget(app, "/isochrone?profile=fast_car&point=42.510008,1.530018&time_limit=400&type=geojson&buckets=10") + .request().get(JsonFeatureCollection.class); Polygon lastPolygon = (Polygon) collection.getFeatures().get(collection.getFeatures().size() - 1).getGeometry(); assertTrue(lastPolygon.contains(geometryFactory.createPoint(new Coordinate(1.580229, 42.533161)))); assertFalse(lastPolygon.contains(geometryFactory.createPoint(new Coordinate(1.584606, 42.535121)))); Polygon beforeLastPolygon = (Polygon) collection.getFeatures().get(collection.getFeatures().size() - 2).getGeometry(); assertTrue(beforeLastPolygon.contains(geometryFactory.createPoint(new Coordinate(1.564136, 42.524938)))); - assertFalse(beforeLastPolygon.contains(geometryFactory.createPoint(new Coordinate(1.571474, 42.529176)))); + assertFalse(beforeLastPolygon.contains(geometryFactory.createPoint(new Coordinate(1.575551, 42.532528)))); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/MVTResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/MVTResourceTest.java index 08a10fae109..906acc54885 100644 --- a/web/src/test/java/com/graphhopper/application/resources/MVTResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/MVTResourceTest.java @@ -20,7 +20,7 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -31,12 +31,14 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.locationtech.jts.geom.Geometry; -import javax.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; import static com.graphhopper.application.util.TestUtils.clientTarget; import static com.graphhopper.util.Parameters.Details.STREET_NAME; @@ -53,13 +55,12 @@ public class MVTResourceTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car"). putObject("graph.encoded_values", "road_class,road_environment,max_speed,surface"). putObject("prepare.min_network_size", 0). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR). - setProfiles(Collections.singletonList(new Profile("car").setVehicle("car"))); + setProfiles(List.of(TestProfiles.constantSpeed("car"))); return config; } @@ -71,23 +72,19 @@ public static void cleanUp() { @Test public void testBasicMvtQuery() throws IOException { - final Response response = clientTarget(app, "/mvt/15/16528/12099.mvt").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - InputStream is = response.readEntity(InputStream.class); + InputStream is = clientTarget(app, "/mvt/15/16528/12099.mvt").request().get(InputStream.class); VectorTileDecoder.FeatureIterable features = new VectorTileDecoder().decode(readInputStream(is)); assertEquals(Arrays.asList("roads"), new ArrayList<>(features.getLayerNames())); VectorTileDecoder.Feature feature = features.iterator().next(); Map attributes = feature.getAttributes(); Geometry geometry = feature.getGeometry(); - assertEquals(48, geometry.getCoordinates().length); + assertEquals(51, geometry.getCoordinates().length); assertEquals("Camì de les Pardines", attributes.get(STREET_NAME)); } @Test public void testDetailsInResponse() throws IOException { - final Response response = clientTarget(app, "/mvt/15/16522/12102.mvt").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - InputStream is = response.readEntity(InputStream.class); + InputStream is = clientTarget(app, "/mvt/15/16522/12102.mvt").request().get(InputStream.class); List features = new VectorTileDecoder().decode(readInputStream(is)).asList(); assertEquals(28, features.size()); diff --git a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTest.java index e8a03ec0500..7bcff1368f3 100644 --- a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTest.java @@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; -import com.graphhopper.config.Profile; -import com.graphhopper.jackson.ResponsePathDeserializer; +import com.graphhopper.jackson.ResponsePathDeserializerHelper; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -33,14 +33,13 @@ import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Response; import java.io.File; import java.util.Arrays; import static com.graphhopper.application.util.TestUtils.clientTarget; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; /** * @author Peter Karich @@ -54,13 +53,13 @@ public class MapMatchingResourceTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car,bike"). putObject("datareader.file", "../map-matching/files/leipzig_germany.osm.pbf"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR). + putObject("graph.encoded_values", "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). setProfiles(Arrays.asList( - new Profile("fast_car").setVehicle("car"), - new Profile("fast_bike").setVehicle("bike"))); + TestProfiles.accessAndSpeed("fast_car", "car"), + TestProfiles.accessSpeedAndPriority("fast_bike", "bike"))); return config; } @@ -71,17 +70,14 @@ public static void cleanUp() { @Test public void testGPX() { - final Response response = clientTarget(app, "/match?profile=fast_car") + JsonNode json = clientTarget(app, "/match?profile=fast_car") .request() - .buildPost(Entity.xml(getClass().getResourceAsStream("/tour2-with-loop.gpx"))) - .invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + .post(Entity.xml(getClass().getResourceAsStream("/tour2-with-loop.gpx")), JsonNode.class); JsonNode path = json.get("paths").get(0); LineString expectedGeometry = readWktLineString("LINESTRING (12.3607 51.34365, 12.36418 51.34443, 12.36379 51.34538, 12.36082 51.34471, 12.36188 51.34278)"); - LineString actualGeometry = ResponsePathDeserializer.decodePolyline(path.get("points").asText(), 10, false).toLineString(false); - assertEquals(DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 0.0, 1E-4); + LineString actualGeometry = ResponsePathDeserializerHelper.decodePolyline(path.get("points").asText(), 10, false, 1e5).toLineString(false); + assertEquals(0.0, DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 1E-4); assertEquals(101, path.get("time").asLong() / 1000f, 1); assertEquals(101, json.get("map_matching").get("time").asLong() / 1000f, 1); assertEquals(812, path.get("distance").asDouble(), 1); @@ -91,17 +87,14 @@ public void testGPX() { @Test public void testBike() throws ParseException { WKTReader wktReader = new WKTReader(); - final Response response = clientTarget(app, "/match?profile=fast_bike") + final JsonNode json = clientTarget(app, "/match?profile=fast_bike") .request() - .buildPost(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx"))) - .invoke(); - assertEquals(200, response.getStatus(), "no success"); - JsonNode json = response.readEntity(JsonNode.class); + .post(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx")), JsonNode.class); JsonNode path = json.get("paths").get(0); LineString expectedGeometry = (LineString) wktReader.read("LINESTRING (12.3607 51.34365, 12.36418 51.34443, 12.36379 51.34538, 12.36082 51.34471, 12.36188 51.34278)"); - LineString actualGeometry = ResponsePathDeserializer.decodePolyline(path.get("points").asText(), 10, false).toLineString(false); - assertEquals(DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 0.0, 1E-4); + LineString actualGeometry = ResponsePathDeserializerHelper.decodePolyline(path.get("points").asText(), 10, false, 1e5).toLineString(false); + assertEquals(0.0, DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 1E-4); // ensure that is actually also is bike! (slower than car) assertEquals(162, path.get("time").asLong() / 1000f, 1); @@ -109,24 +102,24 @@ public void testBike() throws ParseException { @Test public void testGPX10() { - final Response response = clientTarget(app, "/match?profile=fast_car") + JsonNode json = clientTarget(app, "/match?profile=fast_car") .request() - .buildPost(Entity.xml(getClass().getResourceAsStream("gpxv1_0.gpx"))) - .invoke(); - assertEquals(200, response.getStatus()); + .post(Entity.xml(getClass().getResourceAsStream("gpxv1_0.gpx")), JsonNode.class); + assertFalse(json.get("paths").isEmpty()); } @Test public void testEmptyGPX() { - final Response response = clientTarget(app, "/match?profile=fast_car") + try (Response response = clientTarget(app, "/match?profile=fast_car") .request() .buildPost(Entity.xml(getClass().getResourceAsStream("test-only-wpt.gpx"))) - .invoke(); - assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); - JsonNode message = json.get("message"); - assertTrue(message.isValueNode()); - assertTrue(message.asText().startsWith("No tracks found")); + .invoke()) { + assertEquals(400, response.getStatus()); + JsonNode json = response.readEntity(JsonNode.class); + JsonNode message = json.get("message"); + assertTrue(message.isValueNode()); + assertTrue(message.asText().startsWith("No tracks found")); + } } private LineString readWktLineString(String wkt) { diff --git a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java index 7cb9a2adeb9..8394c643762 100644 --- a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java @@ -22,9 +22,10 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; -import com.graphhopper.jackson.ResponsePathDeserializer; +import com.graphhopper.jackson.ResponsePathDeserializerHelper; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; +import com.graphhopper.util.TurnCostsConfig; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import org.junit.jupiter.api.AfterAll; @@ -36,8 +37,8 @@ import org.locationtech.jts.io.ParseException; import org.locationtech.jts.io.WKTReader; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.Response; import java.io.File; import java.util.Arrays; import java.util.Collections; @@ -57,14 +58,14 @@ public class MapMatchingResourceTurnCostsTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car|turn_costs=true,bike"). putObject("datareader.file", "../map-matching/files/leipzig_germany.osm.pbf"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR). + putObject("graph.encoded_values", "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). setProfiles(Arrays.asList( - new Profile("car").setVehicle("car").setTurnCosts(true), - new Profile("car_no_tc").setVehicle("car"), - new Profile("bike").setVehicle("bike")) + TestProfiles.accessAndSpeed("car").setTurnCostsConfig(TurnCostsConfig.car()), + TestProfiles.accessAndSpeed("car_no_tc", "car"), + TestProfiles.accessSpeedAndPriority("bike")) ). setLMProfiles(Arrays.asList( new LMProfile("car"), @@ -100,28 +101,26 @@ public void disableCHLM() { @Test public void errorOnUnknownProfile() { - final Response response = clientTarget(app, "/match?profile=xyz") + try (Response response = clientTarget(app, "/match?profile=xyz") .request() .buildPost(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx"))) - .invoke(); - JsonNode json = response.readEntity(JsonNode.class); - assertTrue(json.has("message"), json.toString()); - assertEquals(400, response.getStatus()); - assertTrue(json.toString().contains("The requested profile 'xyz' does not exist.\\nAvailable profiles: [car, car_no_tc, bike]"), json.toString()); + .invoke()) { + JsonNode json = response.readEntity(JsonNode.class); + assertTrue(json.has("message"), json.toString()); + assertEquals(400, response.getStatus()); + assertTrue(json.toString().contains("The requested profile 'xyz' does not exist.\\nAvailable profiles: [car, car_no_tc, bike]"), json.toString()); + } } private void runCar(String urlParams) { - final Response response = clientTarget(app, "/match?" + urlParams) + JsonNode json = clientTarget(app, "/match?" + urlParams) .request() - .buildPost(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx"))) - .invoke(); - JsonNode json = response.readEntity(JsonNode.class); + .post(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx")), JsonNode.class); assertFalse(json.has("message"), json.toString()); - assertEquals(200, response.getStatus()); JsonNode path = json.get("paths").get(0); LineString expectedGeometry = readWktLineString("LINESTRING (12.3607 51.34365, 12.36418 51.34443, 12.36379 51.34538, 12.36082 51.34471, 12.36188 51.34278)"); - LineString actualGeometry = ResponsePathDeserializer.decodePolyline(path.get("points").asText(), 10, false).toLineString(false); + LineString actualGeometry = ResponsePathDeserializerHelper.decodePolyline(path.get("points").asText(), 10, false, 1e5).toLineString(false); assertEquals(DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 0.0, 1E-4); assertEquals(101, path.get("time").asLong() / 1000f, 1); assertEquals(101, json.get("map_matching").get("time").asLong() / 1000f, 1); @@ -130,17 +129,14 @@ private void runCar(String urlParams) { } private void runBike(String urlParams) { - final Response response = clientTarget(app, "/match?" + urlParams) + JsonNode json = clientTarget(app, "/match?" + urlParams) .request() - .buildPost(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx"))) - .invoke(); - JsonNode json = response.readEntity(JsonNode.class); + .post(Entity.xml(getClass().getResourceAsStream("another-tour-with-loop.gpx")), JsonNode.class); assertFalse(json.has("message"), json.toString()); - assertEquals(200, response.getStatus()); JsonNode path = json.get("paths").get(0); LineString expectedGeometry = readWktLineString("LINESTRING (12.3607 51.34365, 12.36418 51.34443, 12.36379 51.34538, 12.36082 51.34471, 12.36188 51.34278)"); - LineString actualGeometry = ResponsePathDeserializer.decodePolyline(path.get("points").asText(), 10, false).toLineString(false); + LineString actualGeometry = ResponsePathDeserializerHelper.decodePolyline(path.get("points").asText(), 10, false, 1e5).toLineString(false); assertEquals(DiscreteHausdorffDistance.distance(expectedGeometry, actualGeometry), 0.0, 1E-4); assertEquals(162.31, path.get("time").asLong() / 1000f, 0.1); assertEquals(162.31, json.get("map_matching").get("time").asLong() / 1000f, 0.1); diff --git a/web/src/test/java/com/graphhopper/application/resources/NearestResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/NearestResourceTest.java index 6e1a69808fd..8efa1d9ae18 100644 --- a/web/src/test/java/com/graphhopper/application/resources/NearestResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/NearestResourceTest.java @@ -20,8 +20,8 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; import com.graphhopper.resources.NearestResource; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -30,13 +30,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.core.Response; import java.io.File; -import java.util.Collections; +import java.util.List; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; /** * @author svantulden @@ -49,11 +47,10 @@ public class NearestResourceTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car"). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("graph.location", dir). putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList(new Profile("car").setVehicle("car"))); + setProfiles(List.of(TestProfiles.constantSpeed("car"))); return config; } @@ -65,9 +62,7 @@ public static void cleanUp() { @Test public void testBasicNearestQuery() { - final Response response = clientTarget(app, "/nearest?point=42.554851,1.536198").request().buildGet().invoke(); - assertEquals(200, response.getStatus(), "HTTP status"); - NearestResource.Response json = response.readEntity(NearestResource.Response.class); + NearestResource.Response json = clientTarget(app, "/nearest?point=42.554851,1.536198").request().get(NearestResource.Response.class); assertArrayEquals(new double[]{1.5363743623376815, 42.554839049600155}, json.coordinates, "nearest point"); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/NearestResourceWithEleTest.java b/web/src/test/java/com/graphhopper/application/resources/NearestResourceWithEleTest.java index ead34bd125a..fb30b8e0761 100644 --- a/web/src/test/java/com/graphhopper/application/resources/NearestResourceWithEleTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/NearestResourceWithEleTest.java @@ -22,7 +22,7 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -32,7 +32,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import java.io.File; -import java.util.Collections; +import java.util.List; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.*; @@ -49,13 +49,13 @@ private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). putObject("graph.elevation.provider", "srtm"). + putObject("graph.elevation.clear", true). putObject("graph.elevation.cache_dir", "../core/files/"). putObject("prepare.min_network_size", 0). - putObject("graph.vehicles", "car"). putObject("datareader.file", "../core/files/monaco.osm.gz"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", dir). - setProfiles(Collections.singletonList(new Profile("car").setVehicle("car"))); + setProfiles(List.of(TestProfiles.constantSpeed("car"))); return config; } @@ -74,7 +74,7 @@ public void testWithEleQuery() { double lon = point.get(0).asDouble(); double lat = point.get(1).asDouble(); double ele = point.get(2).asDouble(); - assertTrue(lat == 43.73084185257864 && lon == 7.420749379140277 && ele == 59.0, "nearest point wasn't correct: lat=" + lat + ", lon=" + lon + ", ele=" + ele); + assertTrue(lat == 43.73084185257864 && lon == 7.420749379140277 && ele == 58.80088609718373, "nearest point wasn't correct: lat=" + lat + ", lon=" + lon + ", ele=" + ele); } @Test diff --git a/web/src/test/java/com/graphhopper/application/resources/PtIsochroneTest.java b/web/src/test/java/com/graphhopper/application/resources/PtIsochroneTest.java index f115bb76b4b..8805ff00219 100644 --- a/web/src/test/java/com/graphhopper/application/resources/PtIsochroneTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/PtIsochroneTest.java @@ -21,8 +21,8 @@ import com.graphhopper.application.GraphHopperApplication; import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; -import com.graphhopper.config.Profile; import com.graphhopper.resources.PtIsochroneResource; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -34,12 +34,12 @@ import org.locationtech.jts.geom.Geometry; import org.locationtech.jts.geom.GeometryFactory; -import javax.ws.rs.client.Invocation; -import javax.ws.rs.client.WebTarget; +import jakarta.ws.rs.client.Invocation; +import jakarta.ws.rs.client.WebTarget; import java.io.File; import java.time.LocalDateTime; import java.time.ZoneId; -import java.util.Collections; +import java.util.List; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -55,12 +55,12 @@ public class PtIsochroneTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); - config.getGraphHopperConfiguration() - .putObject("graph.vehicles", "foot") - .putObject("graph.location", GRAPH_LOC) - .putObject("gtfs.file", "../reader-gtfs/files/sample-feed") - .putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList(new Profile("foot").setVehicle("foot"))); + config.getGraphHopperConfiguration(). + putObject("graph.location", GRAPH_LOC). + putObject("gtfs.file", "../reader-gtfs/files/sample-feed"). + putObject("import.osm.ignored_highways", ""). + putObject("graph.encoded_values", "foot_access, foot_priority, foot_average_speed"). + setProfiles(List.of(TestProfiles.accessSpeedAndPriority("foot"))); Helper.removeDir(new File(GRAPH_LOC)); return config; } diff --git a/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTest.java index d536ce7a9ea..68b35a31a4b 100644 --- a/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTest.java @@ -23,7 +23,10 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.Profile; +import com.graphhopper.gtfs.Request; import com.graphhopper.resources.InfoResource; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -32,10 +35,12 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.core.Response; import java.io.File; -import java.util.Collections; +import java.time.LocalDateTime; +import java.util.List; +import static com.graphhopper.application.resources.Util.getWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.*; @@ -55,7 +60,8 @@ private static GraphHopperServerConfiguration createConfig() { putObject("gtfs.file", "../reader-gtfs/files/sample-feed"). putObject("graph.location", DIR). putObject("import.osm.ignored_highways", ""). - setProfiles(Collections.singletonList(new Profile("foot").setVehicle("foot"))); + putObject("graph.encoded_values", "foot_access, foot_priority, foot_average_speed"). + setProfiles(List.of(TestProfiles.accessSpeedAndPriority("foot"))); return config; } @@ -67,94 +73,86 @@ public static void cleanUp() { @Test public void testStationStationQuery() { - final Response response = clientTarget(app, "/route") + JsonNode jsonNode = clientTarget(app, "/route") .queryParam("point", "Stop(NADAV)") .queryParam("point", "Stop(NANAA)") .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "2007-01-01T15:44:00Z") - .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode jsonNode = response.readEntity(JsonNode.class); + .request().get(JsonNode.class); assertEquals(1, jsonNode.at("/paths/0/legs").size()); } @Test public void testStationStationQueryWithOffsetDateTime() { - final Response response = clientTarget(app, "/route") + JsonNode jsonNode = clientTarget(app, "/route") .queryParam("point", "Stop(NADAV)") .queryParam("point", "Stop(NANAA)") .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "2007-01-01T07:44:00-08:00") - .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode jsonNode = response.readEntity(JsonNode.class); + .request().get(JsonNode.class); assertEquals(1, jsonNode.at("/paths/0/legs").size()); } @Test public void testPointPointQuery() { - final Response response = clientTarget(app, "/route") + JsonNode jsonNode = clientTarget(app, "/route") .queryParam("point", "36.914893,-116.76821") // NADAV stop .queryParam("point", "36.914944,-116.761472") //NANAA stop .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "2007-01-01T15:44:00Z") - .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode jsonNode = response.readEntity(JsonNode.class); + .request().get(JsonNode.class); assertEquals(1, jsonNode.at("/paths/0/legs").size()); } @Test public void testWalkQuery() { - final Response response = clientTarget(app, "/route") + GHResponse ghResponse = clientTarget(app, "/route") .queryParam("point", "36.914893,-116.76821") .queryParam("point", "36.914944,-116.761472") .queryParam("profile", "foot") - .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - GHResponse ghResponse = response.readEntity(GHResponse.class); + .request().get(GHResponse.class); assertFalse(ghResponse.hasErrors()); } @Test public void testNoPoints() { - final Response response = clientTarget(app, "/route") + BodyAndStatus response = getWithStatus(clientTarget(app, "/route") .queryParam("profile", "pt") - .request().buildGet().invoke(); + ); assertEquals(400, response.getStatus()); } @Test public void testOnePoint() { - final Response response = clientTarget(app, "/route") + BodyAndStatus response = getWithStatus(clientTarget(app, "/route") .queryParam("point", "36.914893,-116.76821") .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "2007-01-01T08:00:00Z") - .request().buildGet().invoke(); + ); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertEquals("query param point size must be between 2 and 2", json.get("message").asText()); } @Test public void testBadPoints() { - final Response response = clientTarget(app, "/route") + BodyAndStatus response = getWithStatus(clientTarget(app, "/route") .queryParam("point", "pups") .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "2007-01-01T08:00:00Z") - .request().buildGet().invoke(); + ); assertEquals(400, response.getStatus()); } @Test public void testNoTime() { - final Response response = clientTarget(app, "/route") + BodyAndStatus response = getWithStatus(clientTarget(app, "/route") .queryParam("point", "36.914893,-116.76821") // NADAV stop .queryParam("point", "36.914944,-116.761472") //NANAA stop .queryParam("profile", "pt") - .request().buildGet().invoke(); + ); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); // Would prefer a "must not be null" message here, but is currently the same as for a bad time (see below). // I DO NOT want to manually catch this, I want to figure out how to fix this upstream, or live with it. assertTrue(json.get("message").asText().startsWith("query param pt.earliest_departure_time must")); @@ -162,23 +160,38 @@ public void testNoTime() { @Test public void testBadTime() { - final Response response = clientTarget(app, "/route") + BodyAndStatus response = getWithStatus(clientTarget(app, "/route") .queryParam("point", "36.914893,-116.76821") // NADAV stop .queryParam("point", "36.914944,-116.761472") //NANAA stop .queryParam("profile", "pt") .queryParam("pt.earliest_departure_time", "wurst") - .request().buildGet().invoke(); + ); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertEquals("query param pt.earliest_departure_time must be in a ISO-8601 format.", json.get("message").asText()); } @Test - public void testInfo() { - final Response response = clientTarget(app, "/info") + public void testProfileQuery() { + final Response response = clientTarget(app, "/route") + .queryParam("point", "36.91311729030539,-116.76769495010377") + .queryParam("point", "36.91260259593356,-116.76149368286134") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T06:40:00-08:00") + .queryParam("pt.profile", true) + .queryParam("pt.profile_duration", "PT180M") .request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - InfoResource.Info info = response.readEntity(InfoResource.Info.class); + + JsonNode json = response.readEntity(JsonNode.class); + for (JsonNode path : json.at("/paths")) { + System.out.printf("%s %s %s %s\n", path.at("/time"), path.at("/legs/0/departure_time"), path.at("/legs/1/stops/0/stop_id"), path.at("/legs/2/arrival_time")); + } + } + + @Test + public void testInfo() { + InfoResource.Info info = clientTarget(app, "/info") + .request().get(InfoResource.Info.class); assertTrue(info.profiles.stream().anyMatch(p -> p.name.equals("pt"))); } diff --git a/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTripBasedTest.java b/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTripBasedTest.java new file mode 100644 index 00000000000..8742a5d80be --- /dev/null +++ b/web/src/test/java/com/graphhopper/application/resources/PtRouteResourceTripBasedTest.java @@ -0,0 +1,207 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.application.resources; + +import com.fasterxml.jackson.databind.JsonNode; +import com.graphhopper.GHResponse; +import com.graphhopper.application.GraphHopperApplication; +import com.graphhopper.application.GraphHopperServerConfiguration; +import com.graphhopper.application.util.GraphHopperServerTestConfiguration; +import com.graphhopper.config.Profile; +import com.graphhopper.resources.InfoResource; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.Helper; +import io.dropwizard.testing.junit5.DropwizardAppExtension; +import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import jakarta.ws.rs.core.Response; +import java.io.File; +import java.util.Collections; + +import static com.graphhopper.application.util.TestUtils.clientTarget; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Tests the entire app, not the resource, so that the plugging-together + * of stuff (which is different for PT than for the rest) is under test, too. + */ +@ExtendWith(DropwizardExtensionsSupport.class) +public class PtRouteResourceTripBasedTest { + private static final String DIR = "./target/gtfs-app-gh/"; + public static final DropwizardAppExtension app = new DropwizardAppExtension<>(GraphHopperApplication.class, createConfig()); + + private static GraphHopperServerConfiguration createConfig() { + GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); + config.getGraphHopperConfiguration(). + putObject("datareader.file", "../reader-gtfs/files/beatty.osm"). + putObject("gtfs.file", "../reader-gtfs/files/sample-feed"). + putObject("graph.location", DIR). + putObject("import.osm.ignored_highways", ""). + putObject("gtfs.trip_based", true). + putObject("gtfs.schedule_day", "2007-01-01"). + setProfiles(Collections.singletonList(TestProfiles.accessSpeedAndPriority("foot"))) + .putObject("graph.encoded_values", "foot_access, foot_priority, foot_average_speed, car_access, car_average_speed"); + return config; + } + + @BeforeAll + @AfterAll + public static void cleanUp() { + Helper.removeDir(new File(DIR)); + } + + @Test + public void testStationStationQuery() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "Stop(NADAV)") + .queryParam("point", "Stop(NANAA)") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T15:44:00Z") + .request().buildGet().invoke(); + assertEquals(200, response.getStatus()); + JsonNode jsonNode = response.readEntity(JsonNode.class); + assertEquals(1, jsonNode.at("/paths/0/legs").size()); + } + + @Test + public void testStationStationQueryWithOffsetDateTime() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "Stop(NADAV)") + .queryParam("point", "Stop(NANAA)") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T07:44:00-08:00") + .request().buildGet().invoke(); + assertEquals(200, response.getStatus()); + JsonNode jsonNode = response.readEntity(JsonNode.class); + assertEquals(1, jsonNode.at("/paths/0/legs").size()); + } + + @Test + public void testPointPointQuery() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.914893,-116.76821") // NADAV stop + .queryParam("point", "36.914944,-116.761472") //NANAA stop + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T15:44:00Z") + .request().buildGet().invoke(); + assertEquals(200, response.getStatus()); + JsonNode jsonNode = response.readEntity(JsonNode.class); + System.out.println(jsonNode.toPrettyString()); + assertEquals(1, jsonNode.at("/paths/0/legs").size()); + } + + @Test + public void testWalkQuery() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.914893,-116.76821") + .queryParam("point", "36.914944,-116.761472") + .queryParam("profile", "foot") + .request().buildGet().invoke(); + assertEquals(200, response.getStatus()); + GHResponse ghResponse = response.readEntity(GHResponse.class); + assertFalse(ghResponse.hasErrors()); + } + + @Test + public void testNoPoints() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("profile", "pt") + .request().buildGet().invoke(); + assertEquals(400, response.getStatus()); + } + + @Test + public void testOnePoint() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.914893,-116.76821") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T08:00:00Z") + .request().buildGet().invoke(); + assertEquals(400, response.getStatus()); + JsonNode json = response.readEntity(JsonNode.class); + assertEquals("query param point size must be between 2 and 2", json.get("message").asText()); + } + + @Test + public void testBadPoints() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "pups") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T08:00:00Z") + .request().buildGet().invoke(); + assertEquals(400, response.getStatus()); + } + + @Test + public void testNoTime() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.914893,-116.76821") // NADAV stop + .queryParam("point", "36.914944,-116.761472") //NANAA stop + .queryParam("profile", "pt") + .request().buildGet().invoke(); + assertEquals(400, response.getStatus()); + JsonNode json = response.readEntity(JsonNode.class); + // Would prefer a "must not be null" message here, but is currently the same as for a bad time (see below). + // I DO NOT want to manually catch this, I want to figure out how to fix this upstream, or live with it. + assertTrue(json.get("message").asText().startsWith("query param pt.earliest_departure_time must")); + } + + @Test + public void testBadTime() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.914893,-116.76821") // NADAV stop + .queryParam("point", "36.914944,-116.761472") //NANAA stop + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "wurst") + .request().buildGet().invoke(); + assertEquals(400, response.getStatus()); + JsonNode json = response.readEntity(JsonNode.class); + assertEquals("query param pt.earliest_departure_time must be in a ISO-8601 format.", json.get("message").asText()); + } + + @Test + public void testProfileQuery() { + final Response response = clientTarget(app, "/route").queryParam("pt.algorithm", "trip_based") + .queryParam("point", "36.91311729030539,-116.76769495010377") + .queryParam("point", "36.91260259593356,-116.76149368286134") + .queryParam("profile", "pt") + .queryParam("pt.earliest_departure_time", "2007-01-01T06:40:00-08:00") + .queryParam("pt.profile", true) + .queryParam("pt.profile_duration", "PT180M") + .request().buildGet().invoke(); + + JsonNode json = response.readEntity(JsonNode.class); + for (JsonNode path : json.at("/paths")) { + System.out.printf("%s %s %s %s\n", path.at("/time"), path.at("/legs/0/departure_time"), path.at("/legs/1/stops/0/stop_id"), path.at("/legs/2/arrival_time")); + } + } + + @Test + public void testInfo() { + final Response response = clientTarget(app, "/info") + .request().buildGet().invoke(); + assertEquals(200, response.getStatus()); + InfoResource.Info info = response.readEntity(InfoResource.Info.class); + assertTrue(info.profiles.stream().anyMatch(p -> p.name.equals("pt"))); + } + +} diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java index b5996af6bd0..ba31bdb3709 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceClientHCTest.java @@ -26,7 +26,7 @@ import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.application.util.TestUtils; import com.graphhopper.config.CHProfile; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.*; import com.graphhopper.util.details.PathDetail; import com.graphhopper.util.exceptions.PointNotFoundException; @@ -61,20 +61,21 @@ public class RouteResourceClientHCTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car,bike"). putObject("prepare.min_network_size", 0). putObject("graph.elevation.provider", "srtm"). + putObject("graph.elevation.clear", true). putObject("graph.elevation.cache_dir", "../core/files/"). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("graph.encoded_values", "road_class,surface,road_environment,max_speed"). putObject("import.osm.ignored_highways", ""). - putObject("graph.location", DIR) - .setProfiles(Arrays.asList( - new Profile("car").setVehicle("car"), - new Profile("bike").setVehicle("bike"), - new Profile("my_custom_car").setVehicle("car") - )) - .setCHProfiles(Arrays.asList(new CHProfile("car"), new CHProfile("bike"))); + putObject("graph.location", DIR). + putObject("graph.encoded_values", "road_class, surface, road_environment, max_speed, car_access, car_average_speed, bike_access, bike_priority, bike_average_speed"). + setProfiles(Arrays.asList( + TestProfiles.accessAndSpeed("car"), + TestProfiles.accessSpeedAndPriority("bike"), + TestProfiles.accessAndSpeed("my_custom_car", "car") + )). + setCHProfiles(Arrays.asList(new CHProfile("car"), new CHProfile("bike"))); return config; } @@ -118,11 +119,11 @@ public void testSimpleRoute(TestParam p) { GHResponse rsp = gh.route(req); assertFalse(rsp.hasErrors(), "errors:" + rsp.getErrors().toString()); ResponsePath res = rsp.getBest(); - isBetween(60, 70, res.getPoints().size()); + isBetween(70, 80, res.getPoints().size()); isBetween(2900, 3000, res.getDistance()); - isBetween(110, 120, res.getAscend()); - isBetween(70, 80, res.getDescend()); - isBetween(190, 200, res.getRouteWeight()); + isBetween(120, 130, res.getAscend()); + isBetween(75, 85, res.getDescend()); + isBetween(1900, 2000, res.getRouteWeight()); // change vehicle rsp = gh.route(new GHRequest(42.5093, 1.5274, 42.5126, 1.5410). @@ -152,13 +153,13 @@ public void testAlternativeRoute(TestParam p) { assertEquals(2, paths.size()); ResponsePath path = paths.get(0); - assertEquals(35, path.getPoints().size()); - assertEquals(1689, path.getDistance(), 1); + assertEquals(52, path.getPoints().size()); + assertEquals(1690, path.getDistance(), 1); assertTrue(path.getInstructions().toString().contains("Avinguda de Tarragona"), path.getInstructions().toString()); path = paths.get(1); - assertEquals(30, path.getPoints().size()); - assertEquals(1759, path.getDistance(), 1); + assertEquals(35, path.getPoints().size()); + assertEquals(1763, path.getDistance(), 1); assertTrue(path.getInstructions().toString().contains("Avinguda Prat de la Creu"), path.getInstructions().toString()); } @@ -388,6 +389,8 @@ public void testWaypointIndicesAndLegDetails(TestParam p) { GraphHopperWeb gh = createGH(p); List legDetails = Arrays.asList("leg_time", "leg_distance", "leg_weight"); GHRequest req = new GHRequest(). + addPoint(new GHPoint(42.509141, 1.546063)). + // #2915: duplicating the first point yields an empty leg, but there should still be path details for it addPoint(new GHPoint(42.509141, 1.546063)). addPoint(new GHPoint(42.507173, 1.531902)). addPoint(new GHPoint(42.505435, 1.515943)). @@ -404,32 +407,32 @@ public void testWaypointIndicesAndLegDetails(TestParam p) { GHResponse response = gh.route(req); ResponsePath path = response.getBest(); - assertEquals(5428, path.getDistance(), 5); - assertEquals(10, path.getWaypoints().size()); + assertEquals(5158, path.getDistance(), 5); + assertEquals(11, path.getWaypoints().size()); assertEquals(path.getTime(), path.getPathDetails().get("leg_time").stream().mapToLong(d -> (long) d.getValue()).sum(), 1); assertEquals(path.getDistance(), path.getPathDetails().get("leg_distance").stream().mapToDouble(d -> (double) d.getValue()).sum(), 1); assertEquals(path.getRouteWeight(), path.getPathDetails().get("leg_weight").stream().mapToDouble(d -> (double) d.getValue()).sum(), 1); - assertEquals(9, path.getPathDetails().get("leg_time").size()); - assertEquals(9, path.getPathDetails().get("leg_distance").size()); - assertEquals(9, path.getPathDetails().get("leg_weight").size()); + assertEquals(10, path.getPathDetails().get("leg_time").size()); + assertEquals(10, path.getPathDetails().get("leg_distance").size()); + assertEquals(10, path.getPathDetails().get("leg_weight").size()); List pointListFromInstructions = getPointListFromInstructions(path); for (String detail : legDetails) { List pathDetails = path.getPathDetails().get(detail); // explicitly check one of the waypoints - assertEquals(42.50539, path.getWaypoints().get(2).lat); - assertEquals(42.50539, path.getPoints().get(pathDetails.get(1).getLast()).getLat()); - assertEquals(42.50539, path.getPoints().get(pathDetails.get(2).getFirst()).getLat()); + assertEquals(42.505398, path.getWaypoints().get(3).lat); + assertEquals(42.505398, path.getPoints().get(pathDetails.get(2).getLast()).getLat()); + assertEquals(42.505398, path.getPoints().get(pathDetails.get(3).getFirst()).getLat()); // check all the waypoints assertEquals(path.getWaypoints().get(0), path.getPoints().get(pathDetails.get(0).getFirst())); for (int i = 1; i < path.getWaypoints().size(); ++i) assertEquals(path.getWaypoints().get(i), path.getPoints().get(pathDetails.get(i - 1).getLast())); List pointListFromLegDetails = getPointListFromLegDetails(path, detail); - assertEquals(9, pointListFromLegDetails.size()); + assertEquals(10, pointListFromLegDetails.size()); assertPointListsEquals(pointListFromInstructions, pointListFromLegDetails); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelLMTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelLMTest.java index 6b8685bde23..1c54ca6a90c 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelLMTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelLMTest.java @@ -22,8 +22,7 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; -import com.graphhopper.util.CustomModel; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -32,8 +31,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Response; +import jakarta.ws.rs.client.Entity; import java.io.File; import java.util.Arrays; @@ -52,15 +50,14 @@ public class RouteResourceCustomModelLMTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car,foot"). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("graph.location", DIR). putObject("import.osm.ignored_highways", ""). - putObject("graph.encoded_values", "surface"). + putObject("graph.encoded_values", "surface, car_access, car_average_speed, foot_access, foot_priority, foot_average_speed"). setProfiles(Arrays.asList( - new Profile("car_custom").setCustomModel(new CustomModel().setDistanceInfluence(15d)).setVehicle("car"), - new Profile("foot_profile").setVehicle("foot"), - new Profile("foot_custom").setVehicle("foot"))). + TestProfiles.accessAndSpeed("car_custom", "car"), + TestProfiles.accessSpeedAndPriority("foot_profile", "foot"), + TestProfiles.accessSpeedAndPriority("foot_custom", "foot"))). setLMProfiles(Arrays.asList(new LMProfile("car_custom"), new LMProfile("foot_custom"))); return config; } @@ -77,8 +74,7 @@ public void testBasic() { " \"points\": [[1.518946,42.531453],[1.54006,42.511178]]," + " \"profile\": \"car_custom\"" + "}"; - Response response = query(jsonQuery, 200); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = query(jsonQuery); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); JsonNode path = json.get("paths").get(0); @@ -92,7 +88,7 @@ public void testWithRules() { " \"profile\": \"car_custom\", \"custom_model\":{" + " \"priority\": [{\"if\": \"road_class != SECONDARY\", \"multiply_by\": 0.5}]}" + "}"; - JsonNode jsonNode = query(body, 200).readEntity(JsonNode.class); + JsonNode jsonNode = query(body); JsonNode path = jsonNode.get("paths").get(0); assertEquals(path.get("distance").asDouble(), 1317, 5); @@ -106,7 +102,7 @@ public void testWithRules() { "{\"else\": \"\", \"multiply_by\": 0.66}" + "]}" + "}"; - jsonNode = query(body, 200).readEntity(JsonNode.class); + jsonNode = query(body); path = jsonNode.get("paths").get(0); assertEquals(path.get("distance").asDouble(), 1707, 5); } @@ -121,7 +117,7 @@ public void testAvoidTunnels() { " ]" + "}" + "}"; - JsonNode jsonNode = query(body, 200).readEntity(JsonNode.class); + JsonNode jsonNode = query(body); JsonNode path = jsonNode.get("paths").get(0); assertEquals(path.get("distance").asDouble(), 2437, 5); } @@ -134,14 +130,13 @@ public void testSimplisticWheelchair() { "\"priority\":[" + " {\"if\": \"road_class == STEPS\", \"multiply_by\": 0}]}" + "}"; - JsonNode jsonNode = query(body, 200).readEntity(JsonNode.class); + JsonNode jsonNode = query(body); JsonNode path = jsonNode.get("paths").get(0); assertEquals(path.get("distance").asDouble(), 328, 5); } - Response query(String body, int code) { - Response response = clientTarget(app, "/route").request().post(Entity.json(body)); - assertEquals(code, response.getStatus()); - return response; + JsonNode query(String body) { + JsonNode json = clientTarget(app, "/route").request().post(Entity.json(body), JsonNode.class); + return json; } } diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java index bbb623cc16a..46c3c4ad0ae 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java @@ -24,9 +24,11 @@ import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.CHProfile; import com.graphhopper.config.Profile; -import com.graphhopper.util.CustomModel; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; +import com.graphhopper.util.TurnCostsConfig; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import org.junit.jupiter.api.AfterAll; @@ -36,17 +38,17 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Response; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.util.Arrays; +import java.util.List; +import static com.graphhopper.application.resources.Util.postWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; +import static com.graphhopper.json.Statement.ElseIf; import static com.graphhopper.json.Statement.If; -import static com.graphhopper.json.Statement.Op.LIMIT; -import static com.graphhopper.json.Statement.Op.MULTIPLY; +import static com.graphhopper.json.Statement.Op.*; import static org.junit.jupiter.api.Assertions.*; @ExtendWith(DropwizardExtensionsSupport.class) @@ -58,44 +60,49 @@ public class RouteResourceCustomModelTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "bike,car,foot,roads"). putObject("prepare.min_network_size", 200). putObject("datareader.file", "../core/files/north-bayreuth.osm.gz"). putObject("graph.location", DIR). - putObject("graph.encoded_values", "max_height,max_weight,max_width,hazmat,toll,surface,track_type,hgv,average_slope,max_slope,bus_access"). + putObject("graph.elevation.provider", "srtm"). + putObject("graph.elevation.clear", true). + putObject("graph.elevation.cache_dir", "../core/files/"). putObject("custom_areas.directory", "./src/test/resources/com/graphhopper/application/resources/areas"). putObject("import.osm.ignored_highways", ""). - setProfiles(Arrays.asList( - new Profile("wheelchair"), - new Profile("roads").setCustomModel(new CustomModel()).setVehicle("roads"), - new Profile("car").setCustomModel(new CustomModel().setDistanceInfluence(70d)).setVehicle("car"), - new Profile("car_with_area").setCustomModel(new CustomModel().addToPriority(If("in_external_area52", MULTIPLY, "0.05"))), - new Profile("bike").setCustomModel(new CustomModel().setDistanceInfluence(0d)).setVehicle("bike"), - new Profile("bike_fastest").setVehicle("bike"), - new Profile("bus").setCustomModel(null).setVehicle("roads").putHint("custom_model_files", Arrays.asList("bus.json")), - new Profile("cargo_bike").setCustomModel(null).setVehicle("bike"). - putHint("custom_model_files", Arrays.asList("cargo_bike.json")), - new Profile("json_bike").setCustomModel(null).setVehicle("roads"). - putHint("custom_model_files", Arrays.asList("bike.json", "bike_elevation.json")), - new Profile("foot_profile").setVehicle("foot"), - new Profile("car_no_unclassified").setCustomModel( - new CustomModel(new CustomModel(). - addToPriority(If("road_class == UNCLASSIFIED", LIMIT, "0")))). - setVehicle("car"), + putObject("graph.encoded_values", "car_access, car_average_speed, road_access, max_speed, " + + "bike_access, bike_priority, bike_average_speed, bike_road_access, bike_network, " + + "foot_access, foot_priority, foot_average_speed, foot_road_access, " + + "max_height, max_weight, max_width, hazmat, toll, surface, track_type, hgv, " + + "average_slope, max_slope, bus_access, road_class, get_off_bike, roundabout, " + + "country, orientation, mtb_rating, hike_rating, road_environment,ferry_speed"). + setProfiles(List.of( + TestProfiles.constantSpeed("roads", 120), + new Profile("car").setCustomModel(TestProfiles.accessAndSpeed("unused", "car"). + getCustomModel().setDistanceInfluence(70d)), + new Profile("car_tc_left").setCustomModel(TestProfiles.accessAndSpeed("car_tc_left", "car"). + getCustomModel().setDistanceInfluence(70d). + addToTurnPenalty(If("change_angle <= -25 && change_angle > -80", ADD, "100")). + addToTurnPenalty(ElseIf("change_angle <= -80 && change_angle >= -180", ADD, "100"))). + setTurnCostsConfig(new TurnCostsConfig(List.of("motor_vehicle"))), + new Profile("car_with_area").setCustomModel(TestProfiles.accessAndSpeed("unused", "car"). + getCustomModel().addToPriority(If("in_external_area52", MULTIPLY, "0.05"))), + TestProfiles.accessSpeedAndPriority("bike"), + new Profile("bus").setCustomModel(null).putHint("custom_model_files", List.of("bus.json")), + new Profile("cargo_bike").setCustomModel(null).putHint("custom_model_files", List.of("cargo_bike.json")), + new Profile("json_bike").setCustomModel(null).putHint("custom_model_files", List.of("bike.json", "bike_elevation.json")), + TestProfiles.accessSpeedAndPriority("foot_profile", "foot"), + new Profile("car_no_unclassified").setCustomModel(TestProfiles.accessAndSpeed("unused", "car").getCustomModel(). + addToPriority(If("road_class == UNCLASSIFIED", LIMIT, "0"))), new Profile("custom_bike"). - setCustomModel(new CustomModel(). + setCustomModel(TestProfiles.accessSpeedAndPriority("unused", "bike").getCustomModel(). addToSpeed(If("road_class == PRIMARY", LIMIT, "28")). - addToPriority(If("max_width < 1.2", MULTIPLY, "0"))). - setVehicle("bike"), + addToPriority(If("max_width < 1.2", MULTIPLY, "0"))), new Profile("custom_bike2").setCustomModel( - new CustomModel(new CustomModel().setDistanceInfluence(70d). - addToPriority(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "0")))). - setVehicle("bike"), - new Profile("custom_bike3").setCustomModel( - new CustomModel(new CustomModel(). - addToSpeed(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "10")). - addToSpeed(If("true", LIMIT, "40")))). - setVehicle("bike"))). + TestProfiles.accessSpeedAndPriority("unused", "bike").getCustomModel().setDistanceInfluence(70d). + addToPriority(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "0"))), + new Profile("custom_bike3").setCustomModel(TestProfiles.accessSpeedAndPriority("unused", "bike").getCustomModel(). + addToSpeed(If("road_class == TERTIARY || road_class == TRACK", MULTIPLY, "10")). + addToSpeed(If("true", LIMIT, "40"))) + )). setCHProfiles(Arrays.asList(new CHProfile("bus"), new CHProfile("car_no_unclassified"))); return config; } @@ -109,7 +116,7 @@ public static void cleanUp() { @Test public void testBlockAreaNotAllowed() { String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"car\", \"block_area\": \"abc\", \"ch.disable\": true}"; - JsonNode jsonNode = query(body, 400).readEntity(JsonNode.class); + JsonNode jsonNode = query(body, 400); assertMessageStartsWith(jsonNode, "The `block_area` parameter is no longer supported. Use a custom model with `areas` instead."); } @@ -123,45 +130,98 @@ public void testBus() { } @Test - public void testRoadsFlagEncoder() { + public void testRoads() { String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"roads\", \"ch.disable\": true, " + "\"custom_model\": {" + - " \"speed\": [{\"if\": \"road_class == PRIMARY\", \"multiply_by\": 0.9}, " + - " {\"if\": \"true\", \"limit_to\": 120}" + - " ]" + + " \"speed\": [" + + " { \"if\": \"road_class == PRIMARY\", \"multiply_by\": 0.9 }, " + + " { \"if\": \"true\", \"limit_to\": 120 }" + + " ]" + "}}"; JsonNode path = getPath(body); assertEquals(path.get("distance").asDouble(), 660, 10); assertEquals(path.get("time").asLong(), 20_000, 1_000); } + @Test + public void testBlock() { + String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"roads\", \"ch.disable\": true, " + + "\"custom_model\": {" + + " \"speed\": [" + + " { \"if\": \"country == DEU\"," + + " \"do\": [" + + " { \"if\": \"road_class == PRIMARY\", \"multiply_by\": 0.9 }, " + + " { \"if\": \"true\", \"limit_to\": 105 }" + + " ]" + + " }, " + + " { \"else_if\": \"country == FRA\"," + + " \"do\": [" + + " { \"if\": \"road_class == PRIMARY\", \"multiply_by\": 0.8 }, " + + " { \"else\": \"\", \"limit_to\": 110 }" + + " ]" + + " }, " + + " { \"else\": \"\"," + + " \"do\": [" + + " { \"if\": \"road_class == PRIMARY\", \"multiply_by\": 0.7 }, " + + " { \"if\": \"true\", \"limit_to\": 115 }" + + " ]" + + " }" + + " ]" + + "}}"; + JsonNode path = getPath(body); + assertEquals(660, path.get("distance").asDouble(), 10); + assertEquals(22_680, path.get("time").asLong(), 1_000); + + body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"roads\", \"ch.disable\": true, " + + "\"custom_model\": {\n" + + " \"speed\": [\n" + + " {\n" + + " \"if\": \"false\",\n" + + " \"limit_to\": \"20\"\n" + + " },\n" + + " {\n" + + " \"else\": \"\",\n" + + " \"do\": [\n" + + " {\n" + + " \"if\": \"true\",\n" + + " \"limit_to\": 255\n" + + " }\n" + + " ]\n" + + " }\n" + + " ]\n" + + "}\n}"; + path = getPath(body); + assertEquals(660, path.get("distance").asDouble(), 10); + assertEquals(19800, path.get("time").asLong(), 1_000); + } + @Test public void testMissingProfile() { String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"custom_model\": {}, \"ch.disable\": true}"; - JsonNode jsonNode = query(body, 400).readEntity(JsonNode.class); + JsonNode jsonNode = query(body, 400); assertMessageStartsWith(jsonNode, "The 'profile' parameter is required when you use the `custom_model` parameter"); } @Test public void testUnknownProfile() { String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"unknown\", \"custom_model\": {}, \"ch.disable\": true}"; - JsonNode jsonNode = query(body, 400).readEntity(JsonNode.class); + JsonNode jsonNode = query(body, 400); assertMessageStartsWith(jsonNode, "The requested profile 'unknown' does not exist.\nAvailable profiles: "); } @Test public void testVehicleAndWeightingNotAllowed() { String body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"truck\",\"custom_model\": {}, \"ch.disable\": true, \"vehicle\": \"truck\"}"; - JsonNode jsonNode = query(body, 400).readEntity(JsonNode.class); + JsonNode jsonNode = query(body, 400); assertEquals("The 'vehicle' parameter is no longer supported. You used 'vehicle=truck'", jsonNode.get("message").asText()); body = "{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"truck\",\"custom_model\": {}, \"ch.disable\": true, \"weighting\": \"custom\"}"; - jsonNode = query(body, 400).readEntity(JsonNode.class); + jsonNode = query(body, 400); assertEquals("The 'weighting' parameter is no longer supported. You used 'weighting=custom'", jsonNode.get("message").asText()); } @ParameterizedTest - @CsvSource(value = {"0.05,3073", "0.5,1498"}) + @CsvSource(value = {"0.05,3093", "0.5,1498"}) public void testAvoidArea(double priority, double expectedDistance) { String bodyFragment = "\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"car\", \"ch.disable\": true"; JsonNode path = getPath("{" + bodyFragment + ", \"custom_model\": {}}"); @@ -189,7 +249,7 @@ public void testAvoidArea(double priority, double expectedDistance) { @Test public void testAvoidArea() { JsonNode path = getPath("{\"points\": [[11.58199, 50.0141], [11.5865, 50.0095]], \"profile\": \"car_with_area\", \"ch.disable\": true, \"custom_model\": {}}"); - assertEquals(path.get("distance").asDouble(), 3073, 10); + assertEquals(path.get("distance").asDouble(), 3093, 10); } @Test @@ -228,21 +288,12 @@ public void testBikeWithPriority() { String coords = " \"points\": [[11.539421, 50.018274], [11.593966, 50.007739]],"; String jsonQuery = "{" + coords + - " \"profile\": \"bike_fastest\"," + + " \"profile\": \"bike\"," + " \"ch.disable\": true" + "}"; JsonNode path = getPath(jsonQuery); double expectedDistance = path.get("distance").asDouble(); - assertEquals(4781, expectedDistance, 10); - - // base profile bike has to use distance_influence = 0 (unlike default) otherwise not comparable to "fastest" - jsonQuery = "{" + - coords + - " \"profile\": \"bike\"," + - " \"ch.disable\": true" + - "}"; - path = getPath(jsonQuery); - assertEquals(4781, path.get("distance").asDouble(), 10); + assertEquals(4833, expectedDistance, 10); } @Test @@ -265,14 +316,14 @@ public void testSubnetworkRemovalPerProfile() { " \"ch.disable\": true" + "}"; JsonNode path = getPath(body); - assertEquals(8754, path.get("distance").asDouble(), 5); + assertEquals(8772, path.get("distance").asDouble(), 10); // CH body = "{\"points\": [[11.556416,50.007739], [11.528864,50.021638]]," + " \"profile\": \"car_no_unclassified\"" + "}"; path = getPath(body); - assertEquals(8754, path.get("distance").asDouble(), 5); + assertEquals(8772, path.get("distance").asDouble(), 10); // different profile body = "{\"points\": [[11.494446, 50.027814], [11.511483, 49.987628]]," + @@ -280,7 +331,7 @@ public void testSubnetworkRemovalPerProfile() { " \"ch.disable\": true" + "}"; path = getPath(body); - assertEquals(5827, path.get("distance").asDouble(), 5); + assertEquals(5838, path.get("distance").asDouble(), 10); } @Test @@ -318,17 +369,6 @@ public void customBikeWithHighSpeed() { assertEquals(77, path.get("time").asLong() / 1000, 1); } - @Test - public void wheelchair() { - String jsonQuery = "{" + - " \"points\": [[11.58199, 50.0141], [11.5865, 50.0095]]," + - " \"profile\": \"wheelchair\"," + - " \"ch.disable\": true" + - "}"; - JsonNode path = getPath(jsonQuery); - assertEquals(1500, path.get("distance").asDouble(), 10); - } - @Test public void testHgv() { String body = "{\"points\": [[11.603998, 50.014554], [11.594095, 50.023334]], \"profile\": \"roads\", \"ch.disable\":true," + @@ -336,8 +376,31 @@ public void testHgv() { " \"speed\": [{\"if\":\"true\", \"limit_to\":\"car_average_speed * 0.9\"}], \n" + " \"priority\": [{\"if\": \"car_access == false || hgv == NO || max_width < 3 || max_height < 4\", \"multiply_by\": \"0\"}]}}"; JsonNode path = getPath(body); - assertEquals(7314, path.get("distance").asDouble(), 10); - assertEquals(943 * 1000, path.get("time").asLong(), 1_000); + assertEquals(7350, path.get("distance").asDouble(), 10); + assertEquals(952 * 1000, path.get("time").asLong(), 1_000); + } + + @Test + public void testTurnCosts() { + String body = "{\"points\": [[11.508198,50.015441], [11.505063,50.01737]], \"profile\": \"car_tc_left\", \"ch.disable\":true }"; + JsonNode path = getPath(body); + assertEquals(1083, path.get("distance").asDouble(), 10); + } + + @Test + public void testTurnCostsAlternativeBug() { + String body = "{\"points\": [[11.503027,49.987546], [11.503149,49.986786]], \"profile\": \"car_tc_left\", \"ch.disable\":true}"; + JsonNode path = getPath(body); + assertEquals(545, path.get("distance").asDouble(), 10); + + body = "{\"points\": [[11.503027,49.987546], [11.503149,49.986786]], \"profile\": \"car_tc_left\", \"ch.disable\":true," + + "\"algorithm\":\"alternative_route\"}"; + JsonNode json = query(body, 200); + assertFalse(json.get("info").has("errors")); + + // TODO LATER: alternative route bug as the two routes are identical!? + assertEquals(2, json.get("paths").size()); + assertEquals(545, json.get("paths").get(0).get("distance").asDouble(), 10); } private void assertMessageStartsWith(JsonNode jsonNode, String message) { @@ -347,17 +410,15 @@ private void assertMessageStartsWith(JsonNode jsonNode, String message) { } JsonNode getPath(String body) { - final Response response = query(body, 200); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = query(body, 200); assertFalse(json.get("info").has("errors")); return json.get("paths").get(0); } - Response query(String body, int code) { - Response response = clientTarget(app, "/route").request().post(Entity.json(body)); - response.bufferEntity(); - JsonNode jsonNode = response.readEntity(JsonNode.class); - assertEquals(code, response.getStatus(), jsonNode.has("message") ? jsonNode.get("message").toString() : "no error message"); - return response; + JsonNode query(String body, int code) { + BodyAndStatus response = postWithStatus(clientTarget(app, "/route"), body); + JsonNode json = response.getBody(); + assertEquals(code, response.getStatus(), json.toPrettyString()); + return json; } } diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceIssue2020Test.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceIssue2020Test.java index 3ffeb757dd3..8ba8bc147a1 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceIssue2020Test.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceIssue2020Test.java @@ -23,7 +23,7 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -32,7 +32,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import javax.ws.rs.core.Response; import java.io.File; import java.util.Collections; @@ -48,13 +47,12 @@ public class RouteResourceIssue2020Test { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "car"). putObject("prepare.lm.split_area_location", "../core/files/split.geo.json"). putObject("datareader.file", "../core/files/north-bayreuth.osm.gz"). - putObject("graph.encoded_values", "road_class,surface,road_environment,max_speed"). + putObject("graph.encoded_values", "road_class,surface,road_environment,max_speed,car_access,car_average_speed"). putObject("import.osm.ignored_highways", ""). putObject("graph.location", DIR). - setProfiles(Collections.singletonList(new Profile("my_car").setVehicle("car"))). + setProfiles(Collections.singletonList(TestProfiles.accessAndSpeed("my_car", "car"))). setLMProfiles(Collections.singletonList(new LMProfile("my_car"))); return config; } @@ -67,10 +65,8 @@ public static void cleanUp() { @Test public void testBasicQuery() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=50.01673,11.49878&point=50.018377,11.502782").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=50.01673,11.49878&point=50.018377,11.502782").request().get(JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); JsonNode path = json.get("paths").get(0); diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceLeipzigTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceLeipzigTest.java index eef496b0b2c..31d816a42ea 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceLeipzigTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceLeipzigTest.java @@ -23,8 +23,8 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.CHProfile; -import com.graphhopper.config.Profile; -import com.graphhopper.util.CustomModel; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -35,14 +35,13 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; -import javax.ws.rs.core.Response; import java.io.File; import java.util.Collections; +import java.util.List; import java.util.Random; +import static com.graphhopper.application.resources.Util.getWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; -import static com.graphhopper.json.Statement.If; -import static com.graphhopper.json.Statement.Op.MULTIPLY; import static com.graphhopper.util.Parameters.Algorithms.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -58,11 +57,10 @@ private static GraphHopperServerConfiguration createConfig() { putObject("prepare.min_network_size", 200). putObject("datareader.file", "../map-matching/files/leipzig_germany.osm.pbf"). putObject("import.osm.ignored_highways", ""). - putObject("graph.location", DIR) - .setProfiles(Collections.singletonList(new Profile("my_car"). - setCustomModel(new CustomModel(). - addToPriority(If("road_access == DESTINATION", MULTIPLY, "0.1"))).setVehicle("car"))) - .setCHProfiles(Collections.singletonList(new CHProfile("my_car"))); + putObject("graph.location", DIR). + putObject("graph.encoded_values", "car_access, car_average_speed"). + setProfiles(List.of(TestProfiles.accessAndSpeed("my_car", "car"))). + setCHProfiles(Collections.singletonList(new CHProfile("my_car"))); return config; } @@ -82,28 +80,26 @@ void testNoErrors() { @ParameterizedTest @CsvSource(value = { - "92,-1,algorithm=" + DIJKSTRA_BI, - "92,-1,algorithm=" + ASTAR_BI, - "30719,1,ch.disable=true&algorithm=" + DIJKSTRA, - "21011,1,ch.disable=true&algorithm=" + ASTAR, - "14720,1,ch.disable=true&algorithm=" + DIJKSTRA_BI, - "10392,1,ch.disable=true&algorithm=" + ASTAR_BI + "88,-1,algorithm=" + DIJKSTRA_BI, + "106,-1,algorithm=" + ASTAR_BI, + "30756,1,ch.disable=true&algorithm=" + DIJKSTRA, + "21147,1,ch.disable=true&algorithm=" + ASTAR, + "14860,1,ch.disable=true&algorithm=" + DIJKSTRA_BI, + "10536,1,ch.disable=true&algorithm=" + ASTAR_BI }) void testTimeout(int expectedVisitedNodes, int timeout, String args) { { // for a long timeout the route calculation works long longTimeout = 10_000; - Response response = clientTarget(app, "/route?timeout_ms=" + longTimeout + "&profile=my_car&point=51.319685,12.335525&point=51.367294,12.434745&" + args).request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode jsonNode = response.readEntity(JsonNode.class); + JsonNode jsonNode = clientTarget(app, "/route?timeout_ms=" + longTimeout + "&profile=my_car&point=51.319685,12.335525&point=51.367294,12.434745&" + args).request().get(JsonNode.class); // by checking the visited nodes we make sure different algorithms are used assertEquals(expectedVisitedNodes, jsonNode.get("hints").get("visited_nodes.sum").asInt()); } { // for a short timeout the route calculation fails, for CH we need to use a negative number, because it is too fast - Response response = clientTarget(app, "/route?timeout_ms=" + timeout + "&profile=my_car&point=51.319685,12.335525&point=51.367294,12.434745&" + args).request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?timeout_ms=" + timeout + "&profile=my_car&point=51.319685,12.335525&point=51.367294,12.434745&" + args)); assertEquals(400, response.getStatus()); - JsonNode jsonNode = response.readEntity(JsonNode.class); + JsonNode jsonNode = response.getBody(); assertTrue(jsonNode.get("message").asText().contains("Connection between locations not found"), jsonNode.get("message").asText()); } } @@ -116,10 +112,9 @@ private static void queryRandomRoutes(int numQueries, double minLat, double maxL double lonFrom = minLon + rnd.nextDouble() * (maxLon - minLon); double latTo = minLat + rnd.nextDouble() * (maxLat - minLat); double lonTo = minLon + rnd.nextDouble() * (maxLon - minLon); - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=" + latFrom + "," + lonFrom + "&point=" + latTo + "," + lonTo).request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode path = response.readEntity(JsonNode.class).get("paths").get(0); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=" + latFrom + "," + lonFrom + "&point=" + latTo + "," + lonTo).request().get(JsonNode.class); + JsonNode path = json.get("paths").get(0); assertTrue(path.get("distance").asDouble() >= 0); } } diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceProfileSelectionTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceProfileSelectionTest.java index 46773311928..5c2479c60c9 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceProfileSelectionTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceProfileSelectionTest.java @@ -24,8 +24,8 @@ import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; -import com.graphhopper.config.Profile; -import com.graphhopper.util.CustomModel; +import com.graphhopper.routing.TestProfiles; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; @@ -35,11 +35,11 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.Response; import java.io.File; import java.util.Arrays; +import static com.graphhopper.application.resources.Util.getWithStatus; +import static com.graphhopper.application.resources.Util.postWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; import static org.junit.jupiter.api.Assertions.*; @@ -51,23 +51,23 @@ public class RouteResourceProfileSelectionTest { private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). - putObject("graph.vehicles", "bike,car,foot"). putObject("prepare.min_network_size", 0). putObject("datareader.file", "../core/files/monaco.osm.gz"). putObject("graph.encoded_values", "road_class,surface,road_environment,max_speed"). putObject("import.osm.ignored_highways", ""). - putObject("graph.location", DIR) - .setProfiles(Arrays.asList( - new Profile("my_car").setVehicle("car"), - new Profile("my_bike").setCustomModel(new CustomModel().setDistanceInfluence(200d)).setVehicle("bike"), - new Profile("my_feet").setVehicle("foot") - )) - .setCHProfiles(Arrays.asList( + putObject("graph.location", DIR). + putObject("graph.encoded_values", "road_class, surface, road_environment, max_speed, car_access, car_average_speed, bike_access, bike_priority, bike_average_speed, foot_access, foot_priority, foot_average_speed"). + setProfiles(Arrays.asList( + TestProfiles.accessAndSpeed("my_car", "car"), + TestProfiles.accessSpeedAndPriority("my_bike", "bike"), + TestProfiles.accessSpeedAndPriority("my_feet", "foot") + )). + setCHProfiles(Arrays.asList( new CHProfile("my_car"), new CHProfile("my_bike"), new CHProfile("my_feet") - )) - .setLMProfiles(Arrays.asList( + )). + setLMProfiles(Arrays.asList( new LMProfile("my_car"), new LMProfile("my_bike"), new LMProfile("my_feet") @@ -85,7 +85,7 @@ public static void cleanUp() { @ValueSource(strings = {"CH", "LM", "flex"}) public void selectUsingProfile(String mode) { assertDistance("my_car", mode, 3563); - assertDistance("my_bike", mode, 3296); + assertDistance("my_bike", mode, 3700); assertDistance("my_feet", mode, 3158); assertError("my_pink_car", mode, "The requested profile 'my_pink_car' does not exist"); } @@ -100,7 +100,7 @@ private void assertError(String profile, String mode, String... expectedErrors) assertError(doPost(profile, mode), expectedErrors); } - private Response doGet(String profile, String mode) { + private BodyAndStatus doGet(String profile, String mode) { String urlParams = "point=43.727879,7.409678&point=43.745987,7.429848"; if (profile != null) urlParams += "&profile=" + profile; @@ -108,10 +108,10 @@ private Response doGet(String profile, String mode) { urlParams += "&ch.disable=true"; if (mode.equals("flex")) urlParams += "&lm.disable=true"; - return clientTarget(app, "/route?" + urlParams).request().buildGet().invoke(); + return getWithStatus(clientTarget(app, "/route?" + urlParams)); } - private Response doPost(String profile, String mode) { + private BodyAndStatus doPost(String profile, String mode) { String jsonStr = "{\"points\": [[7.409678,43.727879], [7.429848, 43.745987]]"; if (profile != null) jsonStr += ",\"profile\": \"" + profile + "\""; @@ -120,11 +120,11 @@ private Response doPost(String profile, String mode) { if (mode.equals("flex")) jsonStr += ",\"lm.disable\": true"; jsonStr += " }"; - return clientTarget(app, "/route").request().post(Entity.json(jsonStr)); + return postWithStatus(clientTarget(app, "/route"), jsonStr); } - private void assertDistance(Response response, double expectedDistance) { - JsonNode json = response.readEntity(JsonNode.class); + private void assertDistance(BodyAndStatus response, double expectedDistance) { + JsonNode json = response.getBody(); assertEquals(200, response.getStatus(), (json.has("message") ? json.get("message").toString() : "")); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); @@ -133,10 +133,10 @@ private void assertDistance(Response response, double expectedDistance) { assertEquals(expectedDistance, distance, 10); } - private void assertError(Response response, String... expectedErrors) { + private void assertError(BodyAndStatus response, String... expectedErrors) { if (expectedErrors.length == 0) throw new IllegalArgumentException("there should be at least one expected error"); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertEquals(400, response.getStatus(), "there should have been an error containing: " + Arrays.toString(expectedErrors)); assertTrue(json.has("message")); for (String expectedError : expectedErrors) diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceTest.java index 2bd1fa1f739..b3f9ea25022 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceTest.java @@ -26,11 +26,12 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.application.util.GraphHopperServerTestConfiguration; import com.graphhopper.config.CHProfile; -import com.graphhopper.config.Profile; +import com.graphhopper.routing.TestProfiles; import com.graphhopper.routing.ev.RoadClass; import com.graphhopper.routing.ev.RoadClassLink; import com.graphhopper.routing.ev.RoadEnvironment; import com.graphhopper.routing.ev.Surface; +import com.graphhopper.util.BodyAndStatus; import com.graphhopper.util.Helper; import com.graphhopper.util.InstructionList; import com.graphhopper.util.Parameters; @@ -39,6 +40,9 @@ import com.graphhopper.util.shapes.GHPoint; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -46,12 +50,10 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import javax.ws.rs.client.Entity; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; import java.io.File; import java.util.*; +import static com.graphhopper.application.resources.Util.getWithStatus; import static com.graphhopper.application.util.TestUtils.clientTarget; import static com.graphhopper.application.util.TestUtils.clientUrl; import static com.graphhopper.util.Instruction.FINISH; @@ -80,7 +82,6 @@ private static GraphHopperServerConfiguration createConfig() { GraphHopperServerConfiguration config = new GraphHopperServerTestConfiguration(); config.getGraphHopperConfiguration(). putObject("profiles_mapbox", mapboxResolver). - putObject("graph.vehicles", "car"). putObject("prepare.min_network_size", 0). putObject("datareader.file", "../core/files/andorra.osm.pbf"). putObject("graph.encoded_values", "road_class,surface,road_environment,max_speed,country"). @@ -88,11 +89,16 @@ private static GraphHopperServerConfiguration createConfig() { putObject("graph.urban_density.threads", 1). // for max_speed_calculator putObject("graph.urban_density.city_radius", 0). putObject("import.osm.ignored_highways", ""). - putObject("graph.location", DIR) + putObject("graph.location", DIR). // adding this so the corresponding check is not just skipped... - .putObject(MAX_NON_CH_POINT_DISTANCE, 10e6) - .setProfiles(Collections.singletonList(new Profile("my_car").setVehicle("car"))) - .setCHProfiles(Collections.singletonList(new CHProfile("my_car"))); + putObject(MAX_NON_CH_POINT_DISTANCE, 10e6). + putObject("routing.snap_preventions_default", "tunnel, bridge, ferry"). + putObject("graph.encoded_values", "road_class, surface, road_environment, max_speed, country, " + + "car_access, car_average_speed, " + + "foot_access, foot_priority, foot_average_speed"). + setProfiles(List.of(TestProfiles.accessAndSpeed("my_car", "car"), + TestProfiles.accessSpeedAndPriority("foot"))). + setCHProfiles(List.of(new CHProfile("my_car"), new CHProfile("foot"))); return config; } @@ -104,12 +110,11 @@ public static void cleanUp() { @Test public void testBasicQuery() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=42.554851,1.536198&point=42.510071,1.548128").request().get(JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); + assertEquals("GraphHopper", infoJson.at("/copyrights/0").asText()); JsonNode path = json.get("paths").get(0); double distance = path.get("distance").asDouble(); assertTrue(distance > 9000, "distance wasn't correct:" + distance); @@ -118,10 +123,9 @@ public void testBasicQuery() { @Test public void testBasicQuerySamePoint() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.510071,1.548128&point=42.510071,1.548128").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode path = response.readEntity(JsonNode.class).get("paths").get(0); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=42.510071,1.548128&point=42.510071,1.548128").request().get(JsonNode.class); + JsonNode path = json.get("paths").get(0); assertEquals(0, path.get("distance").asDouble(), 0.001); assertEquals("[1.548191,42.510033,1.548191,42.510033]", path.get("bbox").toString()); } @@ -129,9 +133,7 @@ public void testBasicQuerySamePoint() { @Test public void testBasicPostQuery() { String jsonStr = "{ \"profile\": \"my_car\", \"points\": [[1.536198,42.554851], [1.548128, 42.510071]] }"; - Response response = clientTarget(app, "/route").request().post(Entity.json(jsonStr)); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route").request().post(Entity.json(jsonStr), JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); JsonNode path = json.get("paths").get(0); @@ -142,45 +144,38 @@ public void testBasicPostQuery() { // we currently just ignore URL parameters in a POST request (not sure if this is a good or bad thing) jsonStr = "{\"points\": [[1.536198,42.554851], [1.548128, 42.510071]], \"profile\": \"my_car\" }"; - response = clientTarget(app, "/route?vehicle=unknown&weighting=unknown").request().post(Entity.json(jsonStr)); - assertEquals(200, response.getStatus()); - assertFalse(response.readEntity(JsonNode.class).get("info").has("errors")); + json = clientTarget(app, "/route?vehicle=unknown&weighting=unknown").request().post(Entity.json(jsonStr), JsonNode.class); + assertFalse(json.get("info").has("errors")); } @Test public void testBasicNavigationQuery() { - Response response = clientTarget(app, "/navigate/directions/v5/gh/driving/1.537174,42.507145;1.539116,42.511368?" + + JsonNode json = clientTarget(app, "/navigate/directions/v5/gh/driving/1.537174,42.507145;1.539116,42.511368?" + "access_token=pk.my_api_key&alternatives=true&geometries=polyline6&overview=full&steps=true&continue_straight=true&" + "annotations=congestion%2Cdistance&language=en&roundabout_exits=true&voice_instructions=true&banner_instructions=true&voice_units=metric"). - request().get(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + request().get(JsonNode.class); assertEquals(1256, json.get("routes").get(0).get("distance").asDouble(), 20); } @Test public void testWrongPointFormat() { - final Response response = clientTarget(app, "/route?profile=my_car&point=1234&point=42.510071,1.548128").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=my_car&point=1234&point=42.510071,1.548128")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertTrue(json.get("message").asText().contains("Cannot parse point '1234'"), "There should be an error " + json.get("message")); } @Test public void testAcceptOnlyXmlButNoTypeParam() { - final Response response = clientTarget(app, "/route?profile=my_car&point=42.554851,1.536198&point=42.510071,1.548128") - .request(MediaType.APPLICATION_XML).buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&point=42.554851,1.536198&point=42.510071,1.548128") + .request(MediaType.APPLICATION_XML).get(JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); } @Test public void testQueryWithoutInstructions() { - final Response response = clientTarget(app, "/route?profile=my_car&point=42.554851,1.536198&point=42.510071,1.548128&instructions=false").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&point=42.554851,1.536198&point=42.510071,1.548128&instructions=false").request().get(JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); JsonNode path = json.get("paths").get(0); @@ -192,10 +187,10 @@ public void testQueryWithoutInstructions() { @Test public void testCHWithHeading_error() { // There are special cases where heading works with node-based CH, but generally it leads to wrong results -> we expect an error - final Response response = clientTarget(app, "/route?profile=my_car&" - + "point=42.496696,1.499323&point=42.497257,1.501501&heading=240&heading=240").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=my_car&" + + "point=42.496696,1.499323&point=42.497257,1.501501&heading=240&heading=240")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertTrue(json.has("message"), "There should have been an error response"); String expected = "The 'heading' parameter is currently not supported for speed mode, you need to disable speed mode with `ch.disable=true`. See issue #483"; assertTrue(json.get("message").asText().contains(expected), "There should be an error containing " + expected + ", but got: " + json.get("message")); @@ -204,10 +199,10 @@ public void testCHWithHeading_error() { @Test public void testCHWithPassThrough_error() { // There are special cases where pass_through works with node-based CH, but generally it leads to wrong results -> we expect an error - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.534133,1.581473&point=42.534781,1.582149&point=42.535042,1.582514&pass_through=true").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=my_car&" + + "point=42.534133,1.581473&point=42.534781,1.582149&point=42.535042,1.582514&pass_through=true")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertTrue(json.has("message"), "There should have been an error response"); String expected = "The '" + Parameters.Routing.PASS_THROUGH + "' parameter is currently not supported for speed mode, you need to disable speed mode with `ch.disable=true`. See issue #1765"; assertTrue(json.get("message").asText().contains(expected), "There should be an error containing " + expected + ", but got: " + json.get("message")); @@ -215,20 +210,18 @@ public void testCHWithPassThrough_error() { @Test public void testJsonRounding() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false").request().get(JsonNode.class); JsonNode cson = json.get("paths").get(0).get("points"); assertTrue(cson.toString().contains("[1.536374,42.554839]"), "unexpected precision!"); } @Test public void testFailIfElevationRequestedButNotIncluded() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false&elevation=true").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=my_car&" + + "point=42.554851234,1.536198&point=42.510071,1.548128&points_encoded=false&elevation=true")); assertEquals(400, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = response.getBody(); assertTrue(json.has("message")); assertEquals("Elevation not supported!", json.get("message").asText()); } @@ -295,16 +288,16 @@ public void testPathDetails() { List averageSpeedList = pathDetails.get("average_speed"); assertEquals(13, averageSpeedList.size()); assertEquals(30.0, averageSpeedList.get(0).getValue()); - assertEquals(14, averageSpeedList.get(0).getLength()); + assertEquals(15, averageSpeedList.get(0).getLength()); assertEquals(60.0, averageSpeedList.get(1).getValue()); assertEquals(5, averageSpeedList.get(1).getLength()); List edgeIdDetails = pathDetails.get("edge_id"); assertEquals(78, edgeIdDetails.size()); - assertEquals(924L, edgeIdDetails.get(0).getValue()); + assertEquals(822L, edgeIdDetails.get(0).getValue()); assertEquals(2, edgeIdDetails.get(0).getLength()); - assertEquals(925L, edgeIdDetails.get(1).getValue()); - assertEquals(8, edgeIdDetails.get(1).getLength()); + assertEquals(844L, edgeIdDetails.get(1).getValue()); + assertEquals(9, edgeIdDetails.get(1).getLength()); long expectedTime = rsp.getBest().getTime(); long actualTime = 0; @@ -339,47 +332,46 @@ public void testPathDetailsNoConnection() { @Test public void testPathDetailsWithoutGraphHopperWeb() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.554851,1.536198&point=42.510071,1.548128&details=average_speed&details=edge_id&details=max_speed&details=urban_density").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - JsonNode json = response.readEntity(JsonNode.class); + JsonNode json = clientTarget(app, "/route?profile=my_car&" + + "point=42.554851,1.536198&point=42.510071,1.548128&details=average_speed&details=edge_id&details=max_speed&details=urban_density").request().get(JsonNode.class); JsonNode infoJson = json.get("info"); assertFalse(infoJson.has("errors")); JsonNode path = json.get("paths").get(0); + assertEquals(9203.674, path.get("distance").asDouble(), .1); assertTrue(path.has("details")); JsonNode details = path.get("details"); assertTrue(details.has("average_speed")); JsonNode averageSpeed = details.get("average_speed"); assertEquals(30.0, averageSpeed.get(0).get(2).asDouble(), .1); - assertEquals(14, averageSpeed.get(0).get(1).asInt(), .1); + assertEquals(15, averageSpeed.get(0).get(1).asInt(), .1); assertEquals(60.0, averageSpeed.get(1).get(2).asDouble(), .1); - assertEquals(19, averageSpeed.get(1).get(1).asInt()); + assertEquals(20, averageSpeed.get(1).get(1).asInt()); assertTrue(details.has("edge_id")); JsonNode edgeIds = details.get("edge_id"); int firstLink = edgeIds.get(0).get(2).asInt(); int lastLink = edgeIds.get(edgeIds.size() - 1).get(2).asInt(); - assertEquals(924, firstLink); - assertEquals(1584, lastLink); + assertEquals(822, firstLink); + assertEquals(1630, lastLink); JsonNode maxSpeed = details.get("max_speed"); - assertEquals("[0,33,50.0]", maxSpeed.get(0).toString()); - assertEquals("[33,34,60.0]", maxSpeed.get(1).toString()); - assertEquals("[34,38,50.0]", maxSpeed.get(2).toString()); - assertEquals("[38,50,90.0]", maxSpeed.get(3).toString()); - assertEquals("[50,52,50.0]", maxSpeed.get(4).toString()); - assertEquals("[52,60,90.0]", maxSpeed.get(5).toString()); + assertEquals("[0,34,50.0]", maxSpeed.get(0).toString()); + assertEquals("[34,35,60.0]", maxSpeed.get(1).toString()); + assertEquals("[35,39,50.0]", maxSpeed.get(2).toString()); + assertEquals("[39,53,90.0]", maxSpeed.get(3).toString()); + assertEquals("[53,55,50.0]", maxSpeed.get(4).toString()); + assertEquals("[55,65,90.0]", maxSpeed.get(5).toString()); JsonNode urbanDensityNode = details.get("urban_density"); - assertEquals("[0,63,\"residential\"]", urbanDensityNode.get(0).toString()); - assertEquals("[63,68,\"rural\"]", urbanDensityNode.get(1).toString()); - assertEquals("[68,71,\"residential\"]", urbanDensityNode.get(2).toString()); - assertEquals("[71,75,\"rural\"]", urbanDensityNode.get(3).toString()); - assertEquals("[75,106,\"residential\"]", urbanDensityNode.get(4).toString()); - assertEquals("[106,128,\"rural\"]", urbanDensityNode.get(5).toString()); - assertEquals("[128,166,\"residential\"]", urbanDensityNode.get(6).toString()); - assertEquals("[166,171,\"rural\"]", urbanDensityNode.get(7).toString()); - assertEquals("[171,183,\"residential\"]", urbanDensityNode.get(8).toString()); - assertEquals("[183,213,\"rural\"]", urbanDensityNode.get(9).toString()); + assertEquals("[0,68,\"residential\"]", urbanDensityNode.get(0).toString()); + assertEquals("[68,73,\"rural\"]", urbanDensityNode.get(1).toString()); + assertEquals("[73,76,\"residential\"]", urbanDensityNode.get(2).toString()); + assertEquals("[76,80,\"rural\"]", urbanDensityNode.get(3).toString()); + assertEquals("[80,115,\"residential\"]", urbanDensityNode.get(4).toString()); + assertEquals("[115,141,\"rural\"]", urbanDensityNode.get(5).toString()); + assertEquals("[141,181,\"residential\"]", urbanDensityNode.get(6).toString()); + assertEquals("[181,186,\"rural\"]", urbanDensityNode.get(7).toString()); + assertEquals("[186,199,\"residential\"]", urbanDensityNode.get(8).toString()); + assertEquals("[199,233,\"rural\"]", urbanDensityNode.get(9).toString()); } @Test @@ -398,17 +390,32 @@ public void testInitInstructionsWithTurnDescription() { } @Test - public void testSnapPreventions() { - GraphHopperWeb hopper = new GraphHopperWeb(clientUrl(app, "route")); - GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271); - request.setProfile("my_car"); - GHResponse rsp = hopper.route(request); - assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); - assertEquals(490, rsp.getBest().getDistance(), 2); + public void testFootInstructionForReverseCarOnewayInRoundabout() { + JsonNode json = clientTarget(app, "/route?profile=foot&" + + "point=42.512263%2C1.535468&point=42.512938%2C1.534875").request().get(JsonNode.class); + JsonNode path = json.get("paths").get(0); + assertEquals(103, path.get("distance").asDouble(), 1); + JsonNode n = path.get("instructions").get(1); + assertEquals("At roundabout, take exit 1 onto Avigunda Sant Antoni, Avinguda Fiter i Rossell", n.get("text").asText()); + } - request.setSnapPreventions(Collections.singletonList("tunnel")); - rsp = hopper.route(request); - assertEquals(1081, rsp.getBest().getDistance(), 2); + @Test + public void testSnapPreventions() { + for (boolean postRequest : List.of(true, false)) { + GraphHopperWeb hopper = new GraphHopperWeb(clientUrl(app, "route")); + hopper.setPostRequest(postRequest); + GHRequest request = new GHRequest(42.511139, 1.53285, 42.508165, 1.532271); + request.setProfile("my_car"); + GHResponse rsp = hopper.route(request); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + assertEquals(1081, rsp.getBest().getDistance(), 2, rsp.getBest().getDistance() + " with post " + postRequest); + + // overwrite default: + request.setSnapPreventions(List.of()); + rsp = hopper.route(request); + assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); + assertEquals(490, rsp.getBest().getDistance(), 2, rsp.getBest().getDistance() + " with post " + postRequest); + } } @Test @@ -434,18 +441,16 @@ public void testPostWithPointHintsAndSnapPrevention() { String jsonStr = "{ \"points\": [[1.53285,42.511139], [1.532271,42.508165]], " + "\"profile\": \"my_car\", " + "\"point_hints\":[\"Avinguda Fiter i Rossell\",\"\"] }"; - Response response = clientTarget(app, "/route").request().post(Entity.json(jsonStr)); - assertEquals(200, response.getStatus()); - JsonNode path = response.readEntity(JsonNode.class).get("paths").get(0); + JsonNode json = clientTarget(app, "/route").request().post(Entity.json(jsonStr), JsonNode.class); + JsonNode path = json.get("paths").get(0); assertEquals(1590, path.get("distance").asDouble(), 2); jsonStr = "{ \"points\": [[1.53285,42.511139], [1.532271,42.508165]], " + "\"profile\": \"my_car\", " + "\"point_hints\":[\"Tunèl del Pont Pla\",\"\"], " + "\"snap_preventions\": [\"tunnel\"] }"; - response = clientTarget(app, "/route").request().post(Entity.json(jsonStr)); - assertEquals(200, response.getStatus()); - path = response.readEntity(JsonNode.class).get("paths").get(0); + json = clientTarget(app, "/route").request().post(Entity.json(jsonStr), JsonNode.class); + path = json.get("paths").get(0); assertEquals(490, path.get("distance").asDouble(), 2); } @@ -466,9 +471,9 @@ public void testGraphHopperWebRealExceptions(boolean usePost) { "The requested profile 'space_shuttle' does not exist"), rsp.getErrors().toString()); // unknown profile via web api - Response response = clientTarget(app, "/route?profile=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke(); + BodyAndStatus response = getWithStatus(clientTarget(app, "/route?profile=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128")); assertEquals(400, response.getStatus()); - String msg = (String) response.readEntity(Map.class).get("message"); + String msg = response.getBody().get("message").toString(); assertTrue(msg.contains("The requested profile 'SPACE-SHUTTLE' does not exist"), msg); // no points @@ -515,33 +520,29 @@ public void testGraphHopperWebRealExceptions(boolean usePost) { assertTrue(ex instanceof IllegalArgumentException, "Wrong exception found: " + ex.getClass().getName() + ", IllegalArgumentException expected."); assertTrue(ex.getMessage().contains("The requested profile 'SPACE-SHUTTLE' does not exist." + - "\nAvailable profiles: [my_car]"), ex.getMessage()); + "\nAvailable profiles: [my_car, foot]"), ex.getMessage()); // an IllegalArgumentException from inside the core is written as JSON, unknown profile - response = clientTarget(app, "/route?profile=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128").request().buildGet().invoke(); + response = getWithStatus(clientTarget(app, "/route?profile=SPACE-SHUTTLE&point=42.554851,1.536198&point=42.510071,1.548128")); assertEquals(400, response.getStatus()); - msg = (String) response.readEntity(Map.class).get("message"); + msg = (String) response.getBody().get("message").toString(); assertTrue(msg.contains("The requested profile 'SPACE-SHUTTLE' does not exist"), msg); } @Test public void testGPX() { - final Response response = clientTarget(app, "/route?profile=my_car&" + - "point=42.554851,1.536198&point=42.510071,1.548128&type=gpx").request().buildGet().invoke(); - assertEquals(200, response.getStatus()); - String str = response.readEntity(String.class); + String str = clientTarget(app, "/route?profile=my_car&" + + "point=42.554851,1.536198&point=42.510071,1.548128&type=gpx").request().get(String.class); // For backward compatibility we currently export route and track. - assertTrue(str.contains("1841.5"), str); - assertFalse(str.contains(" Finish!")); + assertTrue(str.contains("1841.7"), str); + assertFalse(str.contains("Finish!")); assertTrue(str.contains("