diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aed7953cbb8..26301992871 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ jobs: strategy: fail-fast: false matrix: - java-version: [ 24 ] + java-version: [ 25, 26-ea ] steps: - uses: actions/checkout@v4 - uses: actions/setup-java@v4 diff --git a/.github/workflows/publish-maven-central.yml b/.github/workflows/publish-maven-central.yml index cae8df65ac6..a898bd2da6b 100644 --- a/.github/workflows/publish-maven-central.yml +++ b/.github/workflows/publish-maven-central.yml @@ -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/CHANGELOG.md b/CHANGELOG.md index 577e8a55bf2..c24cb05847e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ -### 11.0 [not yet released] +### 12.0 [not yet released] + + + +### 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 @@ -7,6 +11,7 @@ - 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] diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a45fe969111..ed931b31ec6 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -49,6 +49,7 @@ Here is an overview: * 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) @@ -71,6 +72,7 @@ Here is an overview: * 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 @@ -89,8 +91,10 @@ 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 * vvikas, ideas for many to many improvements and #616 * zstadler, multiple fixes and car4wd + * binora, fix mode in navigation response converter ## Translations diff --git a/NOTICE.md b/NOTICE.md index 26c91632c9b..fb71c079848 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -2,7 +2,7 @@ GraphHopper licensed under the Apache license, Version 2.0 -Copyright 2012 - 2024 GraphHopper GmbH +Copyright 2012 - 2025 GraphHopper GmbH The core module includes the following additional software: diff --git a/README.md b/README.md index 307e5a64c86..b43252c7c46 100644 --- a/README.md +++ b/README.md @@ -31,15 +31,18 @@ to get started with contribution. 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. -* 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/) +* 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) See the [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) @@ -103,8 +106,8 @@ See the [changelog file](./CHANGELOG.md) for Java API Changes. 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/10.0/graphhopper-web-10.0.jar \ - https://raw.githubusercontent.com/graphhopper/graphhopper/10.x/config-example.yml \ +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 ``` diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 62be3041b85..e2f774ec5d0 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -22,14 +22,14 @@ 4.0.0 directions-api-client-hc - 11.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Directions API hand-crafted Java Client. com.graphhopper graphhopper-parent - 11.0-SNAPSHOT + 12.0-SNAPSHOT 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 ccae9c4bb4f..9441e0510c4 100644 --- a/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java +++ b/client-hc/src/main/java/com/graphhopper/api/GHMatrixSyncRequester.java @@ -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) { 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 a46e9ef5d9b..f64cc130341 100644 --- a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java @@ -113,7 +113,7 @@ public void customModel() throws JsonProcessingException { "{\"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\":{}}]}," + "\"priority\":[{\"if\":\"surface == DIRT\",\"multiply_by\":\"0.7\"},{\"if\":\"surface == SAND\",\"multiply_by\":\"0.6\"}]," + - "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}]}"); + "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}],\"turn_penalty\":[]}"); assertEquals(expected, objectMapper.valueToTree(customModelJson)); CustomModel cm = objectMapper.readValue("{\"distance_influence\":null}", CustomModel.class); diff --git a/config-example.yml b/config-example.yml index 6a52917c4a6..c0def01fa53 100644 --- a/config-example.yml +++ b/config-example.yml @@ -33,7 +33,7 @@ graphhopper: # turn_costs: # vehicle_types: [motorcar, motor_vehicle] # u_turn_costs: 60 -# for more advanced turn costs, see #2957 or bike_tc.yml +# for more advanced turn costs see avoid_turns.json custom_model_files: [car.json] # You can use the following in-built profiles. After you start GraphHopper it will print which encoded values you'll have to add to graph.encoded_values in this config file. @@ -50,13 +50,26 @@ graphhopper: # - 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 +# 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. @@ -69,7 +82,7 @@ graphhopper: # 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` 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`. + # usage) and the routing will also be slower than without `turn_costs`. profiles_ch: - profile: car @@ -92,8 +105,10 @@ graphhopper: # 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,car_temporal_access,bike_temporal_access,foot_temporal_access + graph.encoded_values: car_access, car_average_speed, road_access, barrier + #### Speed, hybrid and flexible mode #### # To make CH preparation faster for multiple profiles you can increase the default threads if you have enough RAM. @@ -131,7 +146,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). @@ -197,11 +211,11 @@ 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,construction,cycleway,path,pedestrian,steps # typically useful for motorized-only routing + import.osm.ignored_highways: footway,construction,cycleway,path,steps # typically useful for motorized-only routing # import.osm.ignored_highways: motorway,trunk # typically useful for non-motorized routing # configure the memory access, use RAM_STORE for well equipped servers (default and recommended) diff --git a/core/pom.xml b/core/pom.xml index 64725606c10..b68e3611ffd 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -5,7 +5,7 @@ graphhopper-core GraphHopper Core - 11.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 - 11.0-SNAPSHOT + 12.0-SNAPSHOT diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index fa8cb820ccf..fce7fb76675 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -298,6 +298,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 */ @@ -1143,14 +1153,21 @@ public boolean load() { .build(); checkProfilesConsistency(); 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()); - + 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(); @@ -1166,7 +1183,11 @@ protected int getProfileHash(Profile profile) { } private String getProfilesString() { - return profilesByName.values().stream().map(p -> p.getName() + "|" + getProfileHash(p)).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()))); } public void checkProfilesConsistency() { @@ -1439,6 +1460,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) { diff --git a/core/src/main/java/com/graphhopper/GraphHopperConfig.java b/core/src/main/java/com/graphhopper/GraphHopperConfig.java index 7838cc0f107..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; @@ -67,6 +68,7 @@ public List getProfiles() { return profiles; } + @JsonSetter(nulls = Nulls.AS_EMPTY) public GraphHopperConfig setProfiles(List profiles) { this.profiles = profiles; return this; @@ -76,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; @@ -86,7 +88,7 @@ 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; 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/routing/AbstractBidirCHAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractBidirCHAlgo.java index 64fd8d69dff..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; diff --git a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java index bad1d20bf47..5bfc46f643e 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractNonCHBidirAlgo.java @@ -211,7 +211,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..8ca33627577 100644 --- a/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java +++ b/core/src/main/java/com/graphhopper/routing/AbstractRoutingAlgorithm.java @@ -67,7 +67,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/DefaultWeightingFactory.java b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java index 838ad57e85d..d7b8f9034cf 100644 --- a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java +++ b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java @@ -20,15 +20,13 @@ import com.graphhopper.config.Profile; import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; -import com.graphhopper.routing.ev.Orientation; import com.graphhopper.routing.ev.TurnRestriction; import com.graphhopper.routing.util.EncodingManager; 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.routing.weighting.custom.CustomWeighting2; import com.graphhopper.storage.BaseGraph; import com.graphhopper.util.CustomModel; import com.graphhopper.util.PMap; @@ -36,6 +34,7 @@ import com.graphhopper.util.TurnCostsConfig; import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; +import static com.graphhopper.routing.weighting.custom.CustomModelParser.createWeightingParameters; import static com.graphhopper.util.Helper.toLowerCase; public class DefaultWeightingFactory implements WeightingFactory { @@ -57,21 +56,6 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis hints.putAll(profile.getHints()); hints.putAll(requestHints); - 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()); - DecimalEncodedValue oEnc = encodingManager.hasEncodedValue(Orientation.KEY) ? encodingManager.getDecimalEncodedValue(Orientation.KEY) : null; - if (profile.getTurnCostsConfig().hasLeftRightStraightCosts() && oEnc == null) - throw new IllegalArgumentException("Using left_turn_costs,sharp_left_turn_costs,right_turn_costs,sharp_right_turn_costs or straight_costs for turn_costs requires 'orientation' in graph.encoded_values"); - int uTurnCosts = hints.getInt(Parameters.Routing.U_TURN_COSTS, profile.getTurnCostsConfig().getUTurnCosts()); - TurnCostsConfig tcConfig = new TurnCostsConfig(profile.getTurnCostsConfig()).setUTurnCosts(uTurnCosts); - turnCostProvider = new DefaultTurnCostProvider(turnRestrictionEnc, oEnc, graph, tcConfig); - } else { - turnCostProvider = NO_TURN_COST_PROVIDER; - } - String weightingStr = toLowerCase(profile.getWeighting()); if (weightingStr.isEmpty()) throw new IllegalArgumentException("You have to specify a weighting"); @@ -79,15 +63,35 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis Weighting weighting = 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)); + + 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()) + 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; + } + if (hints.has("cm_version")) { if (!hints.getString("cm_version", "").equals("2")) throw new IllegalArgumentException("cm_version: \"2\" is required"); - weighting = CustomModelParser.createWeighting2(encodingManager, turnCostProvider, mergedCustomModel); - } else - weighting = CustomModelParser.createWeighting(encodingManager, turnCostProvider, mergedCustomModel); + weighting = new CustomWeighting2(turnCostProvider, parameters); + } else { + 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"); @@ -105,5 +109,4 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis return weighting; } - } 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/PrepareContractionHierarchies.java b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java index 4e5302e38d9..752e301dd49 100644 --- a/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java +++ b/core/src/main/java/com/graphhopper/routing/ch/PrepareContractionHierarchies.java @@ -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())); } 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 805625421f4..d9cb9680329 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java +++ b/core/src/main/java/com/graphhopper/routing/ev/RoadAccess.java @@ -44,7 +44,7 @@ public String toString() { public static RoadAccess find(String name) { if (name == null) return YES; - if (name.equalsIgnoreCase("permit")) + if (name.equalsIgnoreCase("permit") || name.equalsIgnoreCase("service")) return PRIVATE; try { // public and permissive will be converted into "yes" 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 87745dac69c..99932c62364 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 @@ -49,7 +49,10 @@ protected AbstractAccessParser(BooleanEncodedValue accessEnc, List restr restrictedValues.add("restricted"); restrictedValues.add("military"); restrictedValues.add("emergency"); + restrictedValues.add("unknown"); + restrictedValues.add("private"); + restrictedValues.add("service"); restrictedValues.add("permit"); } @@ -63,12 +66,11 @@ protected void blockFords(boolean 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"); + 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"); } } 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..c832cd1614c 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,14 @@ 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.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, + DecimalEncodedValue ferrySpeedEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, ferrySpeedEnc, bikeRouteEnc); } } 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 ff454fcd92c..9d84a9d3140 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,13 +1,14 @@ 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.storage.IntsRef; -import java.util.*; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; import static com.graphhopper.routing.util.parsers.AbstractAccessParser.INTENDED; @@ -16,30 +17,27 @@ public abstract class BikeCommonAverageSpeedParser extends AbstractAverageSpeedP private static final Set CYCLEWAY_KEYS = Set.of("cycleway", "cycleway:left", "cycleway:both", "cycleway:right"); 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; private final Set restrictedValues = Set.of("no", "agricultural", "forestry", "restricted", "military", "emergency", "private", "permit"); + private final EnumEncodedValue bikeRouteEnc; - protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) { + protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, + EnumEncodedValue smoothnessEnc, + DecimalEncodedValue ferrySpeedEnc, + EnumEncodedValue bikeRouteEnc) { super(speedEnc, ferrySpeedEnc); + 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("grade5", PUSHING_SECTION_SPEED); // like sand setSurfaceSpeed("paved", 18); setSurfaceSpeed("asphalt", 18); @@ -54,12 +52,12 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setSurfaceSpeed("unpaved", 12); setSurfaceSpeed("compacted", 14); setSurfaceSpeed("dirt", 10); - setSurfaceSpeed("earth", 12); + 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); @@ -72,10 +70,11 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded setHighwaySpeed("steps", MIN_SPEED); setHighwaySpeed("cycleway", 18); - setHighwaySpeed("path", PUSHING_SECTION_SPEED); - setHighwaySpeed("footway", PUSHING_SECTION_SPEED); - setHighwaySpeed("platform", PUSHING_SECTION_SPEED); - setHighwaySpeed("pedestrian", PUSHING_SECTION_SPEED); + setHighwaySpeed("path", 6); + setHighwaySpeed("footway", 6); + setHighwaySpeed("platform", 6); + setHighwaySpeed("pedestrian", 6); + setHighwaySpeed("bridleway", 6); setHighwaySpeed("track", 12); setHighwaySpeed("service", 12); setHighwaySpeed("residential", 18); @@ -97,8 +96,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); @@ -123,10 +120,14 @@ protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncoded return Math.min(speed, maxSpeed); } - @Override public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) { - String highwayValue = way.getTag("highway"); - if (highwayValue == null) { + throw new IllegalArgumentException("use handleWayTags with relationFlags"); + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + String highwayValue = way.getTag("highway", ""); + if (highwayValue.isEmpty()) { if (FerrySpeedCalculator.isFerry(way)) { double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc); setSpeed(false, edgeId, edgeIntAccess, ferrySpeed); @@ -141,34 +142,48 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way String surfaceValue = way.getTag("surface"); String trackTypeValue = way.getTag("tracktype"); boolean pushingRestriction = Arrays.stream(way.getTag("vehicle", "").split(";")).anyMatch(restrictedValues::contains); - if ("steps".equals(highwayValue)) { - // ignore - } else if (pushingSectionsHighways.contains(highwayValue)) { - if (way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official") || way.hasTag("segregated", "yes") - || CYCLEWAY_KEYS.stream().anyMatch(k -> way.getTag(k, "").equals("track"))) { - speed = trackTypeSpeeds.getOrDefault(trackTypeValue, highwaySpeeds.get("cycleway")); - } - else if (way.hasTag("bicycle", "yes")) - speed = 12; - } - Integer surfaceSpeed = surfaceSpeeds.get(surfaceValue); + Integer trackTypeSpeed = trackTypeSpeeds.get(trackTypeValue); + if (trackTypeSpeed != null) + surfaceSpeed = surfaceSpeed == null ? trackTypeSpeed : Math.min(surfaceSpeed, trackTypeSpeed); + if (way.hasTag("surface") && surfaceSpeed == null || way.hasTag("bicycle", "dismount") || way.hasTag("railway", "platform") || pushingRestriction && !way.hasTag("bicycle", INTENDED) - || way.hasTag("service")) { + || way.hasTag("service") && !isDesignated(way)) { speed = PUSHING_SECTION_SPEED; - } else if ("track".equals(highwayValue) || - "bridleway".equals(highwayValue) ) { - if (surfaceSpeed != null) - speed = surfaceSpeed; - else if (trackTypeSpeeds.containsKey(trackTypeValue)) - speed = trackTypeSpeeds.get(trackTypeValue); - } else if (surfaceSpeed != null) { - speed = Math.min(surfaceSpeed, speed); + + } else { + + boolean bikeDesignated = isDesignated(way) || RouteNetwork.MISSING != bikeRouteEnc.getEnum(false, edgeId, edgeIntAccess); + 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); + } } + // speed reduction if bad surface + if (surfaceSpeed != null) + speed = Math.min(surfaceSpeed, speed); + Smoothness smoothness = smoothnessEnc.getEnum(false, edgeId, edgeIntAccess); speed = Math.max(MIN_SPEED, smoothnessFactor.get(smoothness) * speed); setSpeed(false, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, false)); @@ -176,12 +191,13 @@ else if (trackTypeSpeeds.containsKey(trackTypeValue)) setSpeed(true, edgeId, edgeIntAccess, applyMaxSpeed(way, speed, true)); } - void setHighwaySpeed(String highway, int speed) { - highwaySpeeds.put(highway, speed); + private boolean isDesignated(ReaderWay way) { + return way.hasTag("bicycle", "designated") || way.hasTag("bicycle", "official") || way.hasTag("segregated", "yes") + || CYCLEWAY_KEYS.stream().anyMatch(k -> way.getTag(k, "").equals("track")); } - void addPushingSection(String highway) { - pushingSectionsHighways.add(highway); + void setHighwaySpeed(String highway, int speed) { + highwaySpeeds.put(highway, speed); } void setTrackTypeSpeed(String tracktype, int 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 1d8a5e53f4c..fb3e35e66fe 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 @@ -10,7 +10,6 @@ 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; @@ -30,7 +29,6 @@ public abstract class BikeCommonPriorityParser implements TagParser { // 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 @@ -42,7 +40,6 @@ protected BikeCommonPriorityParser(DecimalEncodedValue priorityEnc, DecimalEncod this.priorityEnc = priorityEnc; this.avgSpeedEnc = avgSpeedEnc; - // duplicate code as also in BikeCommonAverageSpeedParser addPushingSection("footway"); addPushingSection("pedestrian"); addPushingSection("steps"); @@ -73,18 +70,18 @@ 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, VERY_NICE.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)); + Integer priorityFromRelation = null; + switch (bikeRouteEnc.getEnum(false, edgeId, edgeIntAccess)) { + case INTERNATIONAL, NATIONAL -> priorityFromRelation = BEST.getValue(); + case REGIONAL, LOCAL -> priorityFromRelation = VERY_NICE.getValue(); + } + if (highwayValue == null) { if (FerrySpeedCalculator.isFerry(way)) { priorityFromRelation = SLIGHT_AVOID.getValue(); @@ -150,7 +147,7 @@ private PriorityCode convertClassValueToPriority(String tagvalue) { void collect(ReaderWay way, double wayTypeSpeed, TreeMap weightToPrioMap) { String highway = way.getTag("highway"); if (isDesignated(way)) { - boolean isGoodSurface = way.getTag("tracktype", "").equals("grade1") || goodSurface.contains(way.getTag("surface","")); + 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 @@ -165,7 +162,7 @@ void collect(ReaderWay way, double wayTypeSpeed, TreeMap w } double maxSpeed = Math.max(OSMMaxSpeedParser.parseMaxSpeed(way, false), OSMMaxSpeedParser.parseMaxSpeed(way, true)); - if (preferHighwayTags.contains(highway) || (maxSpeed != MaxSpeed.MAXSPEED_MISSING && maxSpeed <= 30)) { + if (preferHighwayTags.contains(highway) || maxSpeed <= 30) { if (maxSpeed == MaxSpeed.MAXSPEED_MISSING || maxSpeed < avoidSpeedLimit) { weightToPrioMap.put(40d, PREFER); if (way.hasTag("tunnel", INTENDED)) @@ -234,7 +231,6 @@ boolean isDesignated(ReaderWay way) { || way.hasTag("bicycle_road", "yes") || way.hasTag("cyclestreet", "yes") || way.hasTag("bicycle", "official"); } - // TODO duplicated in average speed void addPushingSection(String highway) { pushingSectionsHighways.add(highway); } 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 a13c152a6f7..ae5db4e517e 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 @@ -69,7 +69,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)); } @@ -93,6 +93,13 @@ public WayAccess getAccess(ReaderWay way) { 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; 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 6b9ef246aec..254eddaf674 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 @@ -83,8 +83,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); 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 398a8b435f0..22ee08f59db 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,38 +7,30 @@ 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.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, + DecimalEncodedValue ferrySpeedEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, ferrySpeedEnc, 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("compacted", 16); + 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 148406bf7e1..455a681ec0a 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 @@ -21,11 +21,6 @@ protected MountainBikePriorityParser(DecimalEncodedValue speedEnc, DecimalEncode 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()); - preferHighwayTags.add("road"); preferHighwayTags.add("track"); preferHighwayTags.add("path"); 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/OSMTollParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTollParser.java index 8cb88a6c412..cd02641aa73 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 @@ -57,7 +57,7 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay rea private Toll getCountryDefault(Country country, ReaderWay readerWay) { switch (country) { - case AUT, ROU, SVK, SVN -> { + case ROU, SVK, SVN -> { RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); if (roadClass == RoadClass.MOTORWAY || roadClass == RoadClass.TRUNK) return Toll.ALL; @@ -92,7 +92,7 @@ else if (roadClass == RoadClass.TRUNK || roadClass == RoadClass.PRIMARY) else return Toll.NO; } - case BEL, BLR, LUX, NLD, POL, SWE -> { + case BEL, BLR, LUX, POL, SWE -> { RoadClass roadClass = RoadClass.find(readerWay.getTag("highway", "")); if (roadClass == RoadClass.MOTORWAY) return Toll.HGV; 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..a0b62a6f971 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,27 @@ 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.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, + DecimalEncodedValue ferrySpeedEnc, + EnumEncodedValue bikeRouteEnc) { + super(speedEnc, smoothnessEnc, ferrySpeedEnc, 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 +45,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 c3e3b64df75..ec53218ba99 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 @@ -37,11 +37,6 @@ 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; @@ -56,7 +51,7 @@ void collect(ReaderWay way, double wayTypeSpeed, TreeMap w weightToPrioMap.put(40d, SLIGHT_AVOID); } else if ("track".equals(highway)) { String trackType = way.getTag("tracktype"); - if ("grade1".equals(trackType) || goodSurface.contains(way.getTag("surface",""))) + 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/weighting/DefaultTurnCostProvider.java b/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java index fa15a3966aa..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,8 +19,8 @@ package com.graphhopper.routing.weighting; import com.graphhopper.routing.ev.BooleanEncodedValue; -import com.graphhopper.routing.ev.DecimalEncodedValue; 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; @@ -34,22 +34,13 @@ public class DefaultTurnCostProvider implements TurnCostProvider { private final TurnCostStorage turnCostStorage; private final int uTurnCostsInt; private final double uTurnCosts; - - private final double minTurnAngle; - private final double minSharpTurnAngle; - private final double minUTurnAngle; - - private final double leftTurnCosts; - private final double sharpLeftTurnCosts; - private final double straightCosts; - private final double rightTurnCosts; - private final double sharpRightTurnCosts; private final BaseGraph graph; private final EdgeIntAccess edgeIntAccess; - private final DecimalEncodedValue orientationEnc; + private final CustomWeighting.TurnPenaltyMapping turnPenaltyMapping; - public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, DecimalEncodedValue orientationEnc, - Graph graph, TurnCostsConfig tcConfig) { + 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)"); @@ -62,32 +53,10 @@ public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, DecimalEn this.turnRestrictionEnc = turnRestrictionEnc; this.turnCostStorage = graph.getTurnCostStorage(); - this.orientationEnc = orientationEnc; - if (tcConfig.getMinUTurnAngle() > 180) - throw new IllegalArgumentException("Illegal min_u_turn_angle = " + tcConfig.getMinUTurnAngle()); - if (tcConfig.getMinSharpTurnAngle() > tcConfig.getMinUTurnAngle()) - throw new IllegalArgumentException("Illegal min_sharp_turn_angle = " + tcConfig.getMinSharpTurnAngle()); - if (tcConfig.getMinTurnAngle() > tcConfig.getMinSharpTurnAngle() || tcConfig.getMinTurnAngle() < 0) - throw new IllegalArgumentException("Illegal min_turn_angle = " + tcConfig.getMinTurnAngle()); - if (tcConfig.getLeftTurnCosts() > tcConfig.getSharpLeftTurnCosts()) - throw new IllegalArgumentException("The costs for 'left_turn_costs' (" + tcConfig.getLeftTurnCosts() - + ") must be lower than for 'sharp_left_turn_costs' (" + tcConfig.getSharpLeftTurnCosts() + ")"); - if (tcConfig.getRightTurnCosts() > tcConfig.getSharpRightTurnCosts()) - throw new IllegalArgumentException("The costs for 'right_turn_costs' (" + tcConfig.getRightTurnCosts() - + ") must be lower than for 'sharp_right_turn_costs' (" + tcConfig.getSharpRightTurnCosts() + ")"); - - this.minTurnAngle = tcConfig.getMinTurnAngle(); - this.minSharpTurnAngle = tcConfig.getMinSharpTurnAngle(); - this.minUTurnAngle = tcConfig.getMinUTurnAngle(); - - this.leftTurnCosts = tcConfig.getLeftTurnCosts(); - this.sharpLeftTurnCosts = tcConfig.getSharpLeftTurnCosts(); - this.straightCosts = tcConfig.getStraightCosts(); - this.rightTurnCosts = tcConfig.getRightTurnCosts(); - this.sharpRightTurnCosts = tcConfig.getSharpRightTurnCosts(); - this.graph = graph.getBaseGraph(); this.edgeIntAccess = graph.getBaseGraph().getEdgeAccess(); + + this.turnPenaltyMapping = turnPenaltyMapping; } @Override @@ -99,27 +68,12 @@ public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { if (inEdge == outEdge) { // note that the u-turn costs overwrite any turn costs set in TurnCostStorage return uTurnCosts; - } else { - if (turnRestrictionEnc != null && turnCostStorage.get(turnRestrictionEnc, inEdge, viaNode, outEdge)) + } else if (turnRestrictionEnc != null) { + if (turnCostStorage.get(turnRestrictionEnc, inEdge, viaNode, outEdge)) return Double.POSITIVE_INFINITY; } - - if (orientationEnc != null) { - double changeAngle = calcChangeAngle(inEdge, viaNode, outEdge); - if (changeAngle > -minTurnAngle && changeAngle < minTurnAngle) - return straightCosts; - else if (changeAngle >= minTurnAngle && changeAngle < minSharpTurnAngle) - return rightTurnCosts; - else if (changeAngle >= minSharpTurnAngle && changeAngle <= minUTurnAngle) - return sharpRightTurnCosts; - else if (changeAngle <= -minTurnAngle && changeAngle > -minSharpTurnAngle) - return leftTurnCosts; - else if (changeAngle <= -minSharpTurnAngle && changeAngle >= -minUTurnAngle) - return sharpLeftTurnCosts; - - // Too sharp turn is like an u-turn. - return uTurnCosts; - } + if (turnPenaltyMapping != null) + return turnPenaltyMapping.get(graph, edgeIntAccess, inEdge, viaNode, outEdge); return 0; } @@ -137,26 +91,4 @@ public long calcTurnMillis(int inEdge, int viaNode, int outEdge) { public String toString() { return "default_tcp_" + uTurnCostsInt; } - - double calcChangeAngle(int inEdge, int viaNode, int outEdge) { - // this 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) - boolean inEdgeReverse = !graph.isAdjNode(inEdge, viaNode); - double prevAzimuth = orientationEnc.getDecimal(inEdgeReverse, inEdge, edgeIntAccess); - - boolean outEdgeReverse = !graph.isAdjNode(outEdge, viaNode); - double azimuth = orientationEnc.getDecimal(outEdgeReverse, outEdge, edgeIntAccess); - - // bring parallel to prevOrientation - if (azimuth >= 180) azimuth -= 180; - else azimuth += 180; - - double changeAngle = azimuth - prevAzimuth; - - // keep in [-180, 180] - if (changeAngle > 180) changeAngle -= 360; - else if (changeAngle < -180) changeAngle += 360; - return changeAngle; - } } 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 7fd80c3fb1b..ff737c25e78 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 @@ -20,14 +20,15 @@ import com.graphhopper.json.Statement; import com.graphhopper.routing.ev.*; 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; @@ -36,6 +37,7 @@ 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; @@ -43,6 +45,8 @@ 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"; 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"); @@ -75,15 +79,9 @@ private CustomModelParser() { 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); - return new CustomWeighting(turnCostProvider, parameters); - } - public static CustomWeighting2 createWeighting2(EncodedValueLookup lookup, TurnCostProvider turnCostProvider, CustomModel customModel) { - if (customModel == null) - throw new IllegalStateException("CustomModel cannot be null"); CustomWeighting.Parameters parameters = createWeightingParameters(customModel, lookup); - return new CustomWeighting2(turnCostProvider, parameters); + return new CustomWeighting(turnCostProvider, parameters); } /** @@ -117,6 +115,7 @@ public static CustomWeighting.Parameters createWeightingParameters(CustomModel c 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) { @@ -159,14 +158,17 @@ private static Class createClazz(CustomModel customModel, EncodedValueLookup 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) { @@ -177,10 +179,10 @@ 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 backward_xy or in_xy ... + // 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(BACKWARD_PREFIX) || s.startsWith(IN_AREA_PREFIX)) + if (Character.isUpperCase(s.charAt(0)) || s.startsWith(IN_AREA_PREFIX)) return true; if (nameValidator.isValid(s)) { variables.add(s); @@ -250,6 +252,10 @@ 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 = " + CustomWeightingHelper.GLOBAL_PRIORITY + ";\n"; @@ -261,12 +267,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) : " + @@ -288,6 +349,35 @@ 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 (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 @@ -320,11 +410,15 @@ 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; @@ -335,6 +429,8 @@ private static String createClassTemplate(long counter, 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)) { @@ -395,6 +491,10 @@ private static String createClassTemplate(long counter, + " public double getSpeed(EdgeIteratorState edge, boolean reverse) {\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" + "}"; } @@ -410,9 +510,15 @@ 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)); + || name.toUpperCase(Locale.ROOT).equals(name) || name.startsWith(IN_AREA_PREFIX) || name.equals(CHANGE_ANGLE) + || 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"); @@ -420,6 +526,18 @@ private static List verifyExpressions(StringBuilder express parseBlockStatements(); } + private static Function createSimplifiedLookup(EncodedValueLookup lookup) { + return key -> { + 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 classHelper, String indentation) { @@ -468,10 +586,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 { @@ -481,6 +601,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 e61744eea1d..ad0cad62b7c 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,8 +17,10 @@ */ package com.graphhopper.routing.weighting.custom; +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; @@ -164,6 +166,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(); @@ -174,16 +180,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; } @@ -204,6 +213,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 a047d0db726..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 @@ -19,7 +19,10 @@ 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; @@ -47,19 +50,15 @@ public void init(CustomModel customModel, EncodedValueLookup lookup, Map { - 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; @@ -60,8 +61,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 @@ -74,14 +74,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) { @@ -107,8 +105,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("-")) { @@ -184,7 +181,7 @@ private static void findVariablesForGroup(Set createdObjects, List 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) diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index 75df6ad7d0d..e5aefdbb07a 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -152,6 +152,10 @@ public int getEdges() { return store.getEdges(); } + public int getEdgeKeys() { + return 2 * store.getEdges(); + } + @Override public NodeAccess getNodeAccess() { return nodeAccess; diff --git a/core/src/main/java/com/graphhopper/storage/CHStorage.java b/core/src/main/java/com/graphhopper/storage/CHStorage.java index a54a178048e..a96fee5c9ec 100644 --- a/core/src/main/java/com/graphhopper/storage/CHStorage.java +++ b/core/src/main/java/com/graphhopper/storage/CHStorage.java @@ -63,11 +63,17 @@ 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; + private Consumer lowWeightShortcutConsumer; + 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(); @@ -75,13 +81,21 @@ public static CHStorage fromGraph(BaseGraph baseGraph, CHConfig chConfig) { 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 -> { + store.setLowWeightShortcutConsumer(s -> { // we just log these to find mapping errors NodeAccess nodeAccess = baseGraph.getNodeAccess(); LOGGER.warn("Setting weights smaller than " + s.minWeight + " is not allowed. " + "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) + ")"); + }); + store.setHighWeightShortcutConsumer(s -> { + // we just log these to find potential routing errors + NodeAccess nodeAccess = baseGraph.getNodeAccess(); + 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) + ")"); }); // 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 @@ -116,8 +130,12 @@ public CHStorage(Directory dir, String name, int segmentSize, boolean edgeBased) /** * 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 setLowWeightShortcutConsumer(Consumer lowWeightShortcutConsumer) { + this.lowWeightShortcutConsumer = lowWeightShortcutConsumer; + } + + public void setHighWeightShortcutConsumer(Consumer highWeightShortcutConsumer) { + this.highWeightShortcutConsumer = highWeightShortcutConsumer; } /** @@ -150,8 +168,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 +189,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 +222,14 @@ 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 (lowWeightShortcutConsumer != null && weight < MIN_WEIGHT) + lowWeightShortcutConsumer.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 >= MIN_WEIGHT && 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 +411,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() { @@ -402,10 +444,12 @@ public boolean isClosed() { private int weightFromDouble(double weight) { if (weight < 0) throw new IllegalArgumentException("weight cannot be negative but was " + weight); - if (weight < MIN_WEIGHT) + if (weight < MIN_WEIGHT) { + numShortcutsUnderMinWeight++; weight = MIN_WEIGHT; + } if (weight >= MAX_WEIGHT) { - numShortcutsExceedingWeight++; + numShortcutsOverMaxWeight++; return (int) MAX_STORED_INTEGER_WEIGHT; // negative } else return (int) Math.round(weight * WEIGHT_FACTOR); @@ -439,4 +483,20 @@ public LowWeightShortcut(int nodeA, int nodeB, int shortcut, double weight, doub this.minWeight = minWeight; } } + + public static class HighWeightShortcut { + int nodeA; + int nodeB; + int shortcut; + double weight; + double maxWeight; + + 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.maxWeight = maxWeight; + } + } } 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/util/ArrayUtil.java b/core/src/main/java/com/graphhopper/util/ArrayUtil.java index 7d630690273..6be39f8bea7 100644 --- a/core/src/main/java/com/graphhopper/util/ArrayUtil.java +++ b/core/src/main/java/com/graphhopper/util/ArrayUtil.java @@ -263,4 +263,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/Constants.java b/core/src/main/java/com/graphhopper/util/Constants.java index f2ac1444fd4..1920bbd01e4 100644 --- a/core/src/main/java/com/graphhopper/util/Constants.java +++ b/core/src/main/java/com/graphhopper/util/Constants.java @@ -62,7 +62,7 @@ public class Constants { 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 = 4; - public static final int VERSION_SHORTCUT = 9; + public static final int VERSION_SHORTCUT = 10; public static final int VERSION_NODE_CH = 0; public static final int VERSION_GEOMETRY = 7; public static final int VERSION_TURN_COSTS = 0; 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/PathDetailsBuilderFactory.java b/core/src/main/java/com/graphhopper/util/details/PathDetailsBuilderFactory.java index a2dd1d9ff05..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,6 +43,8 @@ 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")) 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 555ffbaf4af..882f42c0b4c 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bike.json @@ -8,7 +8,7 @@ { "priority": [ { "if": "true", "multiply_by": "bike_priority" }, - { "if": "bike_road_access == PRIVATE && foot_road_access != YES", "multiply_by": "0" }, + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "multiply_by": "0.1" }, { "if": "mtb_rating > 2", "multiply_by": "0" }, { "if": "hike_rating > 1", "multiply_by": "0" }, { "if": "country == DEU && road_class == BRIDLEWAY && bike_road_access != YES", "multiply_by": "0" }, @@ -17,7 +17,7 @@ ], "speed": [ { "if": "true", "limit_to": "bike_average_speed" }, - { "if": "!bike_access && backward_bike_access", "limit_to": "5" }, - { "if": "bike_road_access == PRIVATE && foot_road_access == YES", "limit_to": "5" } + { "if": "!bike_access && backward_bike_access", "limit_to": "6" }, + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "limit_to": "6" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_etc.json b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_etc.json new file mode 100644 index 00000000000..dbdf114c1e1 --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_avoid_private_etc.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 == DESTINATION || bike_road_access == PRIVATE)", + "add": "2000" + } + ] +} 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 index 20d04b1a56f..105fa426f0d 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json @@ -1,8 +1,9 @@ -// Configures bike with turn costs (3sec for left and right turns) which reduces zig-zag routes. +// 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 +// 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 // profiles: @@ -10,21 +11,16 @@ // turn_costs: // vehicle_types: [bicycle] // u_turn_costs: 20 -// left_turn_costs: 3 -// sharp_left_turn_costs: 3 -// right_turn_costs: 3 -// sharp_right_turn_costs: 3 -// custom_model_files: [bike_tc.json, bike_elevation.json] +// custom_model_files: [bike_tc.json, avoid_turns.json, bike_avoid_private_etc.json, bike_elevation.json] { "priority": [ { "if": "true", "multiply_by": "bike_priority" }, - { "if": "bike_road_access == PRIVATE", "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", "limit_to": "5" } + { "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 c19bf0501a7..b8453e238af 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/bus.json +++ b/core/src/main/resources/com/graphhopper/custom_models/bus.json @@ -9,6 +9,7 @@ { "distance_influence": 90, "priority": [ + // also have a look into car_avoid_private_etc.json { "if": "road_access == PRIVATE", "multiply_by": "0" }, { "if": "road_access == DESTINATION", "multiply_by": "0.1" }, { "if": "max_weight < 5 || max_width < 3 || max_height < 4", "multiply_by": "0" }, diff --git a/core/src/main/resources/com/graphhopper/custom_models/car.json b/core/src/main/resources/com/graphhopper/custom_models/car.json index ecf9629fc58..7242866a4d5 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/car.json +++ b/core/src/main/resources/com/graphhopper/custom_models/car.json @@ -1,16 +1,15 @@ // to use this custom model you need to set the following option in the config.yml -// graph.encoded_values: car_access, car_average_speed, road_access +// graph.encoded_values: car_access|block_private=false, car_average_speed, road_access // profiles: // - name: car // turn_costs: // vehicle_types: [motorcar, motor_vehicle] -// custom_model_files: [car.json] +// custom_model_files: [car.json, car_avoid_private_etc.json ] { "distance_influence": 90, "priority": [ - { "if": "!car_access", "multiply_by": "0" }, - { "if": "road_access == DESTINATION || road_access == PRIVATE", "multiply_by": "0.1" } + { "if": "!car_access", "multiply_by": "0" } ], "speed": [ { "if": "true", "limit_to": "car_average_speed" } 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 5af53d71b9d..bd3bcc4e413 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/car4wd.json +++ b/core/src/main/resources/com/graphhopper/custom_models/car4wd.json @@ -4,13 +4,11 @@ // - name: car4wd // turn_costs: // vehicle_types: [motorcar, motor_vehicle -// custom_model_files: [car4wd.json] +// custom_model_files: [car4wd.json, car_avoid_private_etc.json] { "distance_influence": 1, "priority": [ - { "if": "road_access == PRIVATE", "multiply_by": "0" }, - { "if": "road_access == DESTINATION", "multiply_by": "0.1" }, { "if": "track_type != GRADE4 && track_type != GRADE5 && car_access == false", "multiply_by": "0" } ], "speed": [ 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..14b424ac938 --- /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)", + "add": "2000" + } + ] +} 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 fda9eeaea69..385f836d583 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,7 @@ { "priority": [ - { "if": "bike_road_access == PRIVATE", "multiply_by": "0" }, + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "multiply_by": "0.1" }, { "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 }, @@ -13,6 +13,7 @@ ], "speed": [ { "if": "road_class == PRIMARY", "limit_to": 28 }, - { "else": "", "limit_to": 25 } + { "else": "", "limit_to": 25 }, + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "limit_to": "6" } ] } diff --git a/core/src/main/resources/com/graphhopper/custom_models/foot.json b/core/src/main/resources/com/graphhopper/custom_models/foot.json index 06cdac0d801..06451a20473 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/foot.json +++ b/core/src/main/resources/com/graphhopper/custom_models/foot.json @@ -7,10 +7,12 @@ { "priority": [ - { "if": "!foot_access || hike_rating >= 2 || mtb_rating > 2", "multiply_by": "0" }, + { "if": "!foot_access || hike_rating >= 2", "multiply_by": "0" }, { "else": "", "multiply_by": "foot_priority"}, { "if": "country == DEU && road_class == BRIDLEWAY && foot_road_access != YES", "multiply_by": "0" }, - { "if": "foot_road_access == PRIVATE", "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": "foot_road_access == PRIVATE", "multiply_by": "0.1" } ], "speed": [ { "if": "true", "limit_to": "foot_average_speed" } 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 040dfd3359b..0ce2bd18bf2 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/hike.json +++ b/core/src/main/resources/com/graphhopper/custom_models/hike.json @@ -7,9 +7,9 @@ { "priority": [ - { "if": "!foot_access || hike_rating >= 5", "multiply_by": "0"}, + { "if": "!foot_access || hike_rating >= 6", "multiply_by": "0"}, { "else": "", "multiply_by": "foot_priority"}, - { "if": "foot_road_access == PRIVATE", "multiply_by": "0" }, + { "if": "foot_road_access == PRIVATE", "multiply_by": "0.1" }, { "if": "foot_network == INTERNATIONAL || foot_network == NATIONAL", "multiply_by": "1.7"}, { "else_if": "foot_network == REGIONAL || foot_network == LOCAL", "multiply_by": "1.5"} ], 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 aa37a076310..903d32ceb93 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json +++ b/core/src/main/resources/com/graphhopper/custom_models/motorcycle.json @@ -10,6 +10,7 @@ "priority": [ { "if": "!car_access", "multiply_by": "0"}, { "if": "track_type.ordinal() > 1", "multiply_by": "0" }, + // also have a look into car_avoid_private_etc.json { "if": "road_access == PRIVATE", "multiply_by": "0" }, { "if": "road_access == DESTINATION", "multiply_by": "0.1" }, { "if": "road_class == MOTORWAY || road_class == TRUNK", "multiply_by": "0.1" } diff --git a/core/src/main/resources/com/graphhopper/custom_models/mtb.json b/core/src/main/resources/com/graphhopper/custom_models/mtb.json index 6becbf2d27b..c558d6974a8 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/mtb.json +++ b/core/src/main/resources/com/graphhopper/custom_models/mtb.json @@ -14,11 +14,12 @@ { "if": "country == DEU && road_class == BRIDLEWAY && bike_road_access != YES", "multiply_by": "0" }, { "if": "!mtb_access && (!backward_mtb_access || roundabout)", "multiply_by": "0" }, { "else_if": "!mtb_access && backward_mtb_access", "multiply_by": "0.2" }, - { "if": "bike_road_access == PRIVATE", "multiply_by": "0" } + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "multiply_by": "0.1" } ], "speed": [ { "if": "true", "limit_to": "mtb_average_speed" }, { "if": "mtb_rating > 3", "limit_to": "4" }, - { "if": "!mtb_access && backward_mtb_access", "limit_to": "5" } + { "if": "!mtb_access && backward_mtb_access", "limit_to": "6" }, + { "if": "bike_road_access == PRIVATE || bike_road_access == DESTINATION", "limit_to": "6" } ] } 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 c214bf10519..01e25ff0010 100644 --- a/core/src/main/resources/com/graphhopper/custom_models/truck.json +++ b/core/src/main/resources/com/graphhopper/custom_models/truck.json @@ -11,6 +11,7 @@ "priority": [ { "if": "hgv == NO", "multiply_by": "0" }, { "if": "!car_access && road_access != PRIVATE && hgv != DELIVERY && hgv != DESTINATION", "multiply_by": "0" }, + // also have a look into car_avoid_private_etc.json { "if": "road_access == PRIVATE || 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" } diff --git a/core/src/main/resources/com/graphhopper/util/ar.txt b/core/src/main/resources/com/graphhopper/util/ar.txt index 15e8989f47a..e48bb107c1d 100644 --- a/core/src/main/resources/com/graphhopper/util/ar.txt +++ b/core/src/main/resources/com/graphhopper/util/ar.txt @@ -26,16 +26,18 @@ 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 اجمالى صعود @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/ast.txt b/core/src/main/resources/com/graphhopper/util/ast.txt index 11a7de441f0..eddbd46045a 100644 --- a/core/src/main/resources/com/graphhopper/util/ast.txt +++ b/core/src/main/resources/com/graphhopper/util/ast.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/az.txt b/core/src/main/resources/com/graphhopper/util/az.txt index 8ea40f5dc75..99e69062f35 100644 --- a/core/src/main/resources/com/graphhopper/util/az.txt +++ b/core/src/main/resources/com/graphhopper/util/az.txt @@ -36,6 +36,8 @@ 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ş @@ -52,7 +54,8 @@ 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=Çətin və ya təhlükəli hissələr +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 diff --git a/core/src/main/resources/com/graphhopper/util/bg.txt b/core/src/main/resources/com/graphhopper/util/bg.txt index f21b38697ce..f45919902c2 100644 --- a/core/src/main/resources/com/graphhopper/util/bg.txt +++ b/core/src/main/resources/com/graphhopper/util/bg.txt @@ -36,6 +36,8 @@ 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 общо изкачване @@ -53,6 +55,7 @@ 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 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 19b22854eeb..8c6d8a13533 100644 --- a/core/src/main/resources/com/graphhopper/util/bn_BN.txt +++ b/core/src/main/resources/com/graphhopper/util/bn_BN.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/ca.txt b/core/src/main/resources/com/graphhopper/util/ca.txt index ddc9423bb66..7bf11862e61 100644 --- a/core/src/main/resources/com/graphhopper/util/ca.txt +++ b/core/src/main/resources/com/graphhopper/util/ca.txt @@ -36,6 +36,8 @@ 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 @@ -52,7 +54,8 @@ web.footways=vorera web.steep_sections=tram escarpat web.private_sections=tram privat web.restricted_sections= -web.challenging_sections=tram difícil o perillós +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 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 33f1dc01b9e..30de6c7c9af 100644 --- a/core/src/main/resources/com/graphhopper/util/cs_CZ.txt +++ b/core/src/main/resources/com/graphhopper/util/cs_CZ.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 4d89aa7e86a..a9e1eb901e0 100644 --- a/core/src/main/resources/com/graphhopper/util/da_DK.txt +++ b/core/src/main/resources/com/graphhopper/util/da_DK.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 12ddc203136..92371ad2d45 100644 --- a/core/src/main/resources/com/graphhopper/util/de_DE.txt +++ b/core/src/main/resources/com/graphhopper/util/de_DE.txt @@ -33,9 +33,11 @@ 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 @@ -52,7 +54,8 @@ web.footways=Fußwege web.steep_sections=sehr steile Passagen web.private_sections=private Abschnitte web.restricted_sections=zugangsbeschränkte Abschnitte -web.challenging_sections=herausfordernde oder gefährliche 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=Vom Fahrrad absteigen und für %1$s schieben pt_transfer_to=umsteigen auf %1$s diff --git a/core/src/main/resources/com/graphhopper/util/el.txt b/core/src/main/resources/com/graphhopper/util/el.txt index 60c64c86e54..ac47700dc0a 100644 --- a/core/src/main/resources/com/graphhopper/util/el.txt +++ b/core/src/main/resources/com/graphhopper/util/el.txt @@ -36,6 +36,8 @@ 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 συνολική ανάβαση @@ -52,7 +54,8 @@ web.footways=μονοπάτια web.steep_sections=απότομα τμήματα web.private_sections=ιδιωτικά τμήματα web.restricted_sections=περιορισμένα τμήματα -web.challenging_sections=απαιτητικά ή επικίνδυνα τμήματα +web.challenging_sections=Διαδρομή περιλαμβάνει απαιτητικά ή επικίνδυνα τμήματα +web.dangerous_sections= web.trunk_roads_warn=διαδρομή περιλαμβάνει πιθανά επικίνδυνους αυτοκινητρόδρομους ή χειρότερα web.get_off_bike_for=Πρέπει να κατεβείτε από το ποδήλατο και να σπρώξετε για %1$s pt_transfer_to=αλλάξτε στο %1$s @@ -217,7 +220,7 @@ web.poi_pharmacies=φαρμακείο, φαρμακοποιός, φαρμακε web.poi_playgrounds=παιδική χαρά, παιδικές χαρές, παιδότοπος web.poi_public_transit=δημόσια συγκοινωνία, ΜΜΕ web.poi_police=αστυνομία, ΑΤ -web.poi_post= +web.poi_post=ταχυδρομείο, ταχυδρομική υπηρεσία, γραμματόσημα web.poi_post_box= web.poi_railway_station=σταθμός τρένου, στάση τρένου, τρένο, τρένα, σιδηρόδρομος, σιδηροδρομικός σταθμός, σιδηροδρομική στάση web.poi_recycling=ανακύκλωση @@ -227,7 +230,7 @@ web.poi_shopping=μαγαζί, κατάστημα, ψώνια, αγορά web.poi_shop_bakery=φούρνος, αρτοποιείο web.poi_shop_butcher=κρεοπώλης, κρεοπωλείο, αγορά κρέατος, κρέας web.poi_super_markets=σούπερ μάρκετ, υπεραγορά, αγορά -web.poi_swim= +web.poi_swim=κολύμβηση, κολύμπι, μπάνιο web.poi_toilets=τουαλέτα, τουαλέτες, WC web.poi_tourism=τουρισμός, τουριστικό, τουριστικά web.poi_townhall=δημαρχείο, κοινότητα, γραφείο κοινότητας 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 6cff5ce4cef..4fa3d073f65 100644 --- a/core/src/main/resources/com/graphhopper/util/en_US.txt +++ b/core/src/main/resources/com/graphhopper/util/en_US.txt @@ -33,9 +33,11 @@ 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 @@ -52,7 +54,8 @@ web.footways=footways web.steep_sections=steep sections web.private_sections=private sections web.restricted_sections=restricted sections -web.challenging_sections=challenging or dangerous 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=Get off the bike and push for %1$s pt_transfer_to=change to %1$s diff --git a/core/src/main/resources/com/graphhopper/util/eo.txt b/core/src/main/resources/com/graphhopper/util/eo.txt index b6cb08ea403..00bcd941595 100644 --- a/core/src/main/resources/com/graphhopper/util/eo.txt +++ b/core/src/main/resources/com/graphhopper/util/eo.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/es.txt b/core/src/main/resources/com/graphhopper/util/es.txt index 12e07500982..3c404d0edfe 100644 --- a/core/src/main/resources/com/graphhopper/util/es.txt +++ b/core/src/main/resources/com/graphhopper/util/es.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/fa.txt b/core/src/main/resources/com/graphhopper/util/fa.txt index cb82093f000..822800bc779 100644 --- a/core/src/main/resources/com/graphhopper/util/fa.txt +++ b/core/src/main/resources/com/graphhopper/util/fa.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 تغییر دهید diff --git a/core/src/main/resources/com/graphhopper/util/fi.txt b/core/src/main/resources/com/graphhopper/util/fi.txt index d552a0fa7e0..8d21afb2264 100644 --- a/core/src/main/resources/com/graphhopper/util/fi.txt +++ b/core/src/main/resources/com/graphhopper/util/fi.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/fil.txt b/core/src/main/resources/com/graphhopper/util/fil.txt index 3bbc3d477e2..9600eb32611 100644 --- a/core/src/main/resources/com/graphhopper/util/fil.txt +++ b/core/src/main/resources/com/graphhopper/util/fil.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= 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 a9493ef7c22..60148193929 100644 --- a/core/src/main/resources/com/graphhopper/util/fr_CH.txt +++ b/core/src/main/resources/com/graphhopper/util/fr_CH.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 8a41d92da80..fa2578f9c54 100644 --- a/core/src/main/resources/com/graphhopper/util/fr_FR.txt +++ b/core/src/main/resources/com/graphhopper/util/fr_FR.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/gl.txt b/core/src/main/resources/com/graphhopper/util/gl.txt index 453212a420b..eb5de9a6673 100644 --- a/core/src/main/resources/com/graphhopper/util/gl.txt +++ b/core/src/main/resources/com/graphhopper/util/gl.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/he.txt b/core/src/main/resources/com/graphhopper/util/he.txt index ab4ecab4c9f..52cbf1fde45 100644 --- a/core/src/main/resources/com/graphhopper/util/he.txt +++ b/core/src/main/resources/com/graphhopper/util/he.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 b72d94975ce..00886c4fe8e 100644 --- a/core/src/main/resources/com/graphhopper/util/hr_HR.txt +++ b/core/src/main/resources/com/graphhopper/util/hr_HR.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/hsb.txt b/core/src/main/resources/com/graphhopper/util/hsb.txt index 4ba34492e12..2fbc335ecf2 100644 --- a/core/src/main/resources/com/graphhopper/util/hsb.txt +++ b/core/src/main/resources/com/graphhopper/util/hsb.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= 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 bc6796c9965..8d1cfe636fd 100644 --- a/core/src/main/resources/com/graphhopper/util/hu_HU.txt +++ b/core/src/main/resources/com/graphhopper/util/hu_HU.txt @@ -36,6 +36,8 @@ 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 @@ -52,7 +54,8 @@ web.footways=gyalogút web.steep_sections=meredek szakasz web.private_sections=magánút web.restricted_sections=korlátozott szakasz -web.challenging_sections=nehéz vagy veszélyes szakaszok +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 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 b38fe6016f7..22799b762fa 100644 --- a/core/src/main/resources/com/graphhopper/util/in_ID.txt +++ b/core/src/main/resources/com/graphhopper/util/in_ID.txt @@ -36,6 +36,8 @@ stopover=titik hubung %1$s roundabout_enter=Masuk bundaran roundabout_exit=Pada bundaran, keluar melalui %1$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 @@ -52,7 +54,8 @@ web.footways=Jalan setapak web.steep_sections=bagian curam web.private_sections=bagian pribadi web.restricted_sections= -web.challenging_sections=Bagian yang menantang atau berbahaya +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 diff --git a/core/src/main/resources/com/graphhopper/util/it.txt b/core/src/main/resources/com/graphhopper/util/it.txt index 0f7fb84daea..6794a4b9c67 100644 --- a/core/src/main/resources/com/graphhopper/util/it.txt +++ b/core/src/main/resources/com/graphhopper/util/it.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/ja.txt b/core/src/main/resources/com/graphhopper/util/ja.txt index 9fe495bf82f..2b9147ead74 100644 --- a/core/src/main/resources/com/graphhopper/util/ja.txt +++ b/core/src/main/resources/com/graphhopper/util/ja.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/ko.txt b/core/src/main/resources/com/graphhopper/util/ko.txt index 2a692c9066b..5aeba4251a5 100644 --- a/core/src/main/resources/com/graphhopper/util/ko.txt +++ b/core/src/main/resources/com/graphhopper/util/ko.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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(으)로 환승 diff --git a/core/src/main/resources/com/graphhopper/util/kz.txt b/core/src/main/resources/com/graphhopper/util/kz.txt index 4242e412ab6..d6d9232dbef 100644 --- a/core/src/main/resources/com/graphhopper/util/kz.txt +++ b/core/src/main/resources/com/graphhopper/util/kz.txt @@ -36,6 +36,8 @@ 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 көтерілу @@ -53,6 +55,7 @@ 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-ған отырыңыз 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 c161f7d849a..f6cc7b8eaf9 100644 --- a/core/src/main/resources/com/graphhopper/util/lt_LT.txt +++ b/core/src/main/resources/com/graphhopper/util/lt_LT.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/mn.txt b/core/src/main/resources/com/graphhopper/util/mn.txt index c62a10fb920..699273e8ee5 100644 --- a/core/src/main/resources/com/graphhopper/util/mn.txt +++ b/core/src/main/resources/com/graphhopper/util/mn.txt @@ -36,6 +36,8 @@ 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 нийт өгсөнө @@ -52,7 +54,8 @@ web.footways=явган хүний ​​зам web.steep_sections=эгц хэсгүүд web.private_sections=хувийн хэсгүүд web.restricted_sections= -web.challenging_sections=хүнд хэцүү эсвэл аюултай хэсгүүд +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn=Маршрут нь аюултай байж болзошгүй гол зам буюу түүнээс ч дор байдаг web.get_off_bike_for=Унадаг дугуйнаасаа бууж, %1$s руу түлхэнэ үү pt_transfer_to=%1$s болгож өөрчлөх 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 5d66e6fb1c5..eb7ec4a23a9 100644 --- a/core/src/main/resources/com/graphhopper/util/nb_NO.txt +++ b/core/src/main/resources/com/graphhopper/util/nb_NO.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/ne.txt b/core/src/main/resources/com/graphhopper/util/ne.txt index 9fc35ffb971..449a9bbd20e 100644 --- a/core/src/main/resources/com/graphhopper/util/ne.txt +++ b/core/src/main/resources/com/graphhopper/util/ne.txt @@ -36,6 +36,8 @@ 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= @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/nl.txt b/core/src/main/resources/com/graphhopper/util/nl.txt index 0de1f3d9788..86b6045728c 100644 --- a/core/src/main/resources/com/graphhopper/util/nl.txt +++ b/core/src/main/resources/com/graphhopper/util/nl.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 @@ -101,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= 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 8185c5fc637..12085f5fd84 100644 --- a/core/src/main/resources/com/graphhopper/util/pl_PL.txt +++ b/core/src/main/resources/com/graphhopper/util/pl_PL.txt @@ -36,6 +36,8 @@ 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ę @@ -53,6 +55,7 @@ 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 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 cef4f672d1b..ee90767598e 100644 --- a/core/src/main/resources/com/graphhopper/util/pt_BR.txt +++ b/core/src/main/resources/com/graphhopper/util/pt_BR.txt @@ -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,25 +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 -leave_ferry= -board_ferry= +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_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.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 @@ -64,17 +67,17 @@ 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= @@ -148,21 +151,21 @@ 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.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= @@ -246,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 3de9eac6335..f26c8b58906 100644 --- a/core/src/main/resources/com/graphhopper/util/pt_PT.txt +++ b/core/src/main/resources/com/graphhopper/util/pt_PT.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/ro.txt b/core/src/main/resources/com/graphhopper/util/ro.txt index cb1b3a2a2b0..45c94ed15fc 100644 --- a/core/src/main/resources/com/graphhopper/util/ro.txt +++ b/core/src/main/resources/com/graphhopper/util/ro.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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= diff --git a/core/src/main/resources/com/graphhopper/util/ru.txt b/core/src/main/resources/com/graphhopper/util/ru.txt index fb611d2296f..32923a563cc 100644 --- a/core/src/main/resources/com/graphhopper/util/ru.txt +++ b/core/src/main/resources/com/graphhopper/util/ru.txt @@ -36,6 +36,8 @@ 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 @@ -52,7 +54,8 @@ web.footways=пешеходные дорожки web.steep_sections=крутые участки web.private_sections=частный сектор web.restricted_sections= -web.challenging_sections=сложные или опасные участки +web.challenging_sections= +web.dangerous_sections= web.trunk_roads_warn=Маршрут содержит потенциально опасные магистральные дороги web.get_off_bike_for=Сойдите с велосипеда и двигайтесь %1$s pt_transfer_to=Пересядьте на %1$s diff --git a/core/src/main/resources/com/graphhopper/util/sk.txt b/core/src/main/resources/com/graphhopper/util/sk.txt index b94b64a615a..31aea6edc8e 100644 --- a/core/src/main/resources/com/graphhopper/util/sk.txt +++ b/core/src/main/resources/com/graphhopper/util/sk.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 bfa0f388c31..62863c798cc 100644 --- a/core/src/main/resources/com/graphhopper/util/sl_SI.txt +++ b/core/src/main/resources/com/graphhopper/util/sl_SI.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 b14760ab75a..601df5a9452 100644 --- a/core/src/main/resources/com/graphhopper/util/sr_RS.txt +++ b/core/src/main/resources/com/graphhopper/util/sr_RS.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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= 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 84743985d3b..44f4b41dd64 100644 --- a/core/src/main/resources/com/graphhopper/util/sv_SE.txt +++ b/core/src/main/resources/com/graphhopper/util/sv_SE.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/tr.txt b/core/src/main/resources/com/graphhopper/util/tr.txt index 18d5aa858d1..39ffb2ede7d 100644 --- a/core/src/main/resources/com/graphhopper/util/tr.txt +++ b/core/src/main/resources/com/graphhopper/util/tr.txt @@ -36,6 +36,8 @@ 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ış @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/uk.txt b/core/src/main/resources/com/graphhopper/util/uk.txt index 25e699d26b9..df469b1cff7 100644 --- a/core/src/main/resources/com/graphhopper/util/uk.txt +++ b/core/src/main/resources/com/graphhopper/util/uk.txt @@ -36,6 +36,8 @@ 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 загалом підйому @@ -53,6 +55,7 @@ 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 diff --git a/core/src/main/resources/com/graphhopper/util/uz.txt b/core/src/main/resources/com/graphhopper/util/uz.txt index c66bb555753..1e9de51e926 100644 --- a/core/src/main/resources/com/graphhopper/util/uz.txt +++ b/core/src/main/resources/com/graphhopper/util/uz.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 98fa44bf78b..8b64c6c52da 100644 --- a/core/src/main/resources/com/graphhopper/util/vi_VN.txt +++ b/core/src/main/resources/com/graphhopper/util/vi_VN.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 bc7033e6849..a1a243adbf2 100644 --- a/core/src/main/resources/com/graphhopper/util/zh_CN.txt +++ b/core/src/main/resources/com/graphhopper/util/zh_CN.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 63b2485c26b..fe2d665d1a7 100644 --- a/core/src/main/resources/com/graphhopper/util/zh_HK.txt +++ b/core/src/main/resources/com/graphhopper/util/zh_HK.txt @@ -36,6 +36,8 @@ 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 @@ -53,6 +55,7 @@ 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 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 6b7424d77b4..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,254 +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= -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 -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.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= +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/GraphHopperTest.java b/core/src/test/java/com/graphhopper/GraphHopperTest.java index 6f74b3d9a0e..d0fd93b3f04 100644 --- a/core/src/test/java/com/graphhopper/GraphHopperTest.java +++ b/core/src/test/java/com/graphhopper/GraphHopperTest.java @@ -1417,8 +1417,8 @@ public void testMultipleVehiclesWithCH() { .setProfile(bikeProfile)); res = rsp.getBest(); assertFalse(rsp.hasErrors(), rsp.getErrors().toString()); - assertEquals(536, res.getTime() / 1000f, 1); - assertEquals(2522, 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")); @@ -2078,34 +2078,6 @@ 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). - setEncodedValuesString("bike_access, bike_priority, bike_average_speed"). - setProfiles(TestProfiles.accessSpeedAndPriority("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(). 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/osm/GraphHopperOSMTest.java b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java index 092c120e3f7..aca5e87cdca 100644 --- a/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java +++ b/core/src/test/java/com/graphhopper/reader/osm/GraphHopperOSMTest.java @@ -424,7 +424,7 @@ public void testFootAndCar() { } @Test - public void testNothingHappensWhenProfilesAreChangedForLoad() { + public void testUnloadProfile() { instance = new GraphHopper().init( new GraphHopperConfig(). putObject("datareader.file", testOsm3). @@ -439,7 +439,7 @@ public void testNothingHappensWhenProfilesAreChangedForLoad() { assertEquals(5, instance.getBaseGraph().getNodes()); instance.close(); - // 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). @@ -448,10 +448,9 @@ public void testNothingHappensWhenProfilesAreChangedForLoad() { 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 encoded values do not matter, since they are ignored when loading the graph anyway instance = new GraphHopper().init( @@ -461,10 +460,8 @@ public void testNothingHappensWhenProfilesAreChangedForLoad() { putObject("graph.encoded_values", "road_class"). putObject("import.osm.ignored_highways", ""). setProfiles(List.of( - TestProfiles.constantSpeed("foot"), TestProfiles.constantSpeed("car") ))). - setOSMFile(testOsm3). setGraphHopperLocation(ghLoc); instance.load(); assertEquals(5, instance.getBaseGraph().getNodes()); @@ -771,7 +768,7 @@ public void testProfilesMustNotBeChanged() { 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(); } { @@ -782,17 +779,17 @@ public void testProfilesMustNotBeChanged() { 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. + // disabling a profile by not 'loading' it is ok GraphHopper hopper = createHopperWithProfiles(List.of( - TestProfiles.constantSpeed("bike1", 60) + 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(); } } diff --git a/core/src/test/java/com/graphhopper/routing/PathTest.java b/core/src/test/java/com/graphhopper/routing/PathTest.java index d97290193ea..c1ecb0d548a 100644 --- a/core/src/test/java/com/graphhopper/routing/PathTest.java +++ b/core/src/test/java/com/graphhopper/routing/PathTest.java @@ -18,10 +18,12 @@ 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.util.parsers.OrientationCalculator; import com.graphhopper.routing.weighting.SpeedWeighting; import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; @@ -46,8 +48,9 @@ public class PathTest { private final DecimalEncodedValue carAvSpeedEnc = new DecimalEncodedValueImpl("speed", 5, 5, true); private final EncodingManager carManager = EncodingManager.start().add(carAvSpeedEnc). - add(VehicleAccess.create("car")).add(Roundabout.create()).add(RoadClass.create()). - add(RoadEnvironment.create()).add(RoadClassLink.create()).add(MaxSpeed.create()).build(); + 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"); @@ -233,7 +236,7 @@ public void calcInstructionsRoundabout(DecimalEncodedValue speedEnc) { // Test instructions List tmpList = getTurnDescriptions(wayList); assertEquals(List.of("continue onto MainStreet 1 2", - "At roundabout, take exit 3 onto 5-8", + "at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -247,7 +250,7 @@ public void calcInstructionsRoundabout(DecimalEncodedValue speedEnc) { wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); tmpList = getTurnDescriptions(wayList); assertEquals(List.of("continue onto MainStreet 1 2", - "At roundabout, take exit 2 onto MainStreet 4 7", + "at roundabout, take exit 2 onto MainStreet 4 7", "arrive at destination"), tmpList); // Test Radian @@ -264,7 +267,7 @@ public void testCalcInstructionsRoundaboutBegin() { assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(List.of("At roundabout, take exit 3 onto 5-8", + assertEquals(List.of("at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); } @@ -279,7 +282,7 @@ public void testCalcInstructionsRoundaboutDirectExit() { InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); assertEquals(List.of("continue onto 3-6", - "At roundabout, take exit 3 onto 5-8", + "at roundabout, take exit 3 onto 5-8", "arrive at destination"), tmpList); roundaboutGraph.inverse3to9(); @@ -488,6 +491,34 @@ public void testCalcIntersectionDetails() { 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 */ @@ -501,7 +532,7 @@ public void testCalcInstructionsRoundabout2() { InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); assertEquals(List.of("continue onto MainStreet 1 2", - "At roundabout, take exit 2 onto 5-8", + "at roundabout, take exit 2 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -578,7 +609,7 @@ public void testCalcInstructionsRoundaboutIssue353() { assertTrue(p.isFound()); InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, carManager, tr); List tmpList = getTurnDescriptions(wayList); - assertEquals(List.of("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); } @@ -593,7 +624,7 @@ public void testCalcInstructionsRoundaboutClockwise() { InstructionList wayList = InstructionsFromEdges.calcInstructions(p, p.graph, weighting, mixedEncodingManager, tr); List tmpList = getTurnDescriptions(wayList); assertEquals(List.of("continue onto MainStreet 1 2", - "At roundabout, take exit 1 onto 5-8", + "at roundabout, take exit 1 onto 5-8", "arrive at destination"), tmpList); // Test Radian @@ -1116,22 +1147,22 @@ public void testFootAndCar_issue3081() { 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)); + 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)); + 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)); + 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)); + assertEquals("at roundabout, take exit 1 onto Nordeast out", wayList.get(1).getTurnDescription(tr)); } static class AccessWeighting implements Weighting { @@ -1189,18 +1220,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); + 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; } diff --git a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java index 2f18ca8766c..b1e2c33b2be 100644 --- a/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java +++ b/core/src/test/java/com/graphhopper/routing/RoutingAlgorithmWithOSMTest.java @@ -319,7 +319,7 @@ public void testMonacoBike3D() { // 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)); // 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, 4220, 233)); // 3. queries.add(new Query(43.728677, 7.41016, 43.739213, 7.427806, 2776, 167)); // 4. @@ -409,7 +409,7 @@ 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, 2597, 118)); 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)); @@ -453,8 +453,8 @@ 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, 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, 95)); GraphHopper hopper = createHopper(KREMS, TestProfiles.accessSpeedAndPriority("mtb")); 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 cbc3b64a371..6ab46e63769 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 @@ -356,6 +356,8 @@ public void testService() { way.setTag("service", "parking_aisle"); assertPriorityAndSpeed(SLIGHT_AVOID, 4, way); + way.setTag("bicycle", "designated"); + assertPriorityAndSpeed(VERY_NICE, 12, way); } @Test 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 index 36291226736..05ea290aaaa 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/BikeCustomModelTest.java @@ -5,6 +5,7 @@ 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; @@ -26,6 +27,7 @@ public class BikeCustomModelTest { 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)). @@ -43,7 +45,7 @@ public void setup() { add(Roundabout.create()). add(Smoothness.create()). add(RoadAccess.create()). - add(BikeRoadAccess.create()). + add(bikeRA). add(FootRoadAccess.create()). add(bikeRating). add(hikeRating).build(); @@ -61,6 +63,9 @@ public void setup() { parsers.addWayTagParser(new BikePriorityParser(em)); parsers.addWayTagParser(new MountainBikePriorityParser(em)); parsers.addWayTagParser(new RacingBikePriorityParser(em)); + parsers.addWayTagParser(new OSMRoadAccessParser<>(bikeRA, + OSMRoadAccessParser.toOSMRestrictions(TransportationMode.BIKE), + (readerWay, accessValue) -> accessValue, BikeRoadAccess::find)); } EdgeIteratorState createEdge(ReaderWay way) { @@ -101,7 +106,13 @@ public void testCustomBike() { way.setTag("sac_scale", "mountain_hiking"); edge = createEdge(way); assertEquals(0.0, p.getEdgeToPriorityMapping().get(edge, false), 0.01); - assertEquals(4.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + assertEquals(8.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + + way.clearTags(); + way.setTag("highway", "tertiary"); + way.setTag("vehicle", "destination"); + edge = createEdge(way); + assertEquals(6, p.getEdgeToSpeedMapping().get(edge, false), 0.01); } @Test @@ -109,11 +120,11 @@ public void testCustomMtbBike() { CustomModel cm = GHUtility.loadCustomModelFromJar("mtb.json"); ReaderWay way = new ReaderWay(0L); way.setTag("highway", "path"); - way.setTag("surface", "ground"); + 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(16.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + assertEquals(10.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); way.setTag("mtb:scale", "3"); edge = createEdge(way); @@ -154,7 +165,7 @@ public void testCustomRacingBike() { EdgeIteratorState edge = createEdge(way); CustomWeighting.Parameters p = CustomModelParser.createWeightingParameters(cm, em); assertEquals(0.9, p.getEdgeToPriorityMapping().get(edge, false), 0.01); - assertEquals(8.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); + assertEquals(6.0, p.getEdgeToSpeedMapping().get(edge, false), 0.01); way.setTag("mtb:scale", "0"); edge = createEdge(way); 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 b3ab7047e98..88d475d15dc 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 @@ -83,21 +83,69 @@ public void testSpeedAndPriority() { // 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.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"); @@ -119,46 +167,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, 12, 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, 6, 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, 12, 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); @@ -166,32 +236,20 @@ public void testSpeedAndPriority() { way.setTag("surface", "paved"); assertPriorityAndSpeed(PREFER, 18, way); - way.clearTags(); - way.setTag("highway", "track"); - way.setTag("bicycle", "designated"); - way.setTag("segregated", "no"); - assertPriorityAndSpeed(PREFER, 12, way); - way.setTag("surface", "asphalt"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - way.setTag("tracktype", "grade1"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - way.removeTag("surface"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - 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", "path"); @@ -200,27 +258,39 @@ public void testSpeedAndPriority() { assertPriorityAndSpeed(VERY_NICE, 6, way); way.clearTags(); - way.setTag("highway", "platform"); - way.setTag("surface", "paved"); - assertPriorityAndSpeed(SLIGHT_AVOID, PUSHING_SECTION_SPEED, 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", "footway"); - way.setTag("surface", "paved"); - way.setTag("bicycle", "designated"); - assertPriorityAndSpeed(VERY_NICE, cyclewaySpeed, way); - way.clearTags(); - - way.setTag("highway", "footway"); - way.setTag("tracktype", "grade4"); - way.setTag("bicycle", "designated"); - assertPriorityAndSpeed(VERY_NICE, 6, way); + 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", "platform"); - way.setTag("surface", "paved"); + 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"); @@ -250,22 +320,6 @@ public void testSpeedAndPriority() { 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); - way.clearTags(); way.setTag("highway", "track"); assertPriorityAndSpeed(UNCHANGED, 12, way); @@ -273,11 +327,6 @@ public void testSpeedAndPriority() { 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"); @@ -287,54 +336,11 @@ public void testSpeedAndPriority() { way.setTag("surface", "unknown_surface"); assertPriorityAndSpeed(UNCHANGED, PUSHING_SECTION_SPEED, way); - way.clearTags(); - way.setTag("highway", "primary"); - way.setTag("surface", "fine_gravel"); - assertPriorityAndSpeed(BAD, 14, 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); - - way.clearTags(); - way.setTag("highway", "cycleway"); - way.setTag("vehicle", "no"); - assertPriorityAndSpeed(VERY_NICE, PUSHING_SECTION_SPEED, way); - way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(VERY_NICE, 18, way); - - way.clearTags(); - way.setTag("highway", "bridleway"); - assertPriorityAndSpeed(AVOID, PUSHING_SECTION_SPEED, way); - way.setTag("surface", "gravel"); - assertPriorityAndSpeed(AVOID, 12, way); - way.setTag("bicycle", "designated"); - assertPriorityAndSpeed(PREFER, 12, way); } @Test @@ -356,7 +362,7 @@ 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); @@ -533,6 +539,15 @@ public void testHandleWayTagsInfluencedByRelation() { osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "lcn"); assertPriorityAndSpeed(VERY_NICE, 18, osmWay, osmRel); + + osmRel.clearTags(); + osmWay.clearTags(); + osmWay.setTag("highway", "track"); + assertPriorityAndSpeed(UNCHANGED, 12, osmWay, osmRel); + + osmRel.setTag("route", "bicycle"); + osmRel.setTag("network", "lcn"); + assertPriorityAndSpeed(VERY_NICE, 18, osmWay, osmRel); } @Test @@ -730,7 +745,7 @@ public void temporalAccessWithPermit() { public void testPedestrian() { ReaderWay way = new ReaderWay(1); way.setTag("highway", "pedestrian"); - assertPriorityAndSpeed(SLIGHT_AVOID, 4, way); + assertPriorityAndSpeed(SLIGHT_AVOID, 6, way); way.setTag("bicycle", "yes"); assertPriorityAndSpeed(PREFER, 12, way); way.setTag("surface", "asphalt"); 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 881adbf9933..afdaed74447 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 @@ -109,6 +109,12 @@ public void testAccess() { way.setTag("motor_vehicle", "no"); assertTrue(parser.getAccess(way).canSkip()); + way.clearTags(); + way.setTag("highway", "service"); + way.setTag("access", "no"); + way.setTag("motor_vehicle", "unknown"); + assertTrue(parser.getAccess(way).canSkip()); + way.clearTags(); way.setTag("highway", "track"); way.setTag("motor_vehicle", "agricultural"); @@ -128,6 +134,11 @@ public void testAccess() { way.setTag("motorcar", "yes"); assertTrue(parser.getAccess(way).isWay()); + way.clearTags(); + way.setTag("highway", "service"); + way.setTag("motor_vehicle", "service"); + assertTrue(parser.getAccess(way).canSkip()); + way.clearTags(); way.setTag("highway", "service"); way.setTag("access", "emergency"); @@ -730,7 +741,7 @@ public void temporalAccess() { @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"); @@ -768,4 +779,43 @@ void nonHighwaysFallbackSpeed_issue2845() { // 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/MountainBikeTagParserTest.java b/core/src/test/java/com/graphhopper/routing/util/parsers/MountainBikeTagParserTest.java index 9e134bf7d35..df11f2bf50e 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 @@ -28,7 +28,6 @@ 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 { @@ -68,17 +67,17 @@ 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); way.setTag("bicycle", "yes"); - assertPriorityAndSpeed(PREFER, 18, way); + assertPriorityAndSpeed(PREFER, 12, way); way.setTag("highway", "track"); way.setTag("bicycle", "yes"); @@ -93,7 +92,7 @@ public void testSpeedAndPriority() { way.clearTags(); way.setTag("highway", "path"); way.setTag("surface", "ground"); - assertPriorityAndSpeed(PREFER, 16, way); + assertPriorityAndSpeed(PREFER, 10, way); } @Test @@ -101,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); @@ -115,18 +114,18 @@ 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); way.setTag("smoothness", "bad"); - assertEquals(12, getSpeedFromFlags(way), 0.01); + assertEquals(10, getSpeedFromFlags(way), 0.01); way.clearTags(); way.setTag("highway", "track"); - way.setTag("tracktype", "grade5"); - assertEquals(6, getSpeedFromFlags(way), 0.01); + way.setTag("tracktype", "grade4"); + assertEquals(8, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "bad"); - assertEquals(4, getSpeedFromFlags(way), 0.01); + assertEquals(6, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "impassable"); assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); @@ -139,20 +138,20 @@ public void testHandleWayTagsInfluencedByBikeAndMtbRelation() { ReaderRelation osmRel = new ReaderRelation(1); // unchanged - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); // relation code is PREFER osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(BEST, 18, osmWay, osmRel); + assertPriorityAndSpeed(VERY_NICE, 18, osmWay, osmRel); // relation code is PREFER osmRel.setTag("network", "rcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(VERY_NICE, 18, osmWay, osmRel); // relation code is PREFER osmRel.setTag("network", "ncn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(BEST, 18, osmWay, osmRel); // PREFER relation, but tertiary road // => no pushing section but road wayTypeCode and faster @@ -161,23 +160,23 @@ public void testHandleWayTagsInfluencedByBikeAndMtbRelation() { osmRel.setTag("route", "bicycle"); osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(BEST, 18, osmWay, osmRel); + assertPriorityAndSpeed(VERY_NICE, 18, osmWay, osmRel); osmWay.clearTags(); osmRel.clearTags(); osmWay.setTag("highway", "track"); // unchanged - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); osmRel.setTag("route", "mtb"); osmRel.setTag("network", "lcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); osmRel.setTag("network", "rcn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); osmRel.setTag("network", "ncn"); - assertPriorityAndSpeed(PREFER, 18, osmWay, osmRel); + assertPriorityAndSpeed(PREFER, 12, osmWay, osmRel); osmWay.clearTags(); osmWay.setTag("highway", "tertiary"); 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 index 2b6786f4614..8ad30aed84e 100644 --- a/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRestrictionSetterTest.java +++ b/core/src/test/java/com/graphhopper/routing/util/parsers/OSMRestrictionSetterTest.java @@ -496,13 +496,6 @@ private IntArrayList calcPath(int from, int to, BooleanEncodedValue turnRestrict return calcPath(this.graph, from, to, turnRestrictionEnc); } - /** - * Shorthand version that calculates the path for the first turn restriction encoded value - */ - private IntArrayList calcPath(Graph graph, int from, int to) { - return calcPath(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 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 22829d75221..9357876ed12 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 @@ -53,7 +53,7 @@ protected EncodingManager createEncodingManager() { @Override protected BikeCommonAccessParser createAccessParser(EncodedValueLookup lookup, PMap pMap) { - return (BikeCommonAccessParser) new RacingBikeAccessParser(lookup, pMap); + return new RacingBikeAccessParser(lookup, pMap); } @Override @@ -77,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 @@ -98,13 +98,20 @@ public void testService() { public void testTrack() { ReaderWay way = new ReaderWay(1); 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, 20, way); + assertPriorityAndSpeed(VERY_NICE, 24, way); way.setTag("tracktype","grade1"); - assertPriorityAndSpeed(VERY_NICE, 20, way); + assertPriorityAndSpeed(VERY_NICE, 24, way); } @Test @@ -116,24 +123,24 @@ public void testGetSpeed() { 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"); @@ -173,7 +180,7 @@ public void testSmoothness() { assertEquals(4, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "bad"); - assertEquals(2, getSpeedFromFlags(way), 0.01); + assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); way.setTag("smoothness", "impassable"); assertEquals(MIN_SPEED, getSpeedFromFlags(way), 0.01); @@ -183,7 +190,7 @@ 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); @@ -200,23 +207,18 @@ public void testHandleWayTagsInfluencedByRelation() { // Now we assume bicycle=yes, and paved osmWay.setTag("tracktype", "grade1"); - assertPriorityAndSpeed(VERY_NICE, 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 assume bicycle=yes, and unpaved and not part of a cycle relation osmWay.clearTags(); osmWay.setTag("highway", "track"); osmWay.setTag("tracktype", "grade3"); - assertPriorityAndSpeed(AVOID_MORE, PUSHING_SECTION_SPEED, osmWay); - - // Now we assume bicycle=yes, and tracktype = null - osmWay.clearTags(); - osmWay.setTag("highway", "track"); - assertPriorityAndSpeed(AVOID_MORE, 2, osmWay); + assertPriorityAndSpeed(AVOID_MORE, 4, osmWay); } @Test @@ -239,19 +241,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); 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 0a309b6b57e..62aeead0db4 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 @@ -82,49 +82,6 @@ public void handleWayTags(int edgeId, EdgeIntAccess intAccess, ReaderWay way, In 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()) - .add(RoadClass.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 = ArrayEdgeIntAccess.createFromBytes(em.getBytesForFlags()); - 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() { BooleanEncodedValue carAccessEnc = VehicleAccess.create("car"); diff --git a/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java b/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java deleted file mode 100644 index 687f5297379..00000000000 --- a/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.graphhopper.routing.weighting; - -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.parsers.OrientationCalculator; -import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.*; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static com.graphhopper.json.Statement.If; -import static com.graphhopper.json.Statement.Op.LIMIT; -import static org.junit.jupiter.api.Assertions.assertEquals; - -public class DefaultTurnCostProviderTest { - - @Test - public void testRawTurnWeight() { - 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)); - EdgeIteratorState edge24 = handleWayTags(edgeIntAccess, calc, graph.edge(2, 4)); - 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)); - - TurnCostsConfig tcConfig = new TurnCostsConfig(); - DefaultTurnCostProvider tcp = new DefaultTurnCostProvider(null, orientationEnc, graph, tcConfig); - assertEquals(-12, tcp.calcChangeAngle(edge12.getEdge(), 2, edge24.getEdge()), 1); - assertEquals(-12, tcp.calcChangeAngle(edge23down.getEdge(), 2, edge12.getEdge()), 1); - - // left - assertEquals(-84, tcp.calcChangeAngle(edge24.getEdge(), 2, edge23.getEdge()), 1); - assertEquals(-84, tcp.calcChangeAngle(edge23.getEdge(), 2, edge12.getEdge()), 1); - - // right - assertEquals(96, tcp.calcChangeAngle(edge23down.getEdge(), 3, edge23.getEdge()), 1); - assertEquals(84, tcp.calcChangeAngle(edge12.getEdge(), 2, edge23.getEdge()), 1); - } - - @Test - public void testCalcTurnWeight() { - BooleanEncodedValue tcAccessEnc = VehicleAccess.create("car"); - DecimalEncodedValue tcAvgSpeedEnc = VehicleSpeed.create("car", 5, 5, true); - DecimalEncodedValue orientEnc = Orientation.create(); - EncodingManager em = new EncodingManager.Builder().add(tcAccessEnc).add(tcAvgSpeedEnc). - add(orientEnc).addTurnCostEncodedValue(TurnRestriction.create("car")).build(); - BaseGraph turnGraph = new BaseGraph.Builder(em).withTurnCosts(true).create(); - - // 4 5 - // 0 - 1 - 2 - // 3 6 - - turnGraph.getNodeAccess().setNode(0, 51.0362, 13.714); - turnGraph.getNodeAccess().setNode(1, 51.0362, 13.720); - turnGraph.getNodeAccess().setNode(2, 51.0362, 13.726); - turnGraph.getNodeAccess().setNode(3, 51.0358, 13.7205); - turnGraph.getNodeAccess().setNode(4, 51.0366, 13.720); - turnGraph.getNodeAccess().setNode(5, 51.0366, 13.726); - turnGraph.getNodeAccess().setNode(6, 51.0358, 13.726); - - Profile profile = new Profile("car"); - TurnCostsConfig config = new TurnCostsConfig(). - setRightTurnCosts(0.5).setSharpRightTurnCosts(1). - setLeftTurnCosts(6).setSharpLeftTurnCosts(12); - profile.setCustomModel(new CustomModel().addToSpeed(If("true", LIMIT, tcAvgSpeedEnc.getName()))); - profile.setTurnCostsConfig(config); - Weighting weighting = new DefaultWeightingFactory(turnGraph, em).createWeighting(profile, new PMap(), false); - OrientationCalculator calc = new OrientationCalculator(orientEnc); - EdgeIntAccess edgeIntAccess = turnGraph.getEdgeAccess(); - EdgeIteratorState edge01 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(0, 1).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - EdgeIteratorState edge13 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(1, 3).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - EdgeIteratorState edge14 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(1, 4).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - EdgeIteratorState edge26 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(2, 6).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - EdgeIteratorState edge25 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(2, 5).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - EdgeIteratorState edge12 = handleWayTags(edgeIntAccess, calc, turnGraph.edge(1, 2).setDistance(500).set(tcAvgSpeedEnc, 15).set(tcAccessEnc, true, true)); - - // from top to left => sharp right turn - assertEquals(1, weighting.calcTurnWeight(edge14.getEdge(), 1, edge01.getEdge()), 0.01); - // left to right => straight - assertEquals(0.0, weighting.calcTurnWeight(edge01.getEdge(), 1, edge12.getEdge()), 0.01); - // top to right => sharp left turn - assertEquals(12, weighting.calcTurnWeight(edge14.getEdge(), 1, edge12.getEdge()), 0.01); - // left to down => right turn - assertEquals(0.5, weighting.calcTurnWeight(edge01.getEdge(), 1, edge13.getEdge()), 0.01); - // bottom to left => left turn - assertEquals(6, weighting.calcTurnWeight(edge13.getEdge(), 1, edge01.getEdge()), 0.01); - - // left to top => sharp left turn => here like 'straight' - assertEquals(12, weighting.calcTurnWeight(edge12.getEdge(), 2, edge25.getEdge()), 0.01); - // down to left => sharp left turn => here again like 'straight' - assertEquals(12, weighting.calcTurnWeight(edge26.getEdge(), 2, edge12.getEdge()), 0.01); - // top to left => sharp right turn - assertEquals(1, weighting.calcTurnWeight(edge25.getEdge(), 2, edge12.getEdge()), 0.01); - } - - EdgeIteratorState handleWayTags(EdgeIntAccess edgeIntAccess, OrientationCalculator calc, EdgeIteratorState edge) { - return handleWayTags(edgeIntAccess, calc, edge, List.of()); - } - - 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/CustomModelParserTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomModelParserTest.java index 60094ac10d5..d258710fc7b 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 @@ -36,8 +36,7 @@ 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.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; @@ -338,16 +337,28 @@ void testBackwardFunction() { } @Test - public void findVariablesForEncodedValueString() { + void testTurnPenalty() { CustomModel customModel = new CustomModel(); - customModel.addToPriority(If("backward_car_access != car_access", MULTIPLY, "0.5")); - List variables = findVariablesForEncodedValuesString(customModel, s -> new DefaultImportRegistry().createImportUnit(s) != null, s -> ""); - assertEquals(List.of("car_access"), variables); + 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(); - customModel = new CustomModel(); + BaseGraph graph = new BaseGraph.Builder(encodingManager).create(); + 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(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 - variables = findVariablesForEncodedValuesString(customModel, s -> new DefaultImportRegistry().createImportUnit(s) != null, s -> ""); + 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 e4addcf4f69..ebe97b14f43 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,18 +1,22 @@ package com.graphhopper.routing.weighting.custom; -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.parsers.OrientationCalculator; +import com.graphhopper.routing.weighting.Weighting; import com.graphhopper.storage.BaseGraph; -import com.graphhopper.util.CustomModel; -import com.graphhopper.util.EdgeIteratorState; -import com.graphhopper.util.Helper; +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.LIMIT; -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 { @@ -66,4 +70,124 @@ public void testNegativeMax() { 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(1, weighting.calcTurnWeight(edge14.getEdge(), 1, edge01.getEdge()), 0.01); + // left to right => straight + assertEquals(0.0, weighting.calcTurnWeight(edge01.getEdge(), 1, edge12.getEdge()), 0.01); + // top to right => sharp left turn + assertEquals(12, weighting.calcTurnWeight(edge14.getEdge(), 1, edge12.getEdge()), 0.01); + // left to down => right turn + assertEquals(0.5, weighting.calcTurnWeight(edge01.getEdge(), 1, edge13.getEdge()), 0.01); + // bottom to left => left turn + assertEquals(6, weighting.calcTurnWeight(edge13.getEdge(), 1, edge01.getEdge()), 0.01); + + // left to top => sharp left turn => here like 'straight' + assertEquals(12, weighting.calcTurnWeight(edge12.getEdge(), 2, edge25.getEdge()), 0.01); + // down to left => sharp left turn => here again like 'straight' + assertEquals(12, weighting.calcTurnWeight(edge26.getEdge(), 2, edge12.getEdge()), 0.01); + // top to left => sharp right turn + assertEquals(1, weighting.calcTurnWeight(edge25.getEdge(), 2, edge12.getEdge()), 0.01); + } + + 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 08fa5a98c8c..b6f8e6e1e8d 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 @@ -405,7 +405,7 @@ public void calcWeightAndTime_withTurnCosts() { BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); CustomModel customModel = createSpeedCustomModel(avSpeedEnc); Weighting weighting = CustomModelParser.createWeighting(encodingManager, - new DefaultTurnCostProvider(turnRestrictionEnc, null, graph, new TurnCostsConfig()), customModel); + 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); @@ -419,7 +419,7 @@ public void calcWeightAndTime_uTurnCosts() { BaseGraph graph = new BaseGraph.Builder(encodingManager).withTurnCosts(true).create(); CustomModel customModel = createSpeedCustomModel(avSpeedEnc); Weighting weighting = CustomModelParser.createWeighting(encodingManager, - new DefaultTurnCostProvider(turnRestrictionEnc, null, graph, new TurnCostsConfig().setUTurnCosts(40)), customModel); + new DefaultTurnCostProvider(turnRestrictionEnc, graph, new TurnCostsConfig().setUTurnCosts(40), null), customModel); EdgeIteratorState edge = graph.edge(0, 1).set(avSpeedEnc, 60, 60).setDistance(100); assertEquals(6 + 40, GHUtility.calcWeightWithTurnWeight(weighting, edge, false, 0), 1.e-6); assertEquals(6 * 1000, GHUtility.calcMillisWithTurnMillis(weighting, edge, false, 0), 1.e-6); diff --git a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java index 26aa8a68a23..a0cf61df880 100644 --- a/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java +++ b/core/src/test/java/com/graphhopper/storage/ShortcutUnpackerTest.java @@ -262,7 +262,7 @@ public void loopShortcut(Fixture f) { @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 diff --git a/docs/core/custom-models.md b/docs/core/custom-models.md index 395f9c70f12..dcc2a527028 100644 --- a/docs/core/custom-models.md +++ b/docs/core/custom-models.md @@ -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 @@ -98,6 +104,7 @@ 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 - curvature: "beeline distance" / edge_distance (0..1) e.g. a curvy road is smaller than 1 +- 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 @@ -109,6 +116,14 @@ There are also some that take on a numeric value, like: - 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 @@ -614,6 +629,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/example/pom.xml b/example/pom.xml index 3025715520b..f231b5e5594 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-example - 11.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Example com.graphhopper graphhopper-parent - 11.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 e6b7a0716aa..d2ef9ac102b 100644 --- a/example/src/main/java/com/graphhopper/example/HeadingExample.java +++ b/example/src/main/java/com/graphhopper/example/HeadingExample.java @@ -38,8 +38,7 @@ static GraphHopper createGraphHopperInstance(String ghLoc) { hopper.setProfiles(new Profile("car"). setCustomModel(new CustomModel(). addToSpeed(If("true", LIMIT, "car_average_speed")). - addToPriority(If("!car_access", MULTIPLY, "0")). - addToPriority(If("road_access == DESTINATION", MULTIPLY, "0.1")))); + addToPriority(If("!car_access", MULTIPLY, "0")))); hopper.getCHPreparationHandler().setCHProfiles(new CHProfile("car")); hopper.importOrLoad(); return hopper; diff --git a/map-matching/pom.xml b/map-matching/pom.xml index dda30c6ca9c..7aa9cfe10b4 100644 --- a/map-matching/pom.xml +++ b/map-matching/pom.xml @@ -3,14 +3,14 @@ 4.0.0 com.graphhopper graphhopper-map-matching - 11.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Map Matching com.graphhopper graphhopper-parent - 11.0-SNAPSHOT + 12.0-SNAPSHOT diff --git a/navigation/pom.xml b/navigation/pom.xml index ce75fc2d862..bb7529b29ad 100644 --- a/navigation/pom.xml +++ b/navigation/pom.xml @@ -5,14 +5,14 @@ 4.0.0 graphhopper-nav - 11.0-SNAPSHOT + 12.0-SNAPSHOT jar GraphHopper Navigation com.graphhopper graphhopper-parent - 11.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 eafebd53132..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; @@ -29,23 +32,66 @@ public class DistanceConfig { final List voiceInstructions; final DistanceUtils.Unit unit; + final String mode; - public DistanceConfig(DistanceUtils.Unit unit, TranslationMap translationMap, Locale locale) { + 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; - 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}) - ); + 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}) + ); + } } } @@ -61,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; + } } diff --git a/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java b/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java index c2d213d2ad8..fed81ca3270 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java +++ b/navigation/src/main/java/com/graphhopper/navigation/NavigateResource.java @@ -30,15 +30,15 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.inject.Inject; -import javax.servlet.http.HttpServletRequest; -import javax.validation.constraints.NotNull; -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.*; @@ -144,7 +144,7 @@ public Response doGet( } 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); + 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)). @@ -214,7 +214,7 @@ public Response doPost(@NotNull GHRequest request, @Context HttpServletRequest h unit = DistanceUtils.Unit.IMPERIAL; } - DistanceConfig config = new DistanceConfig(unit, translationMap, request.getLocale()); + 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)). diff --git a/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java b/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java index a28e010c144..a50513dcf2b 100644 --- a/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java +++ b/navigation/src/main/java/com/graphhopper/navigation/NavigateResponseConverter.java @@ -37,6 +37,13 @@ import static com.graphhopper.util.Parameters.Details.INTERSECTION; +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; @@ -91,12 +98,11 @@ 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()); - fixFirstIntersectionDetail(intersectionDetails); ObjectNode annotation = null; ArrayNode maxSpeedArray = null; @@ -106,26 +112,37 @@ private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, } 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(); + // pointIndexTo is the same as ShallowCopy of the path Points toPoint member + int pointIndexTo = pointIndexFrom + instruction.getPoints().size(); + + ManeuverType maneuverType; + if (isDepartInstruction) { + maneuverType = ManeuverType.DEPART; + fixDepartIntersectionDetail(intersectionDetails, i); + } else { + switch (instruction.getSign()) { + case Instruction.REACHED_VIA, Instruction.FINISH: + maneuverType = ManeuverType.ARRIVE; + break; + case Instruction.USE_ROUNDABOUT : + maneuverType = ManeuverType.ROUNDABOUT; + break; + default : + maneuverType = ManeuverType.TURN; + } } if (annotation != null) putAnnotation(maxSpeedArray, pathDetails, pointIndexFrom, pointIndexTo, distanceConfig.unit); - putInstruction(path.getPoints(), instructions, i, locale, translationMap, instructionJson, - isFirstInstructionOfLeg, distanceConfig, intersectionDetails, 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(); @@ -134,6 +151,9 @@ private static void putRouteInformation(ObjectNode pathJson, ResponsePath path, annotation = legJson.putObject("annotation"); maxSpeedArray = annotation.putArray("maxspeed"); } + isDepartInstruction = true; + time = 0; + distance = 0; } } } @@ -199,34 +219,34 @@ private static void putLegInformation(ObjectNode legJson, ResponsePath path, int } /** - * fix the first IntersectionDetail. + * fix the first IntersectionDetail which is an Depart *

- * The first Intersection of the first step should only have one "bearings" and one + * Departs should only have one "bearings" and one * "out" entry */ - private static void fixFirstIntersectionDetail(List intersectionDetails) { + private static void fixDepartIntersectionDetail(List intersectionDetails, int position) { - if (intersectionDetails.size() < 2) { + if (intersectionDetails.size() < position + 2) { // Can happen if start and stop are at the same spot and other edge cases return; } - final Map firstItersectionMap = (Map) intersectionDetails.get(0).getValue(); + final Map departIntersectionMap = (Map) intersectionDetails.get(position).getValue(); - int out = (int) firstItersectionMap.get("out"); - firstItersectionMap.put("out", 0); + int out = (int) departIntersectionMap.get("out"); + departIntersectionMap.put("out", 0); // bearings - List oldBearings = (List) firstItersectionMap.get("bearings"); + List oldBearings = (List) departIntersectionMap.get("bearings"); List newBearings = new ArrayList<>(); newBearings.add(oldBearings.get(out)); - firstItersectionMap.put("bearings", newBearings); + departIntersectionMap.put("bearings", newBearings); // entries - final List oldEntries = (List) firstItersectionMap.get("entries"); + final List oldEntries = (List) departIntersectionMap.get("entries"); List newEntries = new ArrayList<>(); newEntries.add(oldEntries.get(out)); - firstItersectionMap.put("entries", newEntries); + departIntersectionMap.put("entries", newEntries); } /** @@ -325,18 +345,19 @@ private static List filterIntersectionDetails(PointList points, List private static void putInstruction(PointList points, InstructionList instructions, int instructionIndex, Locale locale, - TranslationMap translationMap, ObjectNode instructionJson, boolean isFirstInstructionOfLeg, + TranslationMap translationMap, ObjectNode stepJson, ManeuverType maneuverType, DistanceConfig distanceConfig, List intersectionDetails, int pointIndexFrom, int pointIndexTo) { Instruction instruction = instructions.get(instructionIndex); - ArrayNode intersections = instructionJson.putArray("intersections"); + ArrayNode intersections = stepJson.putArray("intersections"); // make pointList writeable PointList pointList = instruction.getPoints().clone(false); - if (instructionIndex < instructions.size() - 1) { + 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 + // 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 { @@ -351,8 +372,8 @@ private static void putInstruction(PointList points, InstructionList instruction entryArray.add(true); // copy the bearing from the previous instruction - ArrayNode bearingsrray = intersection.putArray("bearings"); - bearingsrray.add(0); + ArrayNode bearingsArray = intersection.putArray("bearings"); + bearingsArray.add(0); // add the in tag intersection.put("in", 0); @@ -393,24 +414,24 @@ private static void putInstruction(PointList points, InstructionList instruction } } - instructionJson.put("driving_side", "right"); + 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)); - instructionJson.put("mode", instruction.getSign() == Instruction.FERRY ? "ferry" : "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()) { @@ -555,7 +576,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); @@ -574,7 +595,7 @@ private static void putSingleBannerInstruction(Instruction instruction, Locale l } private static void putManeuver(Instruction instruction, ObjectNode instructionJson, Locale locale, - TranslationMap translationMap, boolean isFirstInstructionOfLeg) { + TranslationMap translationMap, ManeuverType maneuverType) { ObjectNode maneuver = instructionJson.putObject("maneuver"); maneuver.put("bearing_after", 0); maneuver.put("bearing_before", 0); @@ -582,42 +603,45 @@ 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"; } } 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 2a64e09ef76..b5d23fb4917 100644 --- a/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java +++ b/navigation/src/test/java/com/graphhopper/navigation/NavigateResponseConverterTest.java @@ -7,6 +7,7 @@ import com.graphhopper.GraphHopper; 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; @@ -29,22 +30,18 @@ public class NavigateResponseConverterTest { private static final String osmFile = "../core/files/andorra.osm.gz"; private static GraphHopper hopper; 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). - setEncodedValuesString("car_access, car_average_speed"). - setProfiles(TestProfiles.accessAndSpeed(profile, "car")). - importOrLoad(); + hopper = new GraphHopper().setOSMFile(osmFile).setStoreOnFlush(true).setGraphHopperLocation(graphFolder) + .setEncodedValuesString("car_access, car_average_speed") + .setProfiles(TestProfiles.accessAndSpeed(profile, "car")).importOrLoad(); } @AfterAll @@ -55,8 +52,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). - 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); @@ -88,6 +85,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); @@ -145,6 +145,16 @@ public void arriveGeometryTest() { 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() { @@ -161,7 +171,7 @@ 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", + 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 @@ -183,7 +193,7 @@ public void voiceInstructionsImperialTest() { 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"); @@ -195,7 +205,7 @@ public void voiceInstructionsImperialTest() { 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", + 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 @@ -212,6 +222,146 @@ 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 maneuver = step.get("maneuver"); + + 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); + maneuver = step.get("maneuver"); + + 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 maneuver = step.get("maneuver"); + + 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); + maneuver = step.get("maneuver"); + + 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 maneuver = step.get("maneuver"); + + 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); + maneuver = step.get("maneuver"); + + 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 maneuver = step.get("maneuver"); + + 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); + maneuver = step.get("maneuver"); + + 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() { @@ -244,7 +394,8 @@ public void voiceInstructionTranslationTest() { 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); @@ -408,7 +559,7 @@ public void testMultipleWaypoints() { 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); @@ -451,6 +602,10 @@ 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(intersections, null); } // Check if the duration and distance of the legs sum up to the overall route @@ -469,4 +624,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 103487598ee..5d7180816e7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.graphhopper graphhopper-parent GraphHopper Parent Project - 11.0-SNAPSHOT + 12.0-SNAPSHOT pom https://www.graphhopper.com 2012 @@ -75,14 +75,14 @@ io.dropwizard dropwizard-dependencies - 3.0.8 + 4.0.16 pom import com.graphhopper.external jackson-datatype-jts - 2.14 + 2.19.2 com.fasterxml.jackson.core @@ -98,7 +98,7 @@ org.locationtech.jts jts-core - 1.19.0 + 1.20.0 org.apache.commons @@ -139,10 +139,11 @@ + - javax.inject - javax.inject - 1 + jakarta.inject + jakarta.inject-api + 2.0.1 org.hamcrest @@ -308,16 +309,13 @@ - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.13 - true - - ossrh - https://oss.sonatype.org/ - true - 10 - + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + central + org.apache.maven.plugins diff --git a/reader-gtfs/pom.xml b/reader-gtfs/pom.xml index ed9dbc9d36c..b17e9a42f1a 100644 --- a/reader-gtfs/pom.xml +++ b/reader-gtfs/pom.xml @@ -10,7 +10,7 @@ com.graphhopper graphhopper-parent - 11.0-SNAPSHOT + 12.0-SNAPSHOT @@ -43,10 +43,6 @@ 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/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 5eebdcec128..4d7b6782e75 100644 --- a/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java +++ b/reader-gtfs/src/main/java/com/graphhopper/gtfs/GraphExplorer.java @@ -160,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 06864413759..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 { 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