Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ferry_speed encoded value #2849

Merged
merged 1 commit into from Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,5 +1,6 @@
### 8.0 [not yet released]

- removed duration:seconds as intermediate tag
- /info endpoint does no longer return the vehicle used per profile and won't return encoded value of vehicles like car_average_speed
- Country rules no longer contain maxspeed handling, enable a much better alternative via `max_speed_calculator.enabled: true`. On the client side use `max_speed_estimated` to determine if max_speed is from OSM or an estimation. See #2810
- bike routing better avoids dangerous roads, see #2796 and #2802
Expand Down
2 changes: 2 additions & 0 deletions core/src/main/java/com/graphhopper/GraphHopper.java
Expand Up @@ -656,6 +656,8 @@ protected OSMParsers buildOSMParsers(Map<String, String> vehiclesByName, List<St
osmParsers.addWayTagParser(new OSMMaxSpeedParser(encodingManager.getDecimalEncodedValue(MaxSpeed.KEY)));
if (!encodedValueStrings.contains(RoadAccess.KEY))
osmParsers.addWayTagParser(new OSMRoadAccessParser(encodingManager.getEnumEncodedValue(RoadAccess.KEY, RoadAccess.class), OSMRoadAccessParser.toOSMRestrictions(TransportationMode.CAR)));
if (!encodedValueStrings.contains(FerrySpeed.KEY))
osmParsers.addWayTagParser(new FerrySpeedCalculator(encodingManager.getDecimalEncodedValue(FerrySpeed.KEY)));
if (encodingManager.hasEncodedValue(AverageSlope.KEY) || encodingManager.hasEncodedValue(MaxSlope.KEY)) {
if (!encodingManager.hasEncodedValue(AverageSlope.KEY) || !encodingManager.hasEncodedValue(MaxSlope.KEY))
throw new IllegalArgumentException("Enable both, average_slope and max_slope");
Expand Down
3 changes: 1 addition & 2 deletions core/src/main/java/com/graphhopper/reader/osm/OSMReader.java
Expand Up @@ -487,11 +487,10 @@ protected void preprocessWay(ReaderWay way, WaySegmentParser.CoordinateSupplier
" minutes), distance=" + distance + " m");
return;
}
// These tags will be present if 1) isCalculateWayDistance was true for this way, 2) no OSM nodes were missing
// tag will be present if 1) isCalculateWayDistance was true for this way, 2) no OSM nodes were missing
// such that the distance could actually be calculated, 3) there was a duration tag we could parse, and 4) the
// derived speed was not unrealistically slow.
way.setTag("speed_from_duration", speedInKmPerHour);
way.setTag("duration:seconds", durationInSeconds);
}

static String fixWayName(String str) {
Expand Down
Expand Up @@ -95,6 +95,8 @@ public EncodedValue create(String name, PMap properties) {
return Curvature.create();
} else if (Crossing.KEY.equals(name)) {
return new EnumEncodedValue<>(Crossing.KEY, Crossing.class);
} else if (FerrySpeed.KEY.equals(name)) {
return FerrySpeed.create();
} else {
throw new IllegalArgumentException("DefaultEncodedValueFactory cannot find EncodedValue " + name);
}
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/java/com/graphhopper/routing/ev/FerrySpeed.java
@@ -0,0 +1,9 @@
package com.graphhopper.routing.ev;

public class FerrySpeed {
public static final String KEY = "ferry_speed";

public static DecimalEncodedValue create() {
return new DecimalEncodedValueImpl(KEY, 5, 2, false);
}
}
Expand Up @@ -168,7 +168,8 @@ private void addDefaultEncodedValues() {
RoadClassLink.KEY,
RoadEnvironment.KEY,
MaxSpeed.KEY,
RoadAccess.KEY
RoadAccess.KEY,
FerrySpeed.KEY
));
if (em.getVehicles().stream().anyMatch(vehicle -> vehicle.contains("bike") || vehicle.contains("mtb") || vehicle.contains("racingbike"))) {
keys.add(BikeNetwork.KEY);
Expand Down Expand Up @@ -274,4 +275,4 @@ public <T extends EncodedValue> T getEncodedValue(String key, Class<T> encodedVa
public static String getKey(String prefix, String str) {
return prefix + "_" + str;
}
}
}
@@ -1,29 +1,34 @@
package com.graphhopper.routing.util;

import com.graphhopper.reader.ReaderWay;
import com.graphhopper.routing.ev.DecimalEncodedValue;
import com.graphhopper.routing.ev.EdgeIntAccess;
import com.graphhopper.routing.util.parsers.TagParser;
import com.graphhopper.storage.IntsRef;

public class FerrySpeedCalculator {
private final double unknownSpeed, minSpeed, maxSpeed;
import java.util.Arrays;
import java.util.Collection;

public FerrySpeedCalculator(double minSpeed, double maxSpeed, double unknownSpeed) {
this.minSpeed = minSpeed;
this.maxSpeed = maxSpeed;
this.unknownSpeed = unknownSpeed;
public class FerrySpeedCalculator implements TagParser {
public static final Collection<String> FERRIES = Arrays.asList("shuttle_train", "ferry");
private DecimalEncodedValue ferrySpeedEnc;

public FerrySpeedCalculator(DecimalEncodedValue ferrySpeedEnc) {
this.ferrySpeedEnc = ferrySpeedEnc;
}

public double getSpeed(ReaderWay way) {
static double getSpeed(ReaderWay way) {
// todo: We currently face two problems related to ferry speeds:
// 1) We cannot account for waiting times for short ferries (when we do the ferry speed is slower than the slowest we can store)
// 2) When the ferry speed is larger than the maximum speed of the encoder (like 15km/h for foot) the
// ferry speed will be faster than what we can store.

// OSMReader adds the artificial 'speed_from_duration', 'duration:seconds' and 'way_distance' tags that we can
// OSMReader adds the artificial 'speed_from_duration' and 'way_distance' tags that we can
// use to set the ferry speed. Otherwise we need to use fallback values.
double speedInKmPerHour = way.getTag("speed_from_duration", Double.NaN);
if (!Double.isNaN(speedInKmPerHour)) {
// we reduce the speed to account for waiting time (we increase the duration by 40%)
double speedWithWaitingTime = speedInKmPerHour / 1.4;
return Math.round(Math.max(minSpeed, Math.min(speedWithWaitingTime, maxSpeed)));
return Math.round(speedInKmPerHour / 1.4);
} else {
// we have no speed value to work with because there was no valid duration tag.
// we have to take a guess based on the distance.
Expand All @@ -35,11 +40,23 @@ else if (wayDistance < 500)
// that take you from one harbour to another, but rather ways that only represent the beginning of a
// longer ferry connection and that are used by multiple different connections, like here: https://www.openstreetmap.org/way/107913687
// It should not matter much which speed we use in this case, so we have no special handling for these.
return minSpeed;
return 1;
else {
// todo: distinguish speed based on the distance of the ferry, see #2532
return unknownSpeed;
return 6;
}
}
}

public static double minmax(double speed, DecimalEncodedValue avgSpeedEnc) {
return Math.max(avgSpeedEnc.getSmallestNonZeroValue(), Math.min(speed, avgSpeedEnc.getMaxStorableDecimal()));
}

@Override
public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way, IntsRef relationFlags) {
if (!way.hasTag("highway") && way.hasTag("route", FERRIES)) {
double ferrySpeed = minmax(getSpeed(way), ferrySpeedEnc);
ferrySpeedEnc.setDecimal(false, edgeId, edgeIntAccess, ferrySpeed);
}
}
}
Expand Up @@ -13,7 +13,6 @@
import java.util.*;

public abstract class AbstractAccessParser implements TagParser {
static final Collection<String> FERRIES = Arrays.asList("shuttle_train", "ferry");
static final Collection<String> ONEWAYS = Arrays.asList("yes", "true", "1", "-1");
static final Collection<String> INTENDED = Arrays.asList("yes", "designated", "official", "permissive");

Expand All @@ -22,7 +21,6 @@ public abstract class AbstractAccessParser implements TagParser {
protected final Set<String> restrictedValues = new HashSet<>(5);

protected final Set<String> intendedValues = new HashSet<>(INTENDED);
protected final Set<String> ferries = new HashSet<>(FERRIES);
protected final Set<String> oneways = new HashSet<>(ONEWAYS);
// http://wiki.openstreetmap.org/wiki/Mapfeatures#Barrier
protected final Set<String> barriers = new HashSet<>(5);
Expand Down
Expand Up @@ -10,17 +10,17 @@
import java.util.HashSet;
import java.util.Set;

import static com.graphhopper.routing.util.parsers.AbstractAccessParser.FERRIES;
import static com.graphhopper.routing.util.FerrySpeedCalculator.FERRIES;

public abstract class AbstractAverageSpeedParser implements TagParser {
// http://wiki.openstreetmap.org/wiki/Mapfeatures#Barrier
protected final DecimalEncodedValue avgSpeedEnc;
protected final Set<String> ferries = new HashSet<>(FERRIES);
protected final FerrySpeedCalculator ferrySpeedCalc;
protected final DecimalEncodedValue ferrySpeedEnc;

protected AbstractAverageSpeedParser(DecimalEncodedValue speedEnc) {
protected AbstractAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeedEnc) {
this.avgSpeedEnc = speedEnc;
this.ferrySpeedCalc = new FerrySpeedCalculator(speedEnc.getSmallestNonZeroValue(), speedEnc.getMaxStorableDecimal(), 6);
this.ferrySpeedEnc = ferrySpeedEnc;
}

/**
Expand Down
Expand Up @@ -7,11 +7,12 @@ public class BikeAverageSpeedParser extends BikeCommonAverageSpeedParser {

public BikeAverageSpeedParser(EncodedValueLookup lookup, PMap properties) {
this(lookup.getDecimalEncodedValue(VehicleSpeed.key(properties.getString("name", "bike"))),
lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class));
lookup.getEnumEncodedValue(Smoothness.KEY, Smoothness.class),
lookup.getDecimalEncodedValue(FerrySpeed.KEY));
}

public BikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue<Smoothness> smoothnessEnc) {
super(speedEnc, smoothnessEnc);
public BikeAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue<Smoothness> smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) {
super(speedEnc, smoothnessEnc, ferrySpeedEnc);
addPushingSection("path");
}
}
Expand Up @@ -8,6 +8,8 @@

import java.util.*;

import static com.graphhopper.routing.util.FerrySpeedCalculator.FERRIES;

public abstract class BikeCommonAccessParser extends AbstractAccessParser implements TagParser {

private static final Set<String> OPP_LANES = new HashSet<>(Arrays.asList("opposite", "opposite_lane", "opposite_track"));
Expand Down Expand Up @@ -41,7 +43,7 @@ public WayAccess getAccess(ReaderWay way) {
if (highwayValue == null) {
WayAccess access = WayAccess.CAN_SKIP;

if (way.hasTag("route", ferries)) {
if (way.hasTag("route", FERRIES)) {
// if bike is NOT explicitly tagged allow bike but only if foot is not specified either
String bikeTag = way.getTag("bicycle");
if (bikeTag == null && !way.hasTag("foot") || intendedValues.contains(bikeTag))
Expand Down
Expand Up @@ -5,6 +5,7 @@
import com.graphhopper.routing.ev.EdgeIntAccess;
import com.graphhopper.routing.ev.EnumEncodedValue;
import com.graphhopper.routing.ev.Smoothness;
import com.graphhopper.routing.util.FerrySpeedCalculator;
import com.graphhopper.util.Helper;

import java.util.HashMap;
Expand All @@ -25,8 +26,8 @@ public abstract class BikeCommonAverageSpeedParser extends AbstractAverageSpeedP
private final EnumEncodedValue<Smoothness> smoothnessEnc;
protected final Set<String> intendedValues = new HashSet<>(5);

protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue<Smoothness> smoothnessEnc) {
super(speedEnc);
protected BikeCommonAverageSpeedParser(DecimalEncodedValue speedEnc, EnumEncodedValue<Smoothness> smoothnessEnc, DecimalEncodedValue ferrySpeedEnc) {
super(speedEnc, ferrySpeedEnc);
this.smoothnessEnc = smoothnessEnc;

// duplicate code as also in BikeCommonPriorityParser
Expand Down Expand Up @@ -133,7 +134,7 @@ public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way
String highwayValue = way.getTag("highway");
if (highwayValue == null) {
if (way.hasTag("route", ferries)) {
double ferrySpeed = ferrySpeedCalc.getSpeed(way);
double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc);
setSpeed(false, edgeId, edgeIntAccess, ferrySpeed);
if (avgSpeedEnc.isStoreTwoDirections())
setSpeed(true, edgeId, edgeIntAccess, ferrySpeed);
Expand Down
Expand Up @@ -11,8 +11,8 @@
import java.util.*;

import static com.graphhopper.routing.ev.RouteNetwork.*;
import static com.graphhopper.routing.util.FerrySpeedCalculator.FERRIES;
import static com.graphhopper.routing.util.PriorityCode.*;
import static com.graphhopper.routing.util.parsers.AbstractAccessParser.FERRIES;
import static com.graphhopper.routing.util.parsers.AbstractAccessParser.INTENDED;
import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.getMaxSpeed;
import static com.graphhopper.routing.util.parsers.AbstractAverageSpeedParser.isValidSpeed;
Expand Down Expand Up @@ -180,7 +180,7 @@ void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, PriorityCode> w
weightToPrioMap.put(50d, priorityCode == null ? AVOID : priorityCode);
if (way.hasTag("tunnel", intendedValues)) {
PriorityCode worse = priorityCode == null ? BAD : priorityCode.worse().worse();
weightToPrioMap.put(50d, worse == EXCLUDE ? REACH_DESTINATION : worse);
weightToPrioMap.put(50d, worse == EXCLUDE ? REACH_DESTINATION : worse);
}
}

Expand All @@ -199,14 +199,14 @@ void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, PriorityCode> w
PriorityCode pushingSectionPrio = SLIGHT_AVOID;
if (way.hasTag("bicycle", "yes") || way.hasTag("bicycle", "permissive"))
pushingSectionPrio = PREFER;
if (isDesignated(way) && (!way.hasTag("highway","steps")))
if (isDesignated(way) && (!way.hasTag("highway", "steps")))
pushingSectionPrio = VERY_NICE;
if (way.hasTag("foot", "yes")) {
pushingSectionPrio = pushingSectionPrio.worse();
if (way.hasTag("segregated", "yes"))
pushingSectionPrio = pushingSectionPrio.better();
}
if (way.hasTag("highway","steps")) {
if (way.hasTag("highway", "steps")) {
pushingSectionPrio = BAD;
}
weightToPrioMap.put(100d, pushingSectionPrio);
Expand All @@ -231,7 +231,8 @@ void collect(ReaderWay way, double wayTypeSpeed, TreeMap<Double, PriorityCode> w
// Increase the priority for scenic routes or in case that maxspeed limits our average speed as compensation. See #630
if (way.hasTag("scenic", "yes") || maxSpeed > 0 && maxSpeed <= wayTypeSpeed) {
PriorityCode lastEntryValue = weightToPrioMap.lastEntry().getValue();
if (lastEntryValue.getValue() < BEST.getValue()) weightToPrioMap.put(110d, lastEntryValue.better());
if (lastEntryValue.getValue() < BEST.getValue())
weightToPrioMap.put(110d, lastEntryValue.better());
}
}

Expand Down
Expand Up @@ -25,6 +25,8 @@

import java.util.*;

import static com.graphhopper.routing.util.FerrySpeedCalculator.FERRIES;

public class CarAccessParser extends AbstractAccessParser implements TagParser {

protected final Set<String> trackTypeValues = new HashSet<>();
Expand Down Expand Up @@ -80,7 +82,7 @@ public WayAccess getAccess(ReaderWay way) {
String highwayValue = way.getTag("highway");
String firstValue = way.getFirstPriorityTag(restrictions);
if (highwayValue == null) {
if (way.hasTag("route", ferries)) {
if (way.hasTag("route", FERRIES)) {
if (restrictedValues.contains(firstValue))
return WayAccess.CAN_SKIP;
if (intendedValues.contains(firstValue) ||
Expand Down
Expand Up @@ -18,10 +18,8 @@
package com.graphhopper.routing.util.parsers;

import com.graphhopper.reader.ReaderWay;
import com.graphhopper.routing.ev.DecimalEncodedValue;
import com.graphhopper.routing.ev.EdgeIntAccess;
import com.graphhopper.routing.ev.EncodedValueLookup;
import com.graphhopper.routing.ev.VehicleSpeed;
import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.util.FerrySpeedCalculator;
import com.graphhopper.util.Helper;
import com.graphhopper.util.PMap;

Expand All @@ -45,11 +43,12 @@ public class CarAverageSpeedParser extends AbstractAverageSpeedParser implements
protected final Map<String, Integer> defaultSpeedMap = new HashMap<>();

public CarAverageSpeedParser(EncodedValueLookup lookup, PMap properties) {
this(lookup.getDecimalEncodedValue(VehicleSpeed.key(properties.getString("name", "car"))));
this(lookup.getDecimalEncodedValue(VehicleSpeed.key(properties.getString("name", "car"))),
lookup.getDecimalEncodedValue(FerrySpeed.KEY));
}

public CarAverageSpeedParser(DecimalEncodedValue speedEnc) {
super(speedEnc);
public CarAverageSpeedParser(DecimalEncodedValue speedEnc, DecimalEncodedValue ferrySpeed) {
super(speedEnc, ferrySpeed);

badSurfaceSpeedMap.add("cobblestone");
badSurfaceSpeedMap.add("unhewn_cobblestone");
Expand Down Expand Up @@ -124,7 +123,7 @@ protected double getSpeed(ReaderWay way) {
@Override
public void handleWayTags(int edgeId, EdgeIntAccess edgeIntAccess, ReaderWay way) {
if (way.hasTag("route", ferries)) {
double ferrySpeed = ferrySpeedCalc.getSpeed(way);
double ferrySpeed = FerrySpeedCalculator.minmax(ferrySpeedEnc.getDecimal(false, edgeId, edgeIntAccess), avgSpeedEnc);
setSpeed(false, edgeId, edgeIntAccess, ferrySpeed);
if (avgSpeedEnc.isStoreTwoDirections())
setSpeed(true, edgeId, edgeIntAccess, ferrySpeed);
Expand Down
Expand Up @@ -18,6 +18,7 @@
package com.graphhopper.routing.util.parsers;

import com.graphhopper.routing.ev.*;
import com.graphhopper.routing.util.FerrySpeedCalculator;
import com.graphhopper.routing.util.TransportationMode;
import com.graphhopper.util.PMap;

Expand Down Expand Up @@ -83,6 +84,8 @@ else if (name.equals(State.KEY))
return new StateParser(lookup.getEnumEncodedValue(State.KEY, State.class));
else if (name.equals(Crossing.KEY))
return new OSMCrossingParser(lookup.getEnumEncodedValue(Crossing.KEY, Crossing.class));
else if (name.equals(FerrySpeed.KEY))
return new FerrySpeedCalculator(lookup.getDecimalEncodedValue(FerrySpeed.KEY));
return null;
}
}
Expand Up @@ -26,6 +26,7 @@
import java.util.*;

import static com.graphhopper.routing.ev.RouteNetwork.*;
import static com.graphhopper.routing.util.FerrySpeedCalculator.FERRIES;
import static com.graphhopper.routing.util.PriorityCode.UNCHANGED;

public class FootAccessParser extends AbstractAccessParser implements TagParser {
Expand Down Expand Up @@ -97,7 +98,7 @@ public WayAccess getAccess(ReaderWay way) {
if (highwayValue == null) {
WayAccess acceptPotentially = WayAccess.CAN_SKIP;

if (way.hasTag("route", ferries)) {
if (way.hasTag("route", FERRIES)) {
String footTag = way.getTag("foot");
if (footTag == null || intendedValues.contains(footTag))
acceptPotentially = WayAccess.FERRY;
Expand Down