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

Better escalator duration control: specific duration from OSM duration tag, default speed from build-config.json #6268

Open
wants to merge 11 commits into
base: dev-2.x
Choose a base branch
from
Open
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package org.opentripplanner.graph_builder.module.osm;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.graph_builder.issue.api.Issue;
import org.opentripplanner.osm.model.OsmWay;
import org.opentripplanner.street.model.edge.EscalatorEdge;
import org.opentripplanner.street.model.vertex.IntersectionVertex;
Expand All @@ -13,9 +17,19 @@
class EscalatorProcessor {

private final Map<Long, IntersectionVertex> intersectionNodes;
private final DataImportIssueStore issueStore;

public EscalatorProcessor(Map<Long, IntersectionVertex> intersectionNodes) {
// If an escalator is tagged as moving less than 5 cm/s, or more than 5 m/s,
// assume it's an error and ignore it.
private static final double SLOW_ESCALATOR_ERROR_CUTOFF = 0.05;
private static final double FAST_ESCALATOR_ERROR_CUTOFF = 5.0;

public EscalatorProcessor(
Map<Long, IntersectionVertex> intersectionNodes,
DataImportIssueStore issueStore
) {
this.intersectionNodes = intersectionNodes;
this.issueStore = issueStore;
}

public void buildEscalatorEdge(OsmWay escalatorWay, double length) {
Expand All @@ -27,30 +41,58 @@ public void buildEscalatorEdge(OsmWay escalatorWay, double length) {
.boxed()
.toList();

Optional<Duration> duration = escalatorWay.getDuration(v ->
issueStore.add(
Issue.issue(
"InvalidDuration",
"Duration for osm node %d is not a valid duration: '%s'; it's replaced with 'Optional.empty()' (unknown).",
escalatorWay.getId(),
v
)
)
);
if (duration.isPresent()) {
double speed = length / duration.get().toSeconds();
if (speed < SLOW_ESCALATOR_ERROR_CUTOFF || speed > FAST_ESCALATOR_ERROR_CUTOFF) {
duration = Optional.empty();
tkalvas marked this conversation as resolved.
Show resolved Hide resolved
issueStore.add(
Issue.issue(
"InvalidDuration",
"Duration for osm node {} makes implied speed {} be outside acceptable range.",
escalatorWay.getId(),
speed
)
);
}
}
for (int i = 0; i < nodes.size() - 1; i++) {
if (escalatorWay.isForwardEscalator()) {
EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i)),
intersectionNodes.get(nodes.get(i + 1)),
length
length,
duration.orElse(null)
);
} else if (escalatorWay.isBackwardEscalator()) {
EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i + 1)),
intersectionNodes.get(nodes.get(i)),
length
length,
duration.orElse(null)
);
} else {
EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i)),
intersectionNodes.get(nodes.get(i + 1)),
length
length,
duration.orElse(null)
);

EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i + 1)),
intersectionNodes.get(nodes.get(i)),
length
length,
duration.orElse(null)
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,10 @@ private void buildBasicGraph() {
long wayCount = osmdb.getWays().size();
ProgressTracker progress = ProgressTracker.track("Build street graph", 5_000, wayCount);
LOG.info(progress.startMessage());
var escalatorProcessor = new EscalatorProcessor(vertexGenerator.intersectionNodes());
var escalatorProcessor = new EscalatorProcessor(
vertexGenerator.intersectionNodes(),
issueStore
);

WAY:for (OsmWay way : osmdb.getWays()) {
WayProperties wayData = way.getOsmProvider().getWayPropertySet().getDataForWay(way);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ protected Collection<KeyValue> map(Edge input) {
List<KeyValue> properties =
switch (input) {
case StreetEdge e -> mapStreetEdge(e);
case EscalatorEdge e -> List.of(kv("distance", e.getDistanceMeters()));
case EscalatorEdge e -> List.of(
kv("distance", e.getDistanceMeters()),
kv("duration", e.getDuration())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the underlying library works with Optional or Duration. You will have to convert to a string.

);
default -> List.of();
};
return ListUtils.combine(baseProps, properties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,14 @@

import gnu.trove.list.TLongList;
import gnu.trove.list.array.TLongArrayList;
import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import org.opentripplanner.graph_builder.module.osm.StreetTraversalPermissionPair;
import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.utils.time.DurationUtils;

public class OsmWay extends OsmWithTags {

Expand Down Expand Up @@ -130,6 +135,10 @@ public boolean isEscalator() {
return (isTag("highway", "steps") && isOneOfTags("conveying", ESCALATOR_CONVEYING_TAGS));
}

public Optional<Duration> getDuration(Consumer<String> errorHandler) {
return getTagAsDuration("duration", errorHandler);
}

public boolean isForwardEscalator() {
return isEscalator() && "forward".equals(this.getTag("conveying"));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.opentripplanner.osm.model;

import java.time.Duration;
import java.time.format.DateTimeParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -221,6 +223,88 @@ public OptionalInt getTagAsInt(String tag, Consumer<String> errorHandler) {
return OptionalInt.empty();
}

/**
* Parse an OSM duration tag, which is one of:
* mm
* hh:mm
* hh:mm:ss
* and where the leading value is not limited to any maximum.
* @param duration string in format mm, hh:mm, or hh:mm:ss
* @return Duration
* @throws DateTimeParseException on bad input
*/
public static Duration parseOsmDuration(String duration) {
// Unfortunately DateFormatParserBuilder doesn't quite do enough for this case.
// It has the capability for expressing optional parts, so it could express hh(:mm(:ss)?)?
// but it cannot express (hh:)?mm(:ss)? where the existence of (:ss) implies the existence
// of (hh:). Even if it did, it would not be able to handle the cases where hours are
// greater than 23 or (if there is no hours part at all) minutes are greater than 59, which
// are both allowed by the spec and exist in OSM data. Durations are not LocalTimes after
// all, in parsing a LocalTime it makes sense and is correct that hours cannot be more than
// 23 or minutes more than 59, but in durations if you have capped the largest unit, it is
// reasonable for the amount of the largest unit to be as large as it needs to be.
int colonCount = (int) duration.chars().filter(ch -> ch == ':').count();
if (colonCount <= 2) {
try {
int i, j;
long hours, minutes, seconds;
// The first :-separated element can be any width, and has no maximum. It still has
// to be non-negative. The following elements must be 2 characters wide, non-negative,
// and less than 60.
switch (colonCount) {
case 0: // case "m"
minutes = Long.parseLong(duration);
if (minutes >= 0) {
return Duration.ofMinutes(minutes);
}
break;
case 1: // case "h:mm"
i = duration.indexOf(':');
hours = Long.parseLong(duration.substring(0, i));
minutes = Long.parseLong(duration.substring(i + 1));
if (duration.length() - i == 3 && hours >= 0 && minutes >= 0 && minutes < 60) {
return Duration.ofHours(hours).plusMinutes(minutes);
}
break;
default: // case "h:mm:ss"
//case 2:
i = duration.indexOf(':');
j = duration.indexOf(':', i + 1);
hours = Long.parseLong(duration.substring(0, i));
minutes = Long.parseLong(duration.substring(i + 1, j));
seconds = Long.parseLong(duration.substring(j + 1));
if (
j - i == 3 &&
duration.length() - j == 3 &&
hours >= 0 &&
minutes >= 0 &&
minutes < 60 &&
seconds >= 0 &&
seconds < 60
) {
return Duration.ofHours(hours).plusMinutes(minutes).plusSeconds(seconds);
}
break;
}
} catch (NumberFormatException e) {
// fallthrough
}
}
throw new DateTimeParseException("bad clock duration", duration, 0);
}

public Optional<Duration> getTagAsDuration(String tag, Consumer<String> errorHandler) {
String value = getTag(tag);
if (value != null) {
try {
return Optional.of(parseOsmDuration(value));
} catch (DateTimeParseException e) {
errorHandler.accept(value);
}
}
return Optional.empty();
}

/**
* Some tags are allowed to have values like 55, "true" or "false".
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public final class WalkPreferences implements Serializable {
private final double safetyFactor;

private final double escalatorReluctance;
private final double escalatorSpeed;

private WalkPreferences() {
this.speed = 1.33;
Expand All @@ -39,6 +40,7 @@ private WalkPreferences() {
this.stairsTimeFactor = 3.0;
this.safetyFactor = 1.0;
this.escalatorReluctance = 1.5;
this.escalatorSpeed = 0.45;
}

private WalkPreferences(Builder builder) {
Expand All @@ -49,6 +51,7 @@ private WalkPreferences(Builder builder) {
this.stairsTimeFactor = Units.reluctance(builder.stairsTimeFactor);
this.safetyFactor = Units.reluctance(builder.safetyFactor);
this.escalatorReluctance = Units.reluctance(builder.escalatorReluctance);
this.escalatorSpeed = Units.speed(builder.escalatorSpeed);
}

public static Builder of() {
Expand Down Expand Up @@ -108,6 +111,14 @@ public double safetyFactor() {
return safetyFactor;
}

public double escalatorReluctance() {
return escalatorReluctance;
}

public double escalatorSpeed() {
return escalatorSpeed;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand All @@ -120,7 +131,8 @@ public boolean equals(Object o) {
doubleEquals(that.stairsReluctance, stairsReluctance) &&
doubleEquals(that.stairsTimeFactor, stairsTimeFactor) &&
doubleEquals(that.safetyFactor, safetyFactor) &&
doubleEquals(that.escalatorReluctance, escalatorReluctance)
doubleEquals(that.escalatorReluctance, escalatorReluctance) &&
doubleEquals(that.escalatorSpeed, escalatorSpeed)
);
}

Expand All @@ -133,7 +145,8 @@ public int hashCode() {
stairsReluctance,
stairsTimeFactor,
safetyFactor,
escalatorReluctance
escalatorReluctance,
escalatorSpeed
);
}

Expand All @@ -148,13 +161,10 @@ public String toString() {
.addNum("stairsTimeFactor", stairsTimeFactor, DEFAULT.stairsTimeFactor)
.addNum("safetyFactor", safetyFactor, DEFAULT.safetyFactor)
.addNum("escalatorReluctance", escalatorReluctance, DEFAULT.escalatorReluctance)
.addNum("escalatorSpeed", escalatorSpeed, DEFAULT.escalatorSpeed)
.toString();
}

public double escalatorReluctance() {
return escalatorReluctance;
}

public static class Builder {

private final WalkPreferences original;
Expand All @@ -166,6 +176,7 @@ public static class Builder {
private double safetyFactor;

private double escalatorReluctance;
private double escalatorSpeed;

public Builder(WalkPreferences original) {
this.original = original;
Expand All @@ -176,6 +187,7 @@ public Builder(WalkPreferences original) {
this.stairsTimeFactor = original.stairsTimeFactor;
this.safetyFactor = original.safetyFactor;
this.escalatorReluctance = original.escalatorReluctance;
this.escalatorSpeed = original.escalatorSpeed;
}

public WalkPreferences original() {
Expand Down Expand Up @@ -251,6 +263,15 @@ public Builder withEscalatorReluctance(double escalatorReluctance) {
return this;
}

public double escalatorSpeed() {
return escalatorSpeed;
}

public Builder withEscalatorSpeed(double escalatorSpeed) {
this.escalatorSpeed = escalatorSpeed;
return this;
}

public Builder apply(Consumer<Builder> body) {
body.accept(this);
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_4;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_5;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
import static org.opentripplanner.standalone.config.routerequest.ItineraryFiltersConfig.mapItineraryFilterParams;
import static org.opentripplanner.standalone.config.routerequest.TransferConfig.mapTransferPreferences;
import static org.opentripplanner.standalone.config.routerequest.TriangleOptimizationConfig.mapOptimizationTriangle;
Expand Down Expand Up @@ -817,6 +818,14 @@ private static void mapWalkPreferences(NodeAdapter root, WalkPreferences.Builder
"A multiplier for how bad being in an escalator is compared to being in transit for equal lengths of time"
)
.asDouble(dft.escalatorReluctance())
)
.withEscalatorSpeed(
c
.of("escalatorSpeed")
.since(V2_7)
.summary("How fast does an escalator move horizontally?")
.description("Horizontal speed of escalator in m/s.")
.asDouble(dft.escalatorSpeed())
);
}
}
Loading
Loading