Skip to content

Commit 9de1b00

Browse files
committed
Merge branch 'upstream-dev-2.x' into zero-split-elevation-extension-nan-fix
2 parents 16d2ec1 + aa90f80 commit 9de1b00

326 files changed

Lines changed: 11126 additions & 8765 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

application/pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@
288288
<dependency>
289289
<groupId>org.onebusaway</groupId>
290290
<artifactId>onebusaway-gtfs</artifactId>
291-
<version>11.2.2</version>
291+
<version>12.0.1</version>
292292
</dependency>
293293
<!-- Used in DegreeGridNEDTileSource to fetch tiles from Amazon S3 -->
294294
<dependency>
@@ -357,7 +357,7 @@
357357
<dependency>
358358
<groupId>com.azure</groupId>
359359
<artifactId>azure-messaging-servicebus</artifactId>
360-
<version>7.17.17</version>
360+
<version>7.17.18</version>
361361
</dependency>
362362
<dependency>
363363
<groupId>com.azure</groupId>
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package org.opentripplanner.ext.carpooling.routing;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
import static org.opentripplanner.ext.carpooling.CarpoolGraphPathBuilder.createGraphPath;
6+
import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_CENTER;
7+
import static org.opentripplanner.ext.carpooling.CarpoolTestCoordinates.OSLO_NORTH;
8+
import static org.opentripplanner.ext.carpooling.CarpoolTripTestData.createSimpleTrip;
9+
10+
import java.time.Duration;
11+
import java.util.List;
12+
import javax.annotation.Nullable;
13+
import org.junit.jupiter.api.Test;
14+
import org.opentripplanner.astar.model.GraphPath;
15+
import org.opentripplanner.core.model.basic.Cost;
16+
import org.opentripplanner.framework.model.TimeAndCost;
17+
import org.opentripplanner.raptor.spi.RaptorConstants;
18+
import org.opentripplanner.raptor.spi.RaptorCostConverter;
19+
import org.opentripplanner.street.model.edge.Edge;
20+
import org.opentripplanner.street.model.vertex.Vertex;
21+
import org.opentripplanner.street.search.state.State;
22+
23+
class CarpoolAccessEgressTest {
24+
25+
private static final int STOP = 0;
26+
private static final Duration STOP_DURATION = Duration.ofMinutes(2);
27+
private static final int DWELL_SECONDS = (int) STOP_DURATION.getSeconds();
28+
private static final int PICKUP_POSITION = 1;
29+
private static final int DROPOFF_POSITION = 2;
30+
private static final Duration PICKUP_SEGMENT_DURATION = Duration.ofMinutes(1);
31+
32+
/**
33+
* Walk time must be billed at the walk path's own A* weight (which already encodes walk
34+
* reluctance, safety, slope, ...) and the carpool ride at {@code carpoolReluctance}. The ride
35+
* includes the boarding dwell at the pickup stop.
36+
*/
37+
@Test
38+
void c1AddsWalkPathWeightsAndChargesRideAtCarpoolReluctance() {
39+
var walkToPickup = createGraphPath(Duration.ofSeconds(80));
40+
var walkFromDropoff = createGraphPath(Duration.ofSeconds(40));
41+
double carpoolReluctance = 1.0;
42+
43+
var accessEgress = newAccessEgress(
44+
1_000,
45+
walkToPickup,
46+
Duration.ofSeconds(60),
47+
walkFromDropoff,
48+
carpoolReluctance
49+
);
50+
51+
int rideSeconds = 60 + DWELL_SECONDS;
52+
double expectedWeight =
53+
walkToPickup.getWeight() + walkFromDropoff.getWeight() + rideSeconds * carpoolReluctance;
54+
assertEquals(RaptorCostConverter.toRaptorCost(expectedWeight), accessEgress.c1());
55+
}
56+
57+
/** With no walk, the cost reduces to {@code (sharedSegmentSeconds + dwell) * carpoolReluctance}. */
58+
@Test
59+
void c1WithoutWalkUsesOnlyCarpoolReluctance() {
60+
var accessEgress = newAccessEgress(0, null, Duration.ofSeconds(300), null, 1.5);
61+
62+
int rideSeconds = 300 + DWELL_SECONDS;
63+
assertEquals(RaptorCostConverter.toRaptorCost(rideSeconds * 1.5), accessEgress.c1());
64+
}
65+
66+
@Test
67+
void durationInSecondsCoversWalksAndRide() {
68+
var accessEgress = newAccessEgress(
69+
1_000,
70+
createGraphPath(Duration.ofSeconds(80)),
71+
Duration.ofSeconds(60),
72+
createGraphPath(Duration.ofSeconds(40)),
73+
1.0
74+
);
75+
76+
int expectedDuration = 80 + 60 + DWELL_SECONDS + 40;
77+
assertEquals(expectedDuration, accessEgress.durationInSeconds());
78+
assertEquals(1_000, accessEgress.getPassengerDepartureTime());
79+
assertEquals(1_000 + expectedDuration, accessEgress.getPassengerArrivalTime());
80+
}
81+
82+
/**
83+
* {@code withPenalty} installs the new penalty and preserves the leg's stop and time anchors.
84+
* Mirroring {@code DefaultAccessEgress}, the penalty's cost is folded into {@link
85+
* CarpoolAccessEgress#c1()} (Raptor itself does not propagate the time-penalty into c1) and the
86+
* penalty's time is exposed via {@link CarpoolAccessEgress#timePenalty()}. The wall-clock
87+
* {@code durationInSeconds} is unchanged because the time-penalty is virtual time inside Raptor,
88+
* not part of the leg's actual duration.
89+
*/
90+
@Test
91+
void withPenaltyFoldsCostIntoC1AndExposesTimePenalty() {
92+
var original = newAccessEgress(
93+
1_000,
94+
createGraphPath(Duration.ofSeconds(80)),
95+
Duration.ofSeconds(60),
96+
createGraphPath(Duration.ofSeconds(40)),
97+
1.0
98+
);
99+
var newPenalty = new TimeAndCost(Duration.ofSeconds(30), Cost.costOfSeconds(45));
100+
101+
var withPenalty = (CarpoolAccessEgress) original.withPenalty(newPenalty);
102+
103+
assertEquals(newPenalty, withPenalty.penalty());
104+
assertEquals(original.stop(), withPenalty.stop());
105+
assertEquals(original.c1() + newPenalty.cost().toCentiSeconds(), withPenalty.c1());
106+
assertEquals((int) newPenalty.time().toSeconds(), withPenalty.timePenalty());
107+
assertEquals(original.durationInSeconds(), withPenalty.durationInSeconds());
108+
assertEquals(original.getPassengerDepartureTime(), withPenalty.getPassengerDepartureTime());
109+
assertEquals(original.getPassengerArrivalTime(), withPenalty.getPassengerArrivalTime());
110+
}
111+
112+
/** Without a penalty, {@code timePenalty()} returns the Raptor sentinel {@code TIME_NOT_SET}. */
113+
@Test
114+
void timePenaltyDefaultsToTimeNotSet() {
115+
var subject = newAccessEgress(0, null, Duration.ofSeconds(300), null, 1.0);
116+
117+
assertEquals(RaptorConstants.TIME_NOT_SET, subject.timePenalty());
118+
}
119+
120+
/**
121+
* {@code withPenalty} is a one-shot decoration. Calling it on a leg that already has a non-zero
122+
* penalty would otherwise re-fold a cost into {@code c1} and silently discard the previous
123+
* penalty, so the second application throws — matching {@code DefaultAccessEgress}.
124+
*/
125+
@Test
126+
void canNotAddPenaltyTwice() {
127+
var subject = newAccessEgress(
128+
1_000,
129+
createGraphPath(Duration.ofSeconds(80)),
130+
Duration.ofSeconds(60),
131+
createGraphPath(Duration.ofSeconds(40)),
132+
1.0
133+
);
134+
var penalty = new TimeAndCost(Duration.ofSeconds(30), Cost.costOfSeconds(45));
135+
var withPenalty = subject.withPenalty(penalty);
136+
137+
assertThrows(IllegalStateException.class, () -> withPenalty.withPenalty(penalty));
138+
}
139+
140+
/**
141+
* Builds a CarpoolAccessEgress with the passenger picked up mid-trip (pickupPos = 1, dropoffPos
142+
* = 2). The route segments list is therefore [pickupSegment, sharedSegment]: the driver runs
143+
* the first to reach the passenger and the second is the passenger's shared ride. The
144+
* passenger's ride duration is {@code sharedSegmentDuration + STOP_DURATION} — the boarding
145+
* dwell at the pickup stop is part of the ride.
146+
*/
147+
private static CarpoolAccessEgress newAccessEgress(
148+
int passengerDepartureTime,
149+
@Nullable GraphPath<State, Edge, Vertex> walkToPickup,
150+
Duration sharedSegmentDuration,
151+
@Nullable GraphPath<State, Edge, Vertex> walkFromDropoff,
152+
double carpoolReluctance
153+
) {
154+
var candidate = new InsertionCandidate(
155+
createSimpleTrip(OSLO_CENTER, OSLO_NORTH),
156+
PICKUP_POSITION,
157+
DROPOFF_POSITION,
158+
List.of(createGraphPath(PICKUP_SEGMENT_DURATION), createGraphPath(sharedSegmentDuration)),
159+
STOP_DURATION,
160+
null,
161+
walkToPickup,
162+
walkFromDropoff
163+
);
164+
return new CarpoolAccessEgress(
165+
STOP,
166+
passengerDepartureTime,
167+
candidate,
168+
TimeAndCost.ZERO,
169+
carpoolReluctance
170+
);
171+
}
172+
}

0 commit comments

Comments
 (0)