diff --git a/NOTICE.md b/NOTICE.md index 6f4cbf8a318..26c91632c9b 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -2,28 +2,29 @@ GraphHopper licensed under the Apache license, Version 2.0 -Copyright 2012 - 2022 GraphHopper GmbH +Copyright 2012 - 2024 GraphHopper GmbH The core module includes the following additional software: * slf4j.org - SLF4J distributed under the MIT license. * com.carrotsearch:hppc (Apache license) - * SparseArray from the Android project (Apache license) * Snippets regarding mmap, vint/vlong and compression from Lucene (Apache license) * XMLGraphics-Commons for CGIAR elevation files (Apache License) - * Apache Commons Lang - we copied the implementation of the Levenshtein Distance (Apache License) - * Apache Commons Collections - we copied parts of the BinaryHeap (Apache License) + * Apache Commons Lang - we copied the implementation of the Levenshtein Distance - see com.graphhopper.apache.commons.lang3 (Apache License) + * Apache Commons Collections - we copied parts of the BinaryHeap - see com.graphhopper.apache.commons.collections (Apache License) * java-string-similarity - we copied the implementation of JaroWinkler (MIT license) * Jackson (Apache License) - * org.locationtech:jts (EDL), see #1039 + * org.locationtech:jts (EDL) * AngleCalc.atan2 from Jim Shima, 1999 (public domain) * janino compiler (BSD-3-Clause license) - * protobuf - New BSD license - * OSM-binary - LGPL license + * osm-legal-default-speeds-jvm (BSD-3-Clause license) + * kotlin stdlib (Apache License) + * protobuf - (New BSD license) + * OSM-binary - (LGPL license) * Osmosis - public domain, see their github under package/copying.txt reader-gtfs: - + * some files from com.conveyal:gtfs-lib (BSD 2-clause license) * com.google.transit:gtfs-realtime-bindings (Apache license) * com.google.guava:guava (Apache license) @@ -43,8 +44,7 @@ web: * org.eclipse.jetty:jetty-server (Apache License) * Dropwizard and dependencies (Apache license) - * no.ecc:java-vector-tile (Apache license) - * some images from mapbox https://www.mapbox.com/maki/, BSD License, see core/files + * classes in no.ecc are a copy of no.ecc.vectortile:java-vector-tile, see #2698 (Apache license) ## Data diff --git a/client-hc/pom.xml b/client-hc/pom.xml index 052a7589d4e..a1a861f63e9 100644 --- a/client-hc/pom.xml +++ b/client-hc/pom.xml @@ -38,6 +38,7 @@ graphhopper-web-api ${project.parent.version} + com.squareup.okhttp3 okhttp 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 14da63ccec8..99cc6577aa0 100644 --- a/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/GraphHopperWebTest.java @@ -112,8 +112,8 @@ public void customModel() throws JsonProcessingException { "\"type\":\"FeatureCollection\",\"features\":[" + "{\"id\":\"area_1\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.019324184801185,11.28021240234375],[48.019324184801185,11.53564453125],[48.11843396091691,11.53564453125],[48.11843396091691,11.28021240234375],[48.019324184801185,11.28021240234375]]]},\"properties\":{}}," + "{\"id\":\"area_2\",\"type\":\"Feature\",\"geometry\":{\"type\":\"Polygon\",\"coordinates\":[[[48.15509285476017,11.53289794921875],[48.15509285476017,11.8212890625],[48.281365151571755,11.8212890625],[48.281365151571755,11.53289794921875],[48.15509285476017,11.53289794921875]]]},\"properties\":{}}]}," + - "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}]," + - "\"priority\":[{\"if\":\"surface == DIRT\",\"multiply_by\":\"0.7\"},{\"if\":\"surface == SAND\",\"multiply_by\":\"0.6\"}]}"); + "\"priority\":[{\"if\":\"surface == DIRT\",\"multiply_by\":\"0.7\"},{\"if\":\"surface == SAND\",\"multiply_by\":\"0.6\"}]," + + "\"speed\":[{\"if\":\"road_class == MOTORWAY\",\"limit_to\":\"80\"}]}"); assertEquals(expected, objectMapper.valueToTree(customModelJson)); CustomModel cm = objectMapper.readValue("{\"distance_influence\":null}", CustomModel.class); diff --git a/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java b/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java index ae783ce2a37..80eed059c76 100644 --- a/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java +++ b/client-hc/src/test/java/com/graphhopper/api/model/GHGeocodingResponseRepresentationTest.java @@ -18,22 +18,22 @@ package com.graphhopper.api.model; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.graphhopper.jackson.Jackson; import org.junit.jupiter.api.Test; import org.locationtech.jts.geom.Envelope; -import static io.dropwizard.testing.FixtureHelpers.fixture; +import java.io.IOException; + import static org.junit.jupiter.api.Assertions.assertEquals; public class GHGeocodingResponseRepresentationTest { @Test - public void testGeocodingRepresentation() throws JsonProcessingException { + public void testGeocodingRepresentation() throws IOException { ObjectMapper objectMapper = Jackson.newObjectMapper(); - GHGeocodingResponse geocodingResponse = objectMapper.readValue(fixture("fixtures/geocoding-response.json"), GHGeocodingResponse.class); + GHGeocodingResponse geocodingResponse = objectMapper.readValue(getClass().getResource("/fixtures/geocoding-response.json"), GHGeocodingResponse.class); Envelope extent = geocodingResponse.getHits().get(0).getExtent(); // Despite the unusual representation of the bounding box... assertEquals(10.0598605, extent.getMinX(), 0.0); diff --git a/config-example.yml b/config-example.yml index 5b80b313aef..b3bd2fd7965 100644 --- a/config-example.yml +++ b/config-example.yml @@ -33,6 +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 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. @@ -67,7 +68,7 @@ graphhopper: # It's possible to speed up routing by doing a special graph preparation (Contraction Hierarchies, CH). This requires # more RAM/disk space for holding the prepared graph but also means less memory usage per request. Using the following # list you can define for which of the above routing profiles such preparation shall be performed. Note that to support - # profiles with `turn_costs: true` a more elaborate preparation is required (longer preparation time and more memory + # 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`. profiles_ch: - profile: car diff --git a/core/src/main/java/com/graphhopper/GraphHopper.java b/core/src/main/java/com/graphhopper/GraphHopper.java index e3ee4eb9999..a1650985768 100644 --- a/core/src/main/java/com/graphhopper/GraphHopper.java +++ b/core/src/main/java/com/graphhopper/GraphHopper.java @@ -624,16 +624,19 @@ protected List getEVSortIndex(Map profilesByName) { protected OSMParsers buildOSMParsers(Map encodedValuesWithProps, Map activeImportUnits, Map> restrictionVehicleTypesByProfile, - List ignoredHighways, String dateRangeParserString) { + List ignoredHighways) { ImportUnitSorter sorter = new ImportUnitSorter(activeImportUnits); Map sortedImportUnits = new LinkedHashMap<>(); sorter.sort().forEach(name -> sortedImportUnits.put(name, activeImportUnits.get(name))); - DateRangeParser dateRangeParser = DateRangeParser.createInstance(dateRangeParserString); List sortedParsers = new ArrayList<>(); sortedImportUnits.forEach((name, importUnit) -> { BiFunction createTagParser = importUnit.getCreateTagParser(); - if (createTagParser != null) - sortedParsers.add(createTagParser.apply(encodingManager, encodedValuesWithProps.getOrDefault(name, new PMap().putObject("date_range_parser", dateRangeParser)))); + if (createTagParser != null) { + PMap pmap = encodedValuesWithProps.getOrDefault(name, new PMap()); + if (!pmap.has("date_range_parser_day")) + pmap.putObject("date_range_parser_day", dateRangeParserString); + sortedParsers.add(createTagParser.apply(encodingManager, pmap)); + } }); OSMParsers osmParsers = new OSMParsers(); @@ -873,7 +876,7 @@ protected void prepareImport() { deque.addAll(importUnit.getRequiredImportUnits()); } encodingManager = buildEncodingManager(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile); - osmParsers = buildOSMParsers(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile, osmReaderConfig.getIgnoredHighways(), dateRangeParserString); + osmParsers = buildOSMParsers(encodedValuesWithProps, activeImportUnits, restrictionVehicleTypesByProfile, osmReaderConfig.getIgnoredHighways()); } protected void postImportOSM() { diff --git a/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java b/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java index 396da566589..8fd8d585d5b 100644 --- a/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java +++ b/core/src/main/java/com/graphhopper/apache/commons/lang3/StringUtils.java @@ -18,7 +18,7 @@ /** - * This class is a partial Copy of the org.apache.commons.lang3.StringUtils + * This class is a partial copy of the org.apache.commons.lang3.StringUtils * that can be found here: https://github.com/apache/commons-lang/blob/master/src/main/java/org/apache/commons/lang3/StringUtils.java *

* The library can be found here: https://commons.apache.org/proper/commons-lang/ diff --git a/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java b/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java index 4afd9f73c0a..18d49d9a790 100644 --- a/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java +++ b/core/src/main/java/com/graphhopper/reader/osm/WayToEdgeConverter.java @@ -56,9 +56,9 @@ public NodeResult convertForViaNode(LongArrayList fromWays, int viaNode, LongArr result.fromEdges.add(e.value); }); if (result.fromEdges.size() < fromWays.size()) - throw new OSMRestrictionException("has from member ways that aren't adjacent to the via member node"); + throw new OSMRestrictionException("has from-member ways that aren't adjacent to the via-member node"); else if (result.fromEdges.size() > fromWays.size()) - throw new OSMRestrictionException("has from member ways that aren't split at the via member node"); + throw new OSMRestrictionException("has from-member ways that aren't split at the via-member node"); for (LongCursor toWay : toWays) edgesByWay.apply(toWay.value).forEachRemaining(e -> { @@ -66,9 +66,9 @@ else if (result.fromEdges.size() > fromWays.size()) result.toEdges.add(e.value); }); if (result.toEdges.size() < toWays.size()) - throw new OSMRestrictionException("has to member ways that aren't adjacent to the via member node"); + throw new OSMRestrictionException("has to-member ways that aren't adjacent to the via-member node"); else if (result.toEdges.size() > toWays.size()) - throw new OSMRestrictionException("has to member ways that aren't split at the via member node"); + throw new OSMRestrictionException("has to-member ways that aren't split at the via-member node"); return result; } diff --git a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java index a1b6b5c5ac4..531b84b6d7d 100644 --- a/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java +++ b/core/src/main/java/com/graphhopper/routing/DefaultWeightingFactory.java @@ -20,6 +20,8 @@ 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; @@ -31,6 +33,7 @@ import com.graphhopper.util.CustomModel; import com.graphhopper.util.PMap; import com.graphhopper.util.Parameters; +import com.graphhopper.util.TurnCostsConfig; import static com.graphhopper.routing.weighting.TurnCostProvider.NO_TURN_COST_PROVIDER; import static com.graphhopper.util.Helper.toLowerCase; @@ -59,8 +62,12 @@ public Weighting createWeighting(Profile profile, PMap requestHints, boolean dis 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_costs,left_sharp_costs,right_costs,right_sharp_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()); - turnCostProvider = new DefaultTurnCostProvider(turnRestrictionEnc, graph.getTurnCostStorage(), uTurnCosts); + TurnCostsConfig tcConfig = new TurnCostsConfig(profile.getTurnCostsConfig()).setUTurnCosts(uTurnCosts); + turnCostProvider = new DefaultTurnCostProvider(turnRestrictionEnc, oEnc, graph, tcConfig); } else { turnCostProvider = NO_TURN_COST_PROVIDER; } diff --git a/core/src/main/java/com/graphhopper/routing/Router.java b/core/src/main/java/com/graphhopper/routing/Router.java index 204eb668f5e..b7ea9f340d3 100644 --- a/core/src/main/java/com/graphhopper/routing/Router.java +++ b/core/src/main/java/com/graphhopper/routing/Router.java @@ -49,11 +49,11 @@ import java.util.*; -import static com.graphhopper.util.TurnCostsConfig.INFINITE_U_TURN_COSTS; import static com.graphhopper.util.DistanceCalcEarth.DIST_EARTH; import static com.graphhopper.util.Parameters.Algorithms.ALT_ROUTE; import static com.graphhopper.util.Parameters.Algorithms.ROUND_TRIP; import static com.graphhopper.util.Parameters.Routing.*; +import static com.graphhopper.util.TurnCostsConfig.INFINITE_U_TURN_COSTS; public class Router { protected final BaseGraph graph; @@ -103,6 +103,7 @@ public GHResponse route(GHRequest request) { checkPointHints(request); checkCurbsides(request); checkNoBlockArea(request); + checkCustomModel(request); Solver solver = createSolver(request); solver.checkRequest(); @@ -180,6 +181,11 @@ private void checkNoBlockArea(GHRequest request) { throw new IllegalArgumentException("The `block_area` parameter is no longer supported. Use a custom model with `areas` instead."); } + private void checkCustomModel(GHRequest request) { + if (request.getCustomModel() != null && request.getCustomModel().isInternal()) + throw new IllegalArgumentException("CustomModel of query cannot be internal"); + } + protected Solver createSolver(GHRequest request) { final boolean disableCH = getDisableCH(request.getHints()); final boolean disableLM = getDisableLM(request.getHints()); diff --git a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java index 57e6f8ca186..ea03fee0a5e 100644 --- a/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java +++ b/core/src/main/java/com/graphhopper/routing/ev/DefaultImportRegistry.java @@ -98,6 +98,11 @@ else if (MaxLength.KEY.equals(name)) (lookup, props) -> new OSMMaxLengthParser( lookup.getDecimalEncodedValue(MaxLength.KEY)) ); + else if (Orientation.KEY.equals(name)) + return ImportUnit.create(name, props -> Orientation.create(), + (lookup, props) -> new OrientationCalculator( + lookup.getDecimalEncodedValue(Orientation.KEY)) + ); else if (Surface.KEY.equals(name)) return ImportUnit.create(name, props -> Surface.create(), (lookup, props) -> new OSMSurfaceParser( diff --git a/core/src/main/java/com/graphhopper/routing/ev/Orientation.java b/core/src/main/java/com/graphhopper/routing/ev/Orientation.java new file mode 100644 index 00000000000..85863dd7df8 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/ev/Orientation.java @@ -0,0 +1,28 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.ev; + +public class Orientation { + public static final String KEY = "orientation"; + + // Due to pillar nodes we need 2 values: the orientation at the adjacent node and the reverse + // value for orientation at the base node. Store in degrees. + public static DecimalEncodedValue create() { + return new DecimalEncodedValueImpl(KEY, 5, 0, 360 / 30.0, false, true, false); + } +} diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java index 4330f8edf5e..ddcd565931b 100644 --- a/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OSMTemporalAccessParser.java @@ -37,7 +37,6 @@ */ public class OSMTemporalAccessParser implements TagParser { - private static final Logger logger = LoggerFactory.getLogger(OSMTemporalAccessParser.class); private final Collection conditionals; private final Setter restrictionSetter; private final DateRangeParser parser; diff --git a/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java b/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java new file mode 100644 index 00000000000..945919a6fb5 --- /dev/null +++ b/core/src/main/java/com/graphhopper/routing/util/parsers/OrientationCalculator.java @@ -0,0 +1,52 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.graphhopper.routing.util.parsers; + +import com.graphhopper.reader.ReaderWay; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.storage.IntsRef; +import com.graphhopper.util.PointList; + +import static com.graphhopper.util.AngleCalc.ANGLE_CALC; + +public class OrientationCalculator implements TagParser { + + private final DecimalEncodedValue orientationEnc; + + public OrientationCalculator(DecimalEncodedValue orientationEnc) { + this.orientationEnc = orientationEnc; + } + + @Override + public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) { + PointList pointList = way.getTag("point_list", null); + if (pointList != null) { + // store orientation in degrees and use the end of the edge + double azimuth = ANGLE_CALC.calcAzimuth(pointList.getLat(pointList.size() - 2), pointList.getLon(pointList.size() - 2), + pointList.getLat(pointList.size() - 1), pointList.getLon(pointList.size() - 1)); + orientationEnc.setDecimal(false, edgeId, edgeIntAccess, azimuth); + + // same for the opposite direction + double revAzimuth = ANGLE_CALC.calcAzimuth(pointList.getLat(1), pointList.getLon(1), + pointList.getLat(0), pointList.getLon(0)); + orientationEnc.setDecimal(true, edgeId, edgeIntAccess, revAzimuth); + } + } +} + diff --git a/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java b/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java index 4823e08ae4d..1be39c134a6 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/DefaultTurnCostProvider.java @@ -18,10 +18,14 @@ package com.graphhopper.routing.weighting; -import com.graphhopper.util.TurnCostsConfig; import com.graphhopper.routing.ev.BooleanEncodedValue; +import com.graphhopper.routing.ev.DecimalEncodedValue; +import com.graphhopper.routing.ev.EdgeIntAccess; +import com.graphhopper.storage.BaseGraph; +import com.graphhopper.storage.Graph; import com.graphhopper.storage.TurnCostStorage; import com.graphhopper.util.EdgeIterator; +import com.graphhopper.util.TurnCostsConfig; import static com.graphhopper.util.TurnCostsConfig.INFINITE_U_TURN_COSTS; @@ -31,46 +35,92 @@ public class DefaultTurnCostProvider implements TurnCostProvider { private final int uTurnCostsInt; private final double uTurnCosts; - public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, TurnCostStorage turnCostStorage) { - this(turnRestrictionEnc, turnCostStorage, TurnCostsConfig.INFINITE_U_TURN_COSTS); - } + private final double minAngle; + private final double minSharpAngle; + private final double minUTurnAngle; - /** - * @param uTurnCosts the costs of a u-turn in seconds, for {@link TurnCostsConfig#INFINITE_U_TURN_COSTS} the u-turn costs - * will be infinite - */ - public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, TurnCostStorage turnCostStorage, int uTurnCosts) { - if (uTurnCosts < 0 && uTurnCosts != INFINITE_U_TURN_COSTS) { + private final double leftCosts; + private final double leftSharpCosts; + private final double straightCosts; + private final double rightCosts; + private final double rightSharpCosts; + private final BaseGraph graph; + private final EdgeIntAccess edgeIntAccess; + private final DecimalEncodedValue orientationEnc; + + public DefaultTurnCostProvider(BooleanEncodedValue turnRestrictionEnc, DecimalEncodedValue orientationEnc, + Graph graph, TurnCostsConfig tcConfig) { + this.uTurnCostsInt = tcConfig.getUTurnCosts(); + if (uTurnCostsInt < 0 && uTurnCostsInt != INFINITE_U_TURN_COSTS) { throw new IllegalArgumentException("u-turn costs must be positive, or equal to " + INFINITE_U_TURN_COSTS + " (=infinite costs)"); } - this.uTurnCostsInt = uTurnCosts; - this.uTurnCosts = uTurnCosts < 0 ? Double.POSITIVE_INFINITY : uTurnCosts; - if (turnCostStorage == null) { + this.uTurnCosts = uTurnCostsInt < 0 ? Double.POSITIVE_INFINITY : uTurnCostsInt; + if (graph.getTurnCostStorage() == null) { throw new IllegalArgumentException("No storage set to calculate turn weight"); } // if null the TurnCostProvider can be still useful for edge-based routing this.turnRestrictionEnc = turnRestrictionEnc; - this.turnCostStorage = turnCostStorage; - } + this.turnCostStorage = graph.getTurnCostStorage(); - public BooleanEncodedValue getTurnRestrictionEnc() { - return turnRestrictionEnc; + this.orientationEnc = orientationEnc; + if (tcConfig.getMinUTurnAngle() > 180) + throw new IllegalArgumentException("Illegal min_u_turn_angle = " + tcConfig.getMinUTurnAngle()); + if (tcConfig.getMinSharpAngle() > tcConfig.getMinUTurnAngle()) + throw new IllegalArgumentException("Illegal min_sharp_angle = " + tcConfig.getMinSharpAngle()); + if (tcConfig.getMinAngle() > tcConfig.getMinSharpAngle() || tcConfig.getMinAngle() < 0) + throw new IllegalArgumentException("Illegal min_angle = " + tcConfig.getMinAngle()); + if (tcConfig.getLeftCosts() > tcConfig.getLeftSharpCosts()) + throw new IllegalArgumentException("The costs for 'left_costs' (" + tcConfig.getLeftCosts() + + ") must be lower than for 'left_sharp_costs' (" + tcConfig.getLeftSharpCosts() + ")"); + if (tcConfig.getRightCosts() > tcConfig.getRightSharpCosts()) + throw new IllegalArgumentException("The costs for 'right_costs' (" + tcConfig.getRightCosts() + + ") must be lower than for 'right_sharp_costs' (" + tcConfig.getRightSharpCosts() + ")"); + + this.minAngle = tcConfig.getMinAngle(); + this.minSharpAngle = tcConfig.getMinSharpAngle(); + this.minUTurnAngle = tcConfig.getMinUTurnAngle(); + + this.leftCosts = tcConfig.getLeftCosts(); + this.leftSharpCosts = tcConfig.getLeftSharpCosts(); + this.straightCosts = tcConfig.getStraightCosts(); + this.rightCosts = tcConfig.getRightCosts(); + this.rightSharpCosts = tcConfig.getRightSharpCosts(); + + this.graph = graph.getBaseGraph(); + this.edgeIntAccess = graph.getBaseGraph().getEdgeAccess(); } @Override - public double calcTurnWeight(int edgeFrom, int nodeVia, int edgeTo) { - if (!EdgeIterator.Edge.isValid(edgeFrom) || !EdgeIterator.Edge.isValid(edgeTo)) { + public double calcTurnWeight(int inEdge, int viaNode, int outEdge) { + if (!EdgeIterator.Edge.isValid(inEdge) || !EdgeIterator.Edge.isValid(outEdge)) { return 0; } - double tCost = 0; - if (edgeFrom == edgeTo) { + + if (inEdge == outEdge) { // note that the u-turn costs overwrite any turn costs set in TurnCostStorage - tCost = uTurnCosts; + return uTurnCosts; } else { - if (turnRestrictionEnc != null) - tCost = turnCostStorage.get(turnRestrictionEnc, edgeFrom, nodeVia, edgeTo) ? Double.POSITIVE_INFINITY : 0; + if (turnRestrictionEnc != null && turnCostStorage.get(turnRestrictionEnc, inEdge, viaNode, outEdge)) + return Double.POSITIVE_INFINITY; } - return tCost; + + if (orientationEnc != null) { + double changeAngle = calcChangeAngle(inEdge, viaNode, outEdge); + if (changeAngle > -minAngle && changeAngle < minAngle) + return straightCosts; + else if (changeAngle >= minAngle && changeAngle < minSharpAngle) + return rightCosts; + else if (changeAngle >= minSharpAngle && changeAngle <= minUTurnAngle) + return rightSharpCosts; + else if (changeAngle <= -minAngle && changeAngle > -minSharpAngle) + return leftCosts; + else if (changeAngle <= -minSharpAngle && changeAngle >= -minUTurnAngle) + return leftSharpCosts; + + // Too sharp turn is like an u-turn. + return uTurnCosts; + } + return 0; } @Override @@ -87,4 +137,26 @@ 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/FindMinMax.java b/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java index 06b8245ba0a..6d4ffb520c0 100644 --- a/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java +++ b/core/src/main/java/com/graphhopper/routing/weighting/custom/FindMinMax.java @@ -35,8 +35,6 @@ public class FindMinMax { * is based on baseModel). */ public static void checkLMConstraints(CustomModel baseModel, CustomModel queryModel, EncodedValueLookup lookup) { - if (queryModel.isInternal()) - throw new IllegalArgumentException("CustomModel of query cannot be internal"); if (queryModel.getDistanceInfluence() != null) { double bmDI = baseModel.getDistanceInfluence() == null ? 0 : baseModel.getDistanceInfluence(); if (queryModel.getDistanceInfluence() < bmDI) diff --git a/core/src/main/java/com/graphhopper/storage/BaseGraph.java b/core/src/main/java/com/graphhopper/storage/BaseGraph.java index e915fa87320..3abc4c19528 100644 --- a/core/src/main/java/com/graphhopper/storage/BaseGraph.java +++ b/core/src/main/java/com/graphhopper/storage/BaseGraph.java @@ -426,6 +426,15 @@ public boolean isAdjacentToNode(int edge, int node) { return isAdjacentToNode(node, edgePointer); } + /** + * @return true if the specified node is the adjacent node of the specified edge + * (relative to the direction in which the edge is stored). + */ + public boolean isAdjNode(int edge, int node) { + long edgePointer = store.toEdgePointer(edge); + return node == store.getNodeB(edgePointer); + } + private void setWayGeometry_(PointList pillarNodes, long edgePointer, boolean reverse) { if (pillarNodes != null && !pillarNodes.isEmpty()) { if (pillarNodes.getDimension() != nodeAccess.getDimension()) diff --git a/core/src/main/java/com/graphhopper/util/AngleCalc.java b/core/src/main/java/com/graphhopper/util/AngleCalc.java index 4a0ab5939fe..63b9a743df2 100644 --- a/core/src/main/java/com/graphhopper/util/AngleCalc.java +++ b/core/src/main/java/com/graphhopper/util/AngleCalc.java @@ -59,10 +59,9 @@ public double calcOrientation(double lat1, double lon1, double lat2, double lon2 /** * Return orientation of line relative to east. - *

* * @param exact If false the atan gets calculated faster, but it might contain small errors - * @return Orientation in interval -pi to +pi where 0 is east + * @return Orientation in interval -pi to +pi where 0 is east and the "bottom" arc is negative */ public double calcOrientation(double lat1, double lon1, double lat2, double lon2, boolean exact) { double shrinkFactor = cos(toRadians((lat1 + lat2) / 2)); diff --git a/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json new file mode 100644 index 00000000000..6f1cf336c2f --- /dev/null +++ b/core/src/main/resources/com/graphhopper/custom_models/bike_tc.json @@ -0,0 +1,29 @@ +// Configures bike with turn costs (3sec for left and right turns) which reduces zig-zag routes. +// Note, it is not recommended to increase these costs heavily as otherwise larger, bike-unfriendly +// roads will be preferred as they often require less turns. +// +// to use this custom model you need to set the following option in the config.yml +// graph.elevation.provider: srtm # enables elevation +// graph.encoded_values: bike_priority, bike_access, roundabout, bike_average_speed, average_slope, orientation +// profiles: +// - name: bike +// turn_costs: +// vehicle_types: [bicycle] +// u_turn_costs: 20 +// left_costs: 3 +// left_sharp_costs: 3 +// right_costs: 3 +// right_sharp_costs: 3 +// custom_model_files: [bike_tc.json, bike_elevation.json] + +{ + "priority": [ + { "if": "true", "multiply_by": "bike_priority" }, + { "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" } + ] +} diff --git a/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java b/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java new file mode 100644 index 00000000000..839f8e9e2ed --- /dev/null +++ b/core/src/test/java/com/graphhopper/routing/weighting/DefaultTurnCostProviderTest.java @@ -0,0 +1,133 @@ +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(). + setRightCosts(0.5).setRightSharpCosts(1). + setLeftCosts(6).setLeftSharpCosts(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/CustomWeightingTest.java b/core/src/test/java/com/graphhopper/routing/weighting/custom/CustomWeightingTest.java index 2ae5a98dcb9..08fa5a98c8c 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 @@ -404,7 +404,8 @@ public void testTime() { 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, graph.getTurnCostStorage()), customModel); + Weighting weighting = CustomModelParser.createWeighting(encodingManager, + new DefaultTurnCostProvider(turnRestrictionEnc, null, graph, new TurnCostsConfig()), 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); @@ -417,7 +418,8 @@ public void calcWeightAndTime_withTurnCosts() { 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, graph.getTurnCostStorage(), 40), customModel); + Weighting weighting = CustomModelParser.createWeighting(encodingManager, + new DefaultTurnCostProvider(turnRestrictionEnc, null, graph, new TurnCostsConfig().setUTurnCosts(40)), 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/docs/web/api-doc.md b/docs/web/api-doc.md index 38c3dca1d70..b60169f3f6e 100644 --- a/docs/web/api-doc.md +++ b/docs/web/api-doc.md @@ -42,10 +42,10 @@ All official parameters are shown in the following table elevation | false | If `true` a third dimension - the elevation - is included in the polyline or in the GeoJson. IMPORTANT: If enabled you have to use a modified version of the decoding method or set points_encoded to `false`. See the points_encoded attribute for more details. Additionally a request can fail if the vehicle does not support elevation. See the features object for every vehicle. points_encoded | true | If `false` the coordinates in `point` and `snapped_waypoints` are returned as array using the order [lon,lat,elevation] for every point. If `true` the coordinates will be encoded as string leading to less bandwidth usage. You'll need a special handling for the decoding of this string on the client-side. We provide open source code in [Java](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/java/com/graphhopper/http/WebHelper.java#L43) and [JavaScript](https://github.com/graphhopper/graphhopper/blob/d70b63660ac5200b03c38ba3406b8f93976628a6/web/src/main/webapp/js/ghrequest.js#L139). It is especially important to use no 3rd party client if you set `elevation=true`! points_encoded_encoded |1e5| Used in case `points_encoded=true` to encode the `points` string into an array of coordinates. - debug | false | If true, the output will be formatted. - calc_points | true | If the points for the route should be calculated at all printing out only distance and time. - point_hint | - | Optional parameter. Specifies a hint for each `point` parameter to prefer a certain street for the closest location lookup. E.g. if there is an address or house with two or more neighboring streets you can control for which street the closest location is looked up. - snap_prevention | - | Optional parameter to avoid snapping to a certain road class or road environment. Current supported values: `motorway`, `trunk`, `ferry`, `tunnel`, `bridge` and `ford`. Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway` + debug | false | If true, the output will be formatted. + calc_points | true | If the points for the route should be calculated at all printing out only distance and time. + point_hint | - | Optional parameter. When finding the closest road location for GPS coordinates provided in the `point` parameter this hint prefers a road with a similar name. E.g. if there is an address with two close roads you can control which street is preferred. Only include the road name and not the house number to improve the name matching quality. + snap_prevention | - | Optional parameter. 'Snapping' is the process of finding the closest road location for GPS coordinates provided in the `point` parameter. The `snap_prevention` parameter allows you to prevent snapping to specific types of roads. For example, if `snap_prevention` is set to bridge, the routing engine will avoid snapping to a bridge, even if it is the closest road for the given `point`. Current supported values: `motorway`, `trunk`, `ferry`, `tunnel`, `bridge` and `ford`. Multiple values are specified like `snap_prevention=ferry&snap_prevention=motorway`. Note that once snapped the routing algorithm can still route over bridges (or the other values). To avoid this you need to use the `custom_model`. details | - | Optional parameter. You can request additional details for the route: `average_speed`, `street_name`, `edge_id`, `road_class`, `road_environment`, `max_speed` and `time` (and see which other values are configured in `graph.encoded_values`). Multiple values are specified like `details=average_speed&details=time`. The returned format for one detail segment is `[fromRef, toRef, value]`. The `ref` references the points of the response. Value can also be `null` if the property does not exist for one detail segment. curbside | any | Optional parameter applicable to edge-based routing only. It specifies on which side a query point should be relative to the driver when she leaves/arrives at a start/target/via point. Possible values: right, left, any. Specify for every point parameter. See similar heading parameter. curbside_strictness| strict| Optional parameter. If it is set to "strict" there will be an exception in case the curbside parameters cannot be fulfilled (e.g. specifying the wrong side for one-ways). If you don't want this use "soft". diff --git a/pom.xml b/pom.xml index 81cf8d9ba7c..18654e61979 100644 --- a/pom.xml +++ b/pom.xml @@ -75,7 +75,7 @@ io.dropwizard dropwizard-dependencies - 2.1.11 + 3.0.8 pom import diff --git a/web-api/src/main/java/com/graphhopper/util/CustomModel.java b/web-api/src/main/java/com/graphhopper/util/CustomModel.java index 843cf94e933..02e4136ce0a 100644 --- a/web-api/src/main/java/com/graphhopper/util/CustomModel.java +++ b/web-api/src/main/java/com/graphhopper/util/CustomModel.java @@ -159,7 +159,8 @@ public String toString() { private String createContentString() { // used to check against stored custom models, see #2026 return "distanceInfluence=" + distanceInfluence + "|headingPenalty=" + headingPenalty - + "|speedStatements=" + speedStatements + "|priorityStatements=" + priorityStatements + "|areas=" + areas; + + "|speedStatements=" + speedStatements + "|priorityStatements=" + priorityStatements + + "|areas=" + areas; } /** @@ -178,8 +179,8 @@ public static CustomModel merge(CustomModel baseModel, CustomModel queryModel) { mergedCM.headingPenalty = queryModel.headingPenalty; mergedCM.speedStatements.addAll(queryModel.getSpeed()); mergedCM.priorityStatements.addAll(queryModel.getPriority()); - mergedCM.addAreas(queryModel.getAreas()); + mergedCM.addAreas(queryModel.getAreas()); return mergedCM; } } diff --git a/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java b/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java index b13e38d3995..7d4aa853ce1 100644 --- a/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java +++ b/web-api/src/main/java/com/graphhopper/util/TurnCostsConfig.java @@ -2,11 +2,25 @@ import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.ArrayList; import java.util.List; import java.util.Set; public class TurnCostsConfig { public static final int INFINITE_U_TURN_COSTS = -1; + private double leftCosts; // in seconds + private double leftSharpCosts; // in seconds + private double straightCosts; + private double rightCosts; + private double rightSharpCosts; + + // The "right" and "left" turns are symmetric and the negative values are used for "left". + // From 0 to minAngle no turn costs are added. + // From minAngle to minSharpAngle the rightCosts (or leftCosts) are added. + // From minSharpAngle to minUTurnAngle the rightSharpCosts (or leftSharpCosts) are added. + // And beyond minUTurnAngle the uTurnCosts are added. + private double minAngle = 25, minSharpAngle = 80, minUTurnAngle = 180; + private int uTurnCosts = INFINITE_U_TURN_COSTS; private List vehicleTypes; // ensure that no typos can occur like motor_car vs motorcar or bike vs bicycle @@ -27,10 +41,24 @@ public static TurnCostsConfig bike() { return new TurnCostsConfig(List.of("bicycle")); } - // jackson public TurnCostsConfig() { } + public TurnCostsConfig(TurnCostsConfig copy) { + leftCosts = copy.leftCosts; + leftSharpCosts = copy.leftSharpCosts; + straightCosts = copy.straightCosts; + rightCosts = copy.rightCosts; + rightSharpCosts = copy.rightSharpCosts; + uTurnCosts = copy.uTurnCosts; + + minAngle = copy.minAngle; + minSharpAngle = copy.minSharpAngle; + minUTurnAngle = copy.minUTurnAngle; + if (copy.vehicleTypes != null) + vehicleTypes = new ArrayList<>(copy.vehicleTypes); + } + public TurnCostsConfig(List vehicleTypes) { this.vehicleTypes = check(vehicleTypes); } @@ -40,10 +68,6 @@ public TurnCostsConfig(List vehicleTypes, int uTurnCost) { this.uTurnCosts = uTurnCost; } - public void setVehicleTypes(List vehicleTypes) { - this.vehicleTypes = check(vehicleTypes); - } - List check(List restrictions) { if (restrictions == null || restrictions.isEmpty()) throw new IllegalArgumentException("turn_costs cannot have empty vehicle_types"); @@ -54,12 +78,21 @@ List check(List restrictions) { return restrictions; } + public TurnCostsConfig setVehicleTypes(List vehicleTypes) { + this.vehicleTypes = check(vehicleTypes); + return this; + } + @JsonProperty("vehicle_types") public List getVehicleTypes() { check(vehicleTypes); return vehicleTypes; } + /** + * @param uTurnCosts the costs of an u-turn in seconds, for {@link TurnCostsConfig#INFINITE_U_TURN_COSTS} + * the u-turn costs will be infinite + */ public TurnCostsConfig setUTurnCosts(int uTurnCosts) { this.uTurnCosts = uTurnCosts; return this; @@ -70,8 +103,96 @@ public int getUTurnCosts() { return uTurnCosts; } + public boolean hasLeftRightStraightCosts() { + return leftCosts != 0 || leftSharpCosts != 0 || straightCosts != 0 || rightCosts != 0 || rightSharpCosts != 0; + } + + public TurnCostsConfig setLeftCosts(double leftCosts) { + this.leftCosts = leftCosts; + return this; + } + + @JsonProperty("left_costs") + public double getLeftCosts() { + return leftCosts; + } + + public TurnCostsConfig setLeftSharpCosts(double leftSharpCosts) { + this.leftSharpCosts = leftSharpCosts; + return this; + } + + @JsonProperty("left_sharp_costs") + public double getLeftSharpCosts() { + return leftSharpCosts; + } + + public TurnCostsConfig setRightCosts(double rightCosts) { + this.rightCosts = rightCosts; + return this; + } + + @JsonProperty("right_costs") + public double getRightCosts() { + return rightCosts; + } + + public TurnCostsConfig setRightSharpCosts(double rightSharpCosts) { + this.rightSharpCosts = rightSharpCosts; + return this; + } + + @JsonProperty("right_sharp_costs") + public double getRightSharpCosts() { + return rightSharpCosts; + } + + public TurnCostsConfig setStraightCosts(double straightCosts) { + this.straightCosts = straightCosts; + return this; + } + + @JsonProperty("straight_costs") + public double getStraightCosts() { + return straightCosts; + } + + @JsonProperty("min_angle") + public TurnCostsConfig setMinAngle(double minAngle) { + this.minAngle = minAngle; + return this; + } + + public double getMinAngle() { + return minAngle; + } + + @JsonProperty("min_sharp_angle") + public TurnCostsConfig setMinSharpAngle(double minSharpAngle) { + this.minSharpAngle = minSharpAngle; + return this; + } + + public double getMinSharpAngle() { + return minSharpAngle; + } + + @JsonProperty("min_u_turn_angle") + public TurnCostsConfig setMinUTurnAngle(double minUTurnAngle) { + this.minUTurnAngle = minUTurnAngle; + return this; + } + + public double getMinUTurnAngle() { + return minUTurnAngle; + } + @Override public String toString() { - return "vehicleTypes=" + vehicleTypes + ", uTurnCosts=" + uTurnCosts; + return "left=" + leftCosts + ", leftSharp=" + leftSharpCosts + + ", straight=" + straightCosts + + ", right=" + rightCosts + ", rightSharp=" + rightSharpCosts + + ", minAngle=" + minAngle + ", minSharpAngle=" + minSharpAngle + ", minUTurnAngle=" + minUTurnAngle + + ", uTurnCosts=" + uTurnCosts + ", vehicleTypes=" + vehicleTypes; } } diff --git a/web-bundle/pom.xml b/web-bundle/pom.xml index 76dead1c6c1..21e7788b8ed 100644 --- a/web-bundle/pom.xml +++ b/web-bundle/pom.xml @@ -69,7 +69,6 @@ org.locationtech.jts jts-core - 1.19.0 com.fasterxml.jackson.jaxrs diff --git a/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java b/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java index 2b26499d1f6..51640857f3a 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/DurationParam.java @@ -20,20 +20,19 @@ import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; import java.time.Duration; public class DurationParam extends AbstractParam { - public DurationParam(@Nullable String input) { + public DurationParam(String input) { super(input); } - public DurationParam(@Nullable String input, String parameterName) { + public DurationParam(String input, String parameterName) { super(input, parameterName); } @Override - protected Duration parse(@Nullable String input) throws Exception { + protected Duration parse(String input) throws Exception { if (input == null) return null; return Duration.parse(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java b/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java index 29b11022978..8cfdfcdda9a 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GHLocationParam.java @@ -21,20 +21,18 @@ import com.graphhopper.gtfs.GHLocation; import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; - public class GHLocationParam extends AbstractParam { - public GHLocationParam(@Nullable String input) { + public GHLocationParam(String input) { super(input); } - public GHLocationParam(@Nullable String input, String parameterName) { + public GHLocationParam(String input, String parameterName) { super(input, parameterName); } @Override - protected GHLocation parse(@Nullable String input) throws Exception { + protected GHLocation parse(String input) throws Exception { if (input == null) return null; return GHLocation.fromString(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java b/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java index b0607dc047f..c3850984d53 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GHPointParam.java @@ -21,7 +21,6 @@ import com.graphhopper.util.shapes.GHPoint; import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; /** * This is a glue type, used to plug GHPoint as a custom web resource parameter type into Dropwizard, @@ -38,16 +37,16 @@ */ public class GHPointParam extends AbstractParam { - public GHPointParam(@Nullable String input) { + public GHPointParam(String input) { super(input); } - public GHPointParam(@Nullable String input, String parameterName) { + public GHPointParam(String input, String parameterName) { super(input, parameterName); } @Override - protected GHPoint parse(@Nullable String input) throws Exception { + protected GHPoint parse(String input) { if (input == null) return null; return GHPoint.fromString(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java index 05991a1cb7c..0f4d46c6e55 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java +++ b/web-bundle/src/main/java/com/graphhopper/http/GraphHopperBundle.java @@ -36,9 +36,9 @@ import com.graphhopper.util.PMap; import com.graphhopper.util.TranslationMap; import com.graphhopper.util.details.PathDetailsBuilderFactory; -import io.dropwizard.ConfiguredBundle; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.ConfiguredBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.utilities.binding.AbstractBinder; diff --git a/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java b/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java index 840d92de5da..2fdf9da2e65 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java +++ b/web-bundle/src/main/java/com/graphhopper/http/OffsetDateTimeParam.java @@ -20,16 +20,14 @@ import io.dropwizard.jersey.params.AbstractParam; -import javax.annotation.Nullable; import java.time.OffsetDateTime; -import java.time.ZonedDateTime; public class OffsetDateTimeParam extends AbstractParam { - public OffsetDateTimeParam(@Nullable String input) { + public OffsetDateTimeParam(String input) { super(input); } - public OffsetDateTimeParam(@Nullable String input, String parameterName) { + public OffsetDateTimeParam(String input, String parameterName) { super(input, parameterName); } @@ -39,7 +37,7 @@ protected String errorMessage(Exception e) { } @Override - protected OffsetDateTime parse(@Nullable String input) throws Exception { + protected OffsetDateTime parse(String input) { if (input == null) return null; return OffsetDateTime.parse(input); diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java b/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java index 1ecfc74b9f4..910ac53b7d5 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java +++ b/web-bundle/src/main/java/com/graphhopper/http/RealtimeBundle.java @@ -20,11 +20,11 @@ import com.graphhopper.gtfs.GtfsStorage; import com.graphhopper.gtfs.RealtimeFeed; -import io.dropwizard.ConfiguredBundle; import io.dropwizard.client.HttpClientBuilder; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; -import org.apache.http.client.HttpClient; +import io.dropwizard.core.ConfiguredBundle; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; +import org.apache.hc.client5.http.classic.HttpClient; import org.glassfish.hk2.api.Factory; import org.glassfish.hk2.utilities.binding.AbstractBinder; diff --git a/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java b/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java index e563dc135f1..21ffb89e474 100644 --- a/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java +++ b/web-bundle/src/main/java/com/graphhopper/http/RealtimeFeedLoadingCache.java @@ -31,8 +31,8 @@ import com.graphhopper.routing.util.EncodingManager; import com.graphhopper.storage.BaseGraph; import io.dropwizard.lifecycle.Managed; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; +import org.apache.hc.client5.http.classic.HttpClient; +import org.apache.hc.client5.http.classic.methods.HttpGet; import org.glassfish.hk2.api.Factory; import javax.inject.Inject; @@ -112,7 +112,8 @@ private RealtimeFeed fetchFeedsAndCreateGraph() { Map feedMessageMap = new HashMap<>(); for (FeedConfiguration configuration : bundleConfiguration.gtfsrealtime().getFeeds()) { try { - GtfsRealtime.FeedMessage feedMessage = GtfsRealtime.FeedMessage.parseFrom(httpClient.execute(new HttpGet(configuration.getUrl().toURI())).getEntity().getContent()); + GtfsRealtime.FeedMessage feedMessage = httpClient.execute(new HttpGet(configuration.getUrl().toURI()), + response -> GtfsRealtime.FeedMessage.parseFrom(response.getEntity().getContent())); feedMessageMap.put(configuration.getFeedId(), feedMessage); } catch (IOException | URISyntaxException e) { throw new RuntimeException(e); diff --git a/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java b/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java index 067aa5c6e92..b776b1bf82c 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/MapMatchingResource.java @@ -37,7 +37,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.validation.constraints.NotNull; import javax.ws.rs.*; @@ -71,7 +70,6 @@ public interface MapMatchingRouterFactory { private final TranslationMap trMap; private final MapMatchingRouterFactory mapMatchingRouterFactory; private final ObjectMapper objectMapper = Jackson.newObjectMapper(); - @Nullable private final String osmDate; @Inject diff --git a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java index c4ed34b2f05..4467099dd7d 100644 --- a/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java +++ b/web-bundle/src/main/java/com/graphhopper/resources/RouteResource.java @@ -33,7 +33,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import javax.inject.Inject; import javax.inject.Named; import javax.servlet.http.HttpServletRequest; @@ -64,7 +63,6 @@ public class RouteResource { private final ProfileResolver profileResolver; private final GHRequestTransformer ghRequestTransformer; private final Boolean hasElevation; - @Nullable private final String osmDate; @Inject diff --git a/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java b/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java index 22bf4b12e5a..ac1463f297f 100644 --- a/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java +++ b/web/src/main/java/com/graphhopper/application/GraphHopperApplication.java @@ -24,10 +24,10 @@ import com.graphhopper.http.GraphHopperBundle; import com.graphhopper.http.RealtimeBundle; import com.graphhopper.navigation.NavigateResource; -import io.dropwizard.Application; import io.dropwizard.assets.AssetsBundle; -import io.dropwizard.setup.Bootstrap; -import io.dropwizard.setup.Environment; +import io.dropwizard.core.Application; +import io.dropwizard.core.setup.Bootstrap; +import io.dropwizard.core.setup.Environment; import javax.servlet.DispatcherType; import java.util.EnumSet; diff --git a/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java b/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java index b9b8641256f..018f460332a 100644 --- a/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java +++ b/web/src/main/java/com/graphhopper/application/GraphHopperServerConfiguration.java @@ -22,7 +22,7 @@ import com.graphhopper.http.GraphHopperBundleConfiguration; import com.graphhopper.http.RealtimeBundleConfiguration; import com.graphhopper.http.RealtimeConfiguration; -import io.dropwizard.Configuration; +import io.dropwizard.core.Configuration; import javax.validation.constraints.NotNull; diff --git a/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java b/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java index e47186086c7..7deff397be7 100644 --- a/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java +++ b/web/src/main/java/com/graphhopper/application/cli/ImportCommand.java @@ -20,8 +20,8 @@ import com.graphhopper.application.GraphHopperServerConfiguration; import com.graphhopper.http.GraphHopperManaged; -import io.dropwizard.cli.ConfiguredCommand; -import io.dropwizard.setup.Bootstrap; +import io.dropwizard.core.cli.ConfiguredCommand; +import io.dropwizard.core.setup.Bootstrap; import net.sourceforge.argparse4j.inf.Namespace; public class ImportCommand extends ConfiguredCommand { diff --git a/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java b/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java index f20ea56dcdb..a6718066c67 100644 --- a/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java +++ b/web/src/main/java/com/graphhopper/application/cli/MatchCommand.java @@ -29,8 +29,8 @@ import com.graphhopper.matching.MatchResult; import com.graphhopper.matching.Observation; import com.graphhopper.util.*; -import io.dropwizard.cli.ConfiguredCommand; -import io.dropwizard.setup.Bootstrap; +import io.dropwizard.core.cli.ConfiguredCommand; +import io.dropwizard.core.setup.Bootstrap; import net.sourceforge.argparse4j.inf.Argument; import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Subparser; diff --git a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java index 2959c159272..74a5feca871 100644 --- a/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/MapMatchingResourceTurnCostsTest.java @@ -23,9 +23,9 @@ import com.graphhopper.config.CHProfile; import com.graphhopper.config.LMProfile; import com.graphhopper.jackson.ResponsePathDeserializerHelper; -import com.graphhopper.util.TurnCostsConfig; import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.Helper; +import com.graphhopper.util.TurnCostsConfig; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import org.junit.jupiter.api.AfterAll; diff --git a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java index eddbf85c691..85a52715531 100644 --- a/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java +++ b/web/src/test/java/com/graphhopper/application/resources/RouteResourceCustomModelTest.java @@ -27,6 +27,7 @@ import com.graphhopper.routing.TestProfiles; import com.graphhopper.util.GHUtility; import com.graphhopper.util.Helper; +import com.graphhopper.util.TurnCostsConfig; import io.dropwizard.testing.junit5.DropwizardAppExtension; import io.dropwizard.testing.junit5.DropwizardExtensionsSupport; import org.junit.jupiter.api.AfterAll; @@ -62,15 +63,18 @@ private static GraphHopperServerConfiguration createConfig() { putObject("prepare.min_network_size", 200). putObject("datareader.file", "../core/files/north-bayreuth.osm.gz"). putObject("graph.location", DIR). - putObject("graph.encoded_values", "max_height,max_weight,max_width,hazmat,toll,surface,track_type,hgv,average_slope,max_slope,bus_access"). putObject("custom_areas.directory", "./src/test/resources/com/graphhopper/application/resources/areas"). putObject("import.osm.ignored_highways", ""). putObject("graph.encoded_values", "max_height, max_weight, max_width, hazmat, toll, surface, track_type, hgv, average_slope, max_slope, bus_access, " + - "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed, road_class, road_access, get_off_bike, roundabout, foot_access, foot_priority, foot_average_speed, country"). + "car_access, car_average_speed, bike_access, bike_priority, bike_average_speed, road_class, road_access, get_off_bike, roundabout, foot_access, foot_priority, foot_average_speed, country, orientation"). setProfiles(List.of( TestProfiles.constantSpeed("roads", 120), - new Profile("car").setCustomModel(TestProfiles.accessAndSpeed("unused", "car").getCustomModel().setDistanceInfluence(70d)), - new Profile("car_with_area").setCustomModel(TestProfiles.accessAndSpeed("unused", "car").getCustomModel().addToPriority(If("in_external_area52", MULTIPLY, "0.05"))), + new Profile("car").setCustomModel(TestProfiles.accessAndSpeed("unused", "car"). + getCustomModel().setDistanceInfluence(70d)), + new Profile("car_tc_left").setCustomModel(TestProfiles.accessAndSpeed("car_tc_left", "car"). + getCustomModel().setDistanceInfluence(70d)).setTurnCostsConfig(new TurnCostsConfig(List.of("motor_vehicle")).setLeftCosts(100.0).setLeftSharpCosts(100.0)), + new Profile("car_with_area").setCustomModel(TestProfiles.accessAndSpeed("unused", "car"). + getCustomModel().addToPriority(If("in_external_area52", MULTIPLY, "0.05"))), TestProfiles.accessSpeedAndPriority("bike"), new Profile("bus").setCustomModel(null).putHint("custom_model_files", List.of("bus.json")), new Profile("cargo_bike").setCustomModel(null).putHint("custom_model_files", List.of("cargo_bike.json")), @@ -344,6 +348,30 @@ public void testHgv() { assertEquals(944 * 1000, path.get("time").asLong(), 1_000); } + @Test + public void testTurnCosts() { + String body = "{\"points\": [[11.508198,50.015441], [11.505063,50.01737]], \"profile\": \"car_tc_left\", \"ch.disable\":true }"; + JsonNode path = getPath(body); + assertEquals(1067, path.get("distance").asDouble(), 10); + } + + @Test + public void testTurnCostsAlternativeBug() { + String body = "{\"points\": [[11.503027,49.987546], [11.503149,49.986786]], \"profile\": \"car_tc_left\", \"ch.disable\":true}"; + JsonNode path = getPath(body); + assertEquals(545, path.get("distance").asDouble(), 10); + + body = "{\"points\": [[11.503027,49.987546], [11.503149,49.986786]], \"profile\": \"car_tc_left\", \"ch.disable\":true," + + "\"algorithm\":\"alternative_route\"}"; + final Response response = query(body, 200); + JsonNode json = response.readEntity(JsonNode.class); + assertFalse(json.get("info").has("errors")); + + // TODO LATER: alternative route bug as the two routes are identical!? + assertEquals(2, json.get("paths").size()); + assertEquals(545, json.get("paths").get(0).get("distance").asDouble(), 10); + } + private void assertMessageStartsWith(JsonNode jsonNode, String message) { assertNotNull(jsonNode.get("message")); assertTrue(jsonNode.get("message").asText().startsWith(message), "Expected error message to start with:\n" + diff --git a/web/src/test/java/com/graphhopper/application/util/GraphHopperServerTestConfiguration.java b/web/src/test/java/com/graphhopper/application/util/GraphHopperServerTestConfiguration.java index 5c61f6055b0..4464eb3fc3f 100644 --- a/web/src/test/java/com/graphhopper/application/util/GraphHopperServerTestConfiguration.java +++ b/web/src/test/java/com/graphhopper/application/util/GraphHopperServerTestConfiguration.java @@ -18,8 +18,8 @@ package com.graphhopper.application.util; import com.graphhopper.application.GraphHopperServerConfiguration; +import io.dropwizard.core.server.DefaultServerFactory; import io.dropwizard.jetty.HttpConnectorFactory; -import io.dropwizard.server.DefaultServerFactory; /** * @author thomas aulinger diff --git a/web/src/test/java/com/graphhopper/application/util/TestUtils.java b/web/src/test/java/com/graphhopper/application/util/TestUtils.java index 70e1855f8c6..36dd017c5ff 100644 --- a/web/src/test/java/com/graphhopper/application/util/TestUtils.java +++ b/web/src/test/java/com/graphhopper/application/util/TestUtils.java @@ -17,7 +17,7 @@ */ package com.graphhopper.application.util; -import io.dropwizard.Configuration; +import io.dropwizard.core.Configuration; import io.dropwizard.testing.junit5.DropwizardAppExtension; import javax.ws.rs.client.WebTarget;