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

Merged
merged 17 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,35 @@ public void buildEscalatorEdge(OsmWay escalatorWay, double length) {
.boxed()
.toList();

Integer duration = escalatorWay.getDurationSeconds();
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
);
} else if (escalatorWay.isBackwardEscalator()) {
EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i + 1)),
intersectionNodes.get(nodes.get(i)),
length
length,
duration
);
} else {
EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i)),
intersectionNodes.get(nodes.get(i + 1)),
length
length,
duration
);

EscalatorEdge.createEscalatorEdge(
intersectionNodes.get(nodes.get(i + 1)),
intersectionNodes.get(nodes.get(i)),
length
length,
duration
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import gnu.trove.list.TLongList;
import gnu.trove.list.array.TLongArrayList;
import java.time.format.DateTimeParseException;
import java.util.Set;
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 +132,23 @@ public boolean isEscalator() {
return (isTag("highway", "steps") && isOneOfTags("conveying", ESCALATOR_CONVEYING_TAGS));
}

public Integer getDurationSeconds() {
tkalvas marked this conversation as resolved.
Show resolved Hide resolved
var duration = getTag("duration");
if (duration != null) {
try {
long seconds = DurationUtils.parseClockDuration(duration).getSeconds();
if (seconds < 0 || seconds > Integer.MAX_VALUE) {
return null;
}
return (int) seconds;
} catch (DateTimeParseException e) {
// For malformed duration tags, just pretend they weren't there.
return null;
}
}
return null;
}

public boolean isForwardEscalator() {
return isEscalator() && "forward".equals(this.getTag("conveying"));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.opentripplanner.routing.api.request.preference;

import java.io.Serializable;
import java.util.function.Consumer;

public class EscalatorPreferences implements Serializable {

public static final EscalatorPreferences DEFAULT = new EscalatorPreferences();

private final double horizontalSpeed;

/* A quick internet search gives escalator speed range of 0.3-0.6 m/s and angle of 30 degrees.
* Using the angle of 30 degrees and a speed of 0.5 m/s gives a horizontal component
* of approx. 0.43 m/s */
private static final double HORIZONTAL_SPEED = 0.45;

private EscalatorPreferences() {
this.horizontalSpeed = HORIZONTAL_SPEED;
}

private EscalatorPreferences(Builder builder) {
this.horizontalSpeed = builder.horizontalSpeed;
}

public static Builder of() {
return DEFAULT.copyOf();
}

public Builder copyOf() {
return new Builder(this);
}

public double horizontalSpeed() {
tkalvas marked this conversation as resolved.
Show resolved Hide resolved
return horizontalSpeed;
}

@Override
public boolean equals(Object o) {
tkalvas marked this conversation as resolved.
Show resolved Hide resolved
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EscalatorPreferences that = (EscalatorPreferences) o;
return horizontalSpeed == that.horizontalSpeed;
}

public static class Builder {

private final EscalatorPreferences original;
private double horizontalSpeed;

public Builder(EscalatorPreferences original) {
this.original = original;
this.horizontalSpeed = original.horizontalSpeed;
}

public EscalatorPreferences original() {
return original;
}

public Builder withHorizontalSpeed(double horizontalSpeed) {
this.horizontalSpeed = horizontalSpeed;
return this;
}

public Builder apply(Consumer<Builder> body) {
body.accept(this);
return this;
}

public EscalatorPreferences build() {
var value = new EscalatorPreferences(this);
return original.equals(value) ? original : value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public final class StreetPreferences implements Serializable {
private final double turnReluctance;
private final DrivingDirection drivingDirection;
private final ElevatorPreferences elevator;
private final EscalatorPreferences escalator;
private final AccessEgressPreferences accessEgress;
private final IntersectionTraversalModel intersectionTraversalModel;
private final DurationForEnum<StreetMode> maxDirectDuration;
Expand All @@ -39,6 +40,7 @@ private StreetPreferences() {
this.turnReluctance = 1.0;
this.drivingDirection = DrivingDirection.RIGHT;
this.elevator = ElevatorPreferences.DEFAULT;
this.escalator = EscalatorPreferences.DEFAULT;
this.accessEgress = AccessEgressPreferences.DEFAULT;
this.intersectionTraversalModel = IntersectionTraversalModel.SIMPLE;
this.maxDirectDuration = durationForStreetModeOf(ofHours(4));
Expand All @@ -49,6 +51,7 @@ private StreetPreferences(Builder builder) {
this.turnReluctance = Units.reluctance(builder.turnReluctance);
this.drivingDirection = requireNonNull(builder.drivingDirection);
this.elevator = requireNonNull(builder.elevator);
this.escalator = requireNonNull(builder.escalator);
this.accessEgress = requireNonNull(builder.accessEgress);
this.intersectionTraversalModel = requireNonNull(builder.intersectionTraversalModel);
this.maxDirectDuration = requireNonNull(builder.maxDirectDuration);
Expand Down Expand Up @@ -78,6 +81,10 @@ public ElevatorPreferences elevator() {
return elevator;
}

public EscalatorPreferences escalator() {
return escalator;
}

/** Preferences for access/egress routing */
public AccessEgressPreferences accessEgress() {
return accessEgress;
Expand Down Expand Up @@ -110,6 +117,7 @@ public boolean equals(Object o) {
DoubleUtils.doubleEquals(that.turnReluctance, turnReluctance) &&
drivingDirection == that.drivingDirection &&
elevator.equals(that.elevator) &&
escalator.equals(that.escalator) &&
routingTimeout.equals(that.routingTimeout) &&
intersectionTraversalModel == that.intersectionTraversalModel &&
maxDirectDuration.equals(that.maxDirectDuration) &&
Expand Down Expand Up @@ -138,6 +146,7 @@ public String toString() {
.addEnum("drivingDirection", drivingDirection, DEFAULT.drivingDirection)
.addDuration("routingTimeout", routingTimeout, DEFAULT.routingTimeout())
.addObj("elevator", elevator, DEFAULT.elevator)
.addObj("escalator", escalator, DEFAULT.escalator)
.addObj(
"intersectionTraversalModel",
intersectionTraversalModel,
Expand All @@ -154,6 +163,7 @@ public static class Builder {
private double turnReluctance;
private DrivingDirection drivingDirection;
private ElevatorPreferences elevator;
private EscalatorPreferences escalator;
private IntersectionTraversalModel intersectionTraversalModel;
private DurationForEnum<StreetMode> maxDirectDuration;
private Duration routingTimeout;
Expand All @@ -164,6 +174,7 @@ public Builder(StreetPreferences original) {
this.turnReluctance = original.turnReluctance;
this.drivingDirection = original.drivingDirection;
this.elevator = original.elevator;
this.escalator = original.escalator;
this.intersectionTraversalModel = original.intersectionTraversalModel;
this.accessEgress = original.accessEgress;
this.maxDirectDuration = original.maxDirectDuration;
Expand All @@ -189,6 +200,11 @@ public Builder withElevator(Consumer<ElevatorPreferences.Builder> body) {
return this;
}

public Builder withEscalator(Consumer<EscalatorPreferences.Builder> body) {
this.escalator = escalator.copyOf().apply(body).build();
return this;
}

public Builder withAccessEgress(Consumer<AccessEgressPreferences.Builder> body) {
this.accessEgress = accessEgress.copyOf().apply(body).build();
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 @@ -459,6 +460,16 @@ private static void mapStreetPreferences(NodeAdapter c, StreetPreferences.Builde
.asInt(dftElevator.hopTime())
);
})
.withEscalator(escalator -> {
Copy link
Member

Choose a reason for hiding this comment

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

var dftEscalator = dft.escalator();
escalator.withHorizontalSpeed(
c
.of("escalatorSpeed")
tkalvas marked this conversation as resolved.
Show resolved Hide resolved
.since(V2_7)
.summary("How fast does an escalator move horizontally?")
.asDouble(dftEscalator.horizontalSpeed())
);
})
.withAccessEgress(accessEgress -> {
var dftAccessEgress = dft.accessEgress();
accessEgress
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,24 +11,27 @@
/** Represents an escalator. An escalator edge can only be traversed by walking */
public class EscalatorEdge extends Edge {

/* A quick internet search gives escalator speed range of 0.3-0.6 m/s and angle of 30 degrees.
* Using the angle of 30 degrees and a speed of 0.5 m/s gives a horizontal component
* of approx. 0.43 m/s */
private static final double HORIZONTAL_SPEED = 0.45;
private static final LocalizedString NAME = new LocalizedString("name.escalator");
private final double length;
private final Integer duration;

private EscalatorEdge(Vertex v1, Vertex v2, double length) {
private EscalatorEdge(Vertex v1, Vertex v2, double length, Integer duration) {
super(v1, v2);
this.length = length;
this.duration = duration;
}

@Override
public State[] traverse(State s0) {
// Only allow traversal by walking
if (s0.currentMode() == TraverseMode.WALK && !s0.getRequest().wheelchair()) {
var s1 = s0.edit(this);
var time = getDistanceMeters() / HORIZONTAL_SPEED;
double time;
if (duration == null) {
time = getDistanceMeters() / s0.getPreferences().street().escalator().horizontalSpeed();
} else {
time = duration;
}
s1.incrementWeight(s0.getPreferences().walk().escalatorReluctance() * time);
s1.incrementTimeInSeconds((int) Math.round(time));
s1.incrementWalkDistance(getDistanceMeters());
Expand All @@ -51,7 +54,12 @@ public I18NString getName() {
return NAME;
}

public static EscalatorEdge createEscalatorEdge(Vertex from, Vertex to, double length) {
return connectToGraph(new EscalatorEdge(from, to, length));
public static EscalatorEdge createEscalatorEdge(
Vertex from,
Vertex to,
double length,
Integer duration
) {
return connectToGraph(new EscalatorEdge(from, to, length, duration));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,52 @@ static Stream<Arguments> args() {
@ParameterizedTest(name = "escalatorReluctance of {0} should lead to traversal costs of {1}")
@MethodSource("args")
void testWalking(double escalatorReluctance, double expectedWeight) {
var edge = EscalatorEdge.createEscalatorEdge(from, to, 45);
var edge = EscalatorEdge.createEscalatorEdge(from, to, 45, null);
var req = StreetSearchRequest
.of()
.withPreferences(p -> p.withWalk(w -> w.withEscalatorReluctance(escalatorReluctance)))
.withMode(StreetMode.WALK);

var res = edge.traverse(new State(from, req.build()))[0];
assertEquals(res.weight, expectedWeight);
assertEquals(res.getTimeDeltaSeconds(), 100);
assertEquals(expectedWeight, res.weight);
assertEquals(100, res.getTimeDeltaSeconds());
}

@Test
void testDuration() {
// If duration is given, length does not affect timeDeltaSeconds, only duration does.
var edge = EscalatorEdge.createEscalatorEdge(from, to, 45, 60);
var req = StreetSearchRequest.of().withMode(StreetMode.WALK);
var res = edge.traverse(new State(from, req.build()))[0];
assertEquals(60, res.getTimeDeltaSeconds());
}

@Test
void testCycling() {
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10);
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10, null);
var req = StreetSearchRequest.of().withMode(StreetMode.BIKE);
var res = edge.traverse(new State(from, req.build()));
assertThat(res).isEmpty();
}

@Test
void testWheelchair() {
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10);
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10, null);
var req = StreetSearchRequest.of().withMode(StreetMode.WALK).withWheelchair(true);
var res = edge.traverse(new State(from, req.build()));
assertThat(res).isEmpty();
}

@Test
void name() {
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10);
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10, null);
assertEquals("Rolltreppe", edge.getName().toString(Locale.GERMANY));
assertEquals("escalator", edge.getName().toString(Locale.ENGLISH));
}

@Test
void geometry() {
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10);
var edge = EscalatorEdge.createEscalatorEdge(from, to, 10, null);
assertThat(edge.getGeometry().getCoordinates()).isNotEmpty();
}
}
1 change: 1 addition & 0 deletions doc/user/RouteRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ and in the [transferRequests in build-config.json](BuildConfiguration.md#transfe
| elevatorBoardTime | `integer` | How long does it take to get on an elevator, on average. | *Optional* | `90` | 2.0 |
| elevatorHopCost | `integer` | What is the cost of travelling one floor on an elevator? | *Optional* | `20` | 2.0 |
| elevatorHopTime | `integer` | How long does it take to advance one floor on an elevator? | *Optional* | `20` | 2.0 |
| escalatorSpeed | `double` | How fast does an escalator move horizontally? | *Optional* | `0.45` | 2.7 |
| geoidElevation | `boolean` | If true, the Graph's ellipsoidToGeoidDifference is applied to all elevations returned by this query. | *Optional* | `false` | 2.0 |
| ignoreRealtimeUpdates | `boolean` | When true, real-time updates are ignored during this search. | *Optional* | `false` | 2.0 |
| [intersectionTraversalModel](#rd_intersectionTraversalModel) | `enum` | The model that computes the costs of turns. | *Optional* | `"simple"` | 2.2 |
Expand Down
Loading
Loading