diff --git a/application/src/client/index.html b/application/src/client/index.html
index b5df7bb65aa..e8872ab33cd 100644
--- a/application/src/client/index.html
+++ b/application/src/client/index.html
@@ -5,8 +5,8 @@
OTP Debug Client
-
-
+
+
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
index 50c2dd45340..b67ae85a434 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/FlexStopTimesForTest.java
@@ -1,12 +1,12 @@
package org.opentripplanner.ext.flex;
-import static org.opentripplanner.model.StopTime.MISSING_VALUE;
-
import org.opentripplanner._support.geometry.Polygons;
+import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.model.site.StopLocation;
+import org.opentripplanner.transit.model.timetable.Trip;
import org.opentripplanner.utils.time.TimeUtils;
public class FlexStopTimesForTest {
@@ -18,6 +18,8 @@ public class FlexStopTimesForTest {
.build();
private static final RegularStop REGULAR_STOP = TEST_MODEL.stop("stop").build();
+ private static final Trip TRIP = TimetableRepositoryForTest.trip("flex").build();
+
public static StopTime area(String startTime, String endTime) {
return area(AREA_STOP, endTime, startTime);
}
@@ -27,26 +29,74 @@ public static StopTime area(StopLocation areaStop, String endTime, String startT
stopTime.setStop(areaStop);
stopTime.setFlexWindowStart(TimeUtils.time(startTime));
stopTime.setFlexWindowEnd(TimeUtils.time(endTime));
+ stopTime.setTrip(TRIP);
return stopTime;
}
- public static StopTime regularArrival(String arrivalTime) {
- return regularStopTime(TimeUtils.time(arrivalTime), MISSING_VALUE);
+ /**
+ * Returns an invalid combination of a flex area and continuous stopping.
+ */
+ public static StopTime areaWithContinuousStopping(String time) {
+ var st = area(time, time);
+ st.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER);
+ st.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
}
- public static StopTime regularStopTime(String arrivalTime, String departureTime) {
- return regularStopTime(TimeUtils.time(arrivalTime), TimeUtils.time(departureTime));
+ /**
+ * Returns an invalid combination of a flex area and continuous pick up.
+ */
+ public static StopTime areaWithContinuousPickup(String time) {
+ var st = area(time, time);
+ st.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
}
- public static StopTime regularStopTime(int arrivalTime, int departureTime) {
+ /**
+ * Returns an invalid combination of a flex area and continuous drop off.
+ */
+ public static StopTime areaWithContinuousDropOff(String time) {
+ var st = area(time, time);
+ st.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
+ }
+
+ public static StopTime regularStop(String arrivalTime, String departureTime) {
+ return regularStop(TimeUtils.time(arrivalTime), TimeUtils.time(departureTime));
+ }
+
+ public static StopTime regularStop(String time) {
+ return regularStop(TimeUtils.time(time), TimeUtils.time(time));
+ }
+
+ public static StopTime regularStopWithContinuousStopping(String time) {
+ var st = regularStop(TimeUtils.time(time), TimeUtils.time(time));
+ st.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER);
+ st.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
+ }
+
+ public static StopTime regularStopWithContinuousPickup(String time) {
+ var st = regularStop(TimeUtils.time(time), TimeUtils.time(time));
+ st.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
+ }
+
+ public static StopTime regularStopWithContinuousDropOff(String time) {
+ var st = regularStop(TimeUtils.time(time), TimeUtils.time(time));
+ st.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER);
+ return st;
+ }
+
+ public static StopTime regularStop(int arrivalTime, int departureTime) {
var stopTime = new StopTime();
stopTime.setStop(REGULAR_STOP);
stopTime.setArrivalTime(arrivalTime);
stopTime.setDepartureTime(departureTime);
+ stopTime.setTrip(TRIP);
return stopTime;
}
- public static StopTime regularDeparture(String departureTime) {
- return regularStopTime(MISSING_VALUE, TimeUtils.time(departureTime));
- }
+
+
}
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
index e6fddb07cf8..23827b9503b 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/flexpathcalculator/ScheduledFlexPathCalculatorTest.java
@@ -2,7 +2,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area;
-import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopTime;
+import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop;
import static org.opentripplanner.street.model._data.StreetModelForTest.V1;
import static org.opentripplanner.street.model._data.StreetModelForTest.V2;
import static org.opentripplanner.transit.model._data.TimetableRepositoryForTest.id;
@@ -19,9 +19,9 @@ class ScheduledFlexPathCalculatorTest {
.of(id("123"))
.withStopTimes(
List.of(
- regularStopTime("10:00", "10:01"),
+ regularStop("10:00", "10:01"),
area("10:10", "10:20"),
- regularStopTime("10:25", "10:26"),
+ regularStop("10:25", "10:26"),
area("10:40", "10:50")
)
)
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java
new file mode 100644
index 00000000000..3a8e33cfbf2
--- /dev/null
+++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripIntegrationTest.java
@@ -0,0 +1,252 @@
+package org.opentripplanner.ext.flex.trip;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.opentripplanner.test.support.PolylineAssert.assertThatPolylinesAreEqual;
+
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.locationtech.jts.geom.Coordinate;
+import org.opentripplanner.TestOtpModel;
+import org.opentripplanner.TestServerContext;
+import org.opentripplanner._support.time.ZoneIds;
+import org.opentripplanner.ext.fares.DecorateWithFare;
+import org.opentripplanner.ext.flex.FlexIntegrationTestData;
+import org.opentripplanner.ext.flex.FlexParameters;
+import org.opentripplanner.ext.flex.FlexRouter;
+import org.opentripplanner.framework.application.OTPFeature;
+import org.opentripplanner.framework.geometry.EncodedPolyline;
+import org.opentripplanner.framework.i18n.I18NString;
+import org.opentripplanner.graph_builder.module.ValidateAndInterpolateStopTimesForEachTrip;
+import org.opentripplanner.model.GenericLocation;
+import org.opentripplanner.model.StopTime;
+import org.opentripplanner.model.plan.Itinerary;
+import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays;
+import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter;
+import org.opentripplanner.routing.api.request.RouteRequest;
+import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
+import org.opentripplanner.routing.framework.DebugTimingAggregator;
+import org.opentripplanner.routing.graph.Graph;
+import org.opentripplanner.routing.graphfinder.NearbyStop;
+import org.opentripplanner.standalone.api.OtpServerRequestContext;
+import org.opentripplanner.street.model.vertex.StreetLocation;
+import org.opentripplanner.street.search.request.StreetSearchRequest;
+import org.opentripplanner.street.search.state.State;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
+import org.opentripplanner.transit.model.site.AreaStop;
+import org.opentripplanner.transit.service.DefaultTransitService;
+import org.opentripplanner.transit.service.TimetableRepository;
+import org.opentripplanner.utils.time.ServiceDateUtils;
+
+/**
+ * This tests that the feed for the Cobb County Flex service is processed correctly. This service
+ * contains both flex zones but also scheduled stops. Inside the zone, passengers can get on or off
+ * anywhere, so there it works more like a taxi.
+ *
+ * This service is not being offered anymore, but we keep the test because others of the same
+ * type still exist.
+ */
+class ScheduledDeviatedTripIntegrationTest {
+
+ static Graph graph;
+ static TimetableRepository timetableRepository;
+
+ float delta = 0.01f;
+
+ @Test
+ void parseCobbCountyAsScheduledDeviatedTrip() {
+ var flexTrips = timetableRepository.getAllFlexTrips();
+ assertFalse(flexTrips.isEmpty());
+ assertEquals(72, flexTrips.size());
+
+ assertEquals(
+ Set.of(ScheduledDeviatedTrip.class),
+ flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet())
+ );
+
+ var trip = getFlexTrip();
+ var stop = trip
+ .getStops()
+ .stream()
+ .filter(s -> s.getId().getId().equals("cujv"))
+ .findFirst()
+ .orElseThrow();
+ assertEquals(33.85465, stop.getLat(), delta);
+ assertEquals(-84.60039, stop.getLon(), delta);
+
+ var flexZone = trip
+ .getStops()
+ .stream()
+ .filter(s -> s.getId().getId().equals("zone_3"))
+ .findFirst()
+ .orElseThrow();
+ assertEquals(33.825846635310214, flexZone.getLat(), delta);
+ assertEquals(-84.63430143459385, flexZone.getLon(), delta);
+ }
+
+ @Test
+ void calculateDirectFare() {
+ OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
+ var trip = getFlexTrip();
+
+ var from = getNearbyStop(trip, "from-stop");
+ var to = getNearbyStop(trip, "to-stop");
+
+ var router = new FlexRouter(
+ graph,
+ new DefaultTransitService(timetableRepository),
+ FlexParameters.defaultValues(),
+ OffsetDateTime.parse("2021-11-12T10:15:24-05:00").toInstant(),
+ null,
+ 1,
+ 1,
+ List.of(from),
+ List.of(to)
+ );
+
+ var filter = new DecorateWithFare(graph.getFareService());
+
+ var itineraries = router
+ .createFlexOnlyItineraries(false)
+ .stream()
+ .peek(filter::decorate)
+ .toList();
+
+ var itinerary = itineraries.getFirst();
+
+ assertFalse(itinerary.getFares().getLegProducts().isEmpty());
+
+ OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
+ }
+
+ /**
+ * Trips which consist of flex and fixed-schedule stops should work in transit mode.
+ *
+ * The flex stops will show up as intermediate stops (without a departure/arrival time) but you
+ * cannot board or alight.
+ */
+ @Test
+ void flexTripInTransitMode() {
+ var feedId = timetableRepository.getFeedIds().iterator().next();
+
+ var serverContext = TestServerContext.createServerContext(graph, timetableRepository);
+
+ // from zone 3 to zone 2
+ var from = GenericLocation.fromStopId("Transfer Point for Route 30", feedId, "cujv");
+ var to = GenericLocation.fromStopId(
+ "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point",
+ feedId,
+ "yz85"
+ );
+
+ var itineraries = getItineraries(from, to, serverContext);
+
+ assertEquals(2, itineraries.size());
+
+ var itin = itineraries.get(0);
+ var leg = itin.getLegs().get(0);
+
+ assertEquals("cujv", leg.getFrom().stop.getId().getId());
+ assertEquals("yz85", leg.getTo().stop.getId().getId());
+
+ var intermediateStops = leg.getIntermediateStops();
+ assertEquals(1, intermediateStops.size());
+ assertEquals("zone_1", intermediateStops.get(0).place.stop.getId().getId());
+
+ EncodedPolyline legGeometry = EncodedPolyline.encode(leg.getLegGeometry());
+ assertThatPolylinesAreEqual(
+ legGeometry.points(),
+ "kfsmEjojcOa@eBRKfBfHR|ALjBBhVArMG|OCrEGx@OhAKj@a@tAe@hA]l@MPgAnAgw@nr@cDxCm@t@c@t@c@x@_@~@]pAyAdIoAhG}@lE{AzHWhAtt@t~Aj@tAb@~AXdBHn@FlBC`CKnA_@nC{CjOa@dCOlAEz@E|BRtUCbCQ~CWjD??qBvXBl@kBvWOzAc@dDOx@sHv]aIG?q@@c@ZaB\\mA"
+ );
+ }
+
+ /**
+ * We add flex trips, that can potentially not have a departure and arrival time, to the trip.
+ *
+ * Normally these trip times are interpolated/repaired during the graph build but for flex this is
+ * exactly what we don't want. Here we check that the interpolation process is skipped.
+ *
+ * @see ValidateAndInterpolateStopTimesForEachTrip#interpolateStopTimes(List)
+ */
+ @Test
+ void shouldNotInterpolateFlexTimes() {
+ var feedId = timetableRepository.getFeedIds().iterator().next();
+ var pattern = timetableRepository.getTripPatternForId(new FeedScopedId(feedId, "090z:0:01"));
+
+ assertEquals(3, pattern.numberOfStops());
+
+ var tripTimes = pattern.getScheduledTimetable().getTripTimes(0);
+ var arrivalTime = tripTimes.getArrivalTime(1);
+
+ assertEquals(StopTime.MISSING_VALUE, arrivalTime);
+ }
+
+ @BeforeAll
+ static void setup() {
+ TestOtpModel model = FlexIntegrationTestData.cobbFlexGtfs();
+ graph = model.graph();
+ timetableRepository = model.timetableRepository();
+ }
+
+ private static List getItineraries(
+ GenericLocation from,
+ GenericLocation to,
+ OtpServerRequestContext serverContext
+ ) {
+ var zoneId = ZoneIds.NEW_YORK;
+ RouteRequest request = new RouteRequest();
+ request.journey().transit().setFilters(List.of(AllowAllTransitFilter.of()));
+ var dateTime = LocalDateTime.of(2021, Month.DECEMBER, 16, 12, 0).atZone(zoneId);
+ request.setDateTime(dateTime.toInstant());
+ request.setFrom(from);
+ request.setTo(to);
+
+ var transitStartOfTime = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId);
+ var additionalSearchDays = AdditionalSearchDays.defaults(dateTime);
+ var result = TransitRouter.route(
+ request,
+ serverContext,
+ TransitGroupPriorityService.empty(),
+ transitStartOfTime,
+ additionalSearchDays,
+ new DebugTimingAggregator()
+ );
+
+ return result.getItineraries();
+ }
+
+ private static NearbyStop getNearbyStop(FlexTrip, ?> trip, String id) {
+ // getStops() returns a set of stops and the order doesn't correspond to the stop times
+ // of the trip
+ var stopLocation = trip
+ .getStops()
+ .stream()
+ .filter(s -> s instanceof AreaStop)
+ .findFirst()
+ .orElseThrow();
+
+ return new NearbyStop(
+ stopLocation,
+ 0,
+ List.of(),
+ new State(
+ new StreetLocation(id, new Coordinate(0, 0), I18NString.of(id)),
+ StreetSearchRequest.of().build()
+ )
+ );
+ }
+
+ private static FlexTrip, ?> getFlexTrip() {
+ var feedId = timetableRepository.getFeedIds().iterator().next();
+ var tripId = new FeedScopedId(feedId, "a326c618-d42c-4bd1-9624-c314fbf8ecd8");
+ return timetableRepository.getFlexTrip(tripId);
+ }
+}
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
index 38f3a1fc2a8..4beefeb271e 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTripTest.java
@@ -1,255 +1,62 @@
package org.opentripplanner.ext.flex.trip;
-import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.opentripplanner.test.support.PolylineAssert.assertThatPolylinesAreEqual;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.opentripplanner.ext.flex.FlexStopTimesForTest.area;
+import static org.opentripplanner.ext.flex.FlexStopTimesForTest.areaWithContinuousStopping;
+import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStop;
+import static org.opentripplanner.ext.flex.FlexStopTimesForTest.regularStopWithContinuousStopping;
-import java.time.LocalDateTime;
-import java.time.Month;
-import java.time.OffsetDateTime;
import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.Collectors;
-import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.Test;
-import org.locationtech.jts.geom.Coordinate;
-import org.opentripplanner.TestOtpModel;
-import org.opentripplanner.TestServerContext;
-import org.opentripplanner._support.time.ZoneIds;
-import org.opentripplanner.ext.fares.DecorateWithFare;
-import org.opentripplanner.ext.flex.FlexIntegrationTestData;
-import org.opentripplanner.ext.flex.FlexParameters;
-import org.opentripplanner.ext.flex.FlexRouter;
-import org.opentripplanner.framework.application.OTPFeature;
-import org.opentripplanner.framework.geometry.EncodedPolyline;
-import org.opentripplanner.framework.i18n.I18NString;
-import org.opentripplanner.graph_builder.module.ValidateAndInterpolateStopTimesForEachTrip;
-import org.opentripplanner.model.GenericLocation;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
import org.opentripplanner.model.StopTime;
-import org.opentripplanner.model.plan.Itinerary;
-import org.opentripplanner.routing.algorithm.raptoradapter.router.AdditionalSearchDays;
-import org.opentripplanner.routing.algorithm.raptoradapter.router.TransitRouter;
-import org.opentripplanner.routing.api.request.RouteRequest;
-import org.opentripplanner.routing.api.request.request.filter.AllowAllTransitFilter;
-import org.opentripplanner.routing.framework.DebugTimingAggregator;
-import org.opentripplanner.routing.graph.Graph;
-import org.opentripplanner.routing.graphfinder.NearbyStop;
-import org.opentripplanner.standalone.api.OtpServerRequestContext;
-import org.opentripplanner.street.model.vertex.StreetLocation;
-import org.opentripplanner.street.search.request.StreetSearchRequest;
-import org.opentripplanner.street.search.state.State;
-import org.opentripplanner.transit.model.framework.FeedScopedId;
-import org.opentripplanner.transit.model.network.grouppriority.TransitGroupPriorityService;
-import org.opentripplanner.transit.model.site.AreaStop;
-import org.opentripplanner.transit.service.DefaultTransitService;
-import org.opentripplanner.transit.service.TimetableRepository;
-import org.opentripplanner.utils.time.ServiceDateUtils;
-/**
- * This tests that the feed for the Cobb County Flex service is processed correctly. This service
- * contains both flex zones but also scheduled stops. Inside the zone, passengers can get on or off
- * anywhere, so there it works more like a taxi.
- *
- * Read about the details at: https://www.cobbcounty.org/transportation/cobblinc/routes-and-schedules/flex
- */
class ScheduledDeviatedTripTest {
- static Graph graph;
- static TimetableRepository timetableRepository;
-
- float delta = 0.01f;
-
- @Test
- void parseCobbCountyAsScheduledDeviatedTrip() {
- var flexTrips = timetableRepository.getAllFlexTrips();
- assertFalse(flexTrips.isEmpty());
- assertEquals(72, flexTrips.size());
-
- assertEquals(
- Set.of(ScheduledDeviatedTrip.class),
- flexTrips.stream().map(FlexTrip::getClass).collect(Collectors.toSet())
- );
-
- var trip = getFlexTrip();
- var stop = trip
- .getStops()
- .stream()
- .filter(s -> s.getId().getId().equals("cujv"))
- .findFirst()
- .orElseThrow();
- assertEquals(33.85465, stop.getLat(), delta);
- assertEquals(-84.60039, stop.getLon(), delta);
-
- var flexZone = trip
- .getStops()
- .stream()
- .filter(s -> s.getId().getId().equals("zone_3"))
- .findFirst()
- .orElseThrow();
- assertEquals(33.825846635310214, flexZone.getLat(), delta);
- assertEquals(-84.63430143459385, flexZone.getLon(), delta);
- }
-
- @Test
- void calculateDirectFare() {
- OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, true));
- var trip = getFlexTrip();
-
- var from = getNearbyStop(trip, "from-stop");
- var to = getNearbyStop(trip, "to-stop");
-
- var router = new FlexRouter(
- graph,
- new DefaultTransitService(timetableRepository),
- FlexParameters.defaultValues(),
- OffsetDateTime.parse("2021-11-12T10:15:24-05:00").toInstant(),
- null,
- 1,
- 1,
- List.of(from),
- List.of(to)
- );
-
- var filter = new DecorateWithFare(graph.getFareService());
-
- var itineraries = router
- .createFlexOnlyItineraries(false)
- .stream()
- .peek(filter::decorate)
- .toList();
-
- var itinerary = itineraries.getFirst();
-
- assertFalse(itinerary.getFares().getLegProducts().isEmpty());
-
- OTPFeature.enableFeatures(Map.of(OTPFeature.FlexRouting, false));
- }
-
- /**
- * Trips which consist of flex and fixed-schedule stops should work in transit mode.
- *
- * The flex stops will show up as intermediate stops (without a departure/arrival time) but you
- * cannot board or alight.
- */
- @Test
- void flexTripInTransitMode() {
- var feedId = timetableRepository.getFeedIds().iterator().next();
-
- var serverContext = TestServerContext.createServerContext(graph, timetableRepository);
-
- // from zone 3 to zone 2
- var from = GenericLocation.fromStopId("Transfer Point for Route 30", feedId, "cujv");
- var to = GenericLocation.fromStopId(
- "Zone 1 - PUBLIX Super Market,Zone 1 Collection Point",
- feedId,
- "yz85"
- );
-
- var itineraries = getItineraries(from, to, serverContext);
-
- assertEquals(2, itineraries.size());
-
- var itin = itineraries.get(0);
- var leg = itin.getLegs().get(0);
-
- assertEquals("cujv", leg.getFrom().stop.getId().getId());
- assertEquals("yz85", leg.getTo().stop.getId().getId());
-
- var intermediateStops = leg.getIntermediateStops();
- assertEquals(1, intermediateStops.size());
- assertEquals("zone_1", intermediateStops.get(0).place.stop.getId().getId());
-
- EncodedPolyline legGeometry = EncodedPolyline.encode(leg.getLegGeometry());
- assertThatPolylinesAreEqual(
- legGeometry.points(),
- "kfsmEjojcOa@eBRKfBfHR|ALjBBhVArMG|OCrEGx@OhAKj@a@tAe@hA]l@MPgAnAgw@nr@cDxCm@t@c@t@c@x@_@~@]pAyAdIoAhG}@lE{AzHWhAtt@t~Aj@tAb@~AXdBHn@FlBC`CKnA_@nC{CjOa@dCOlAEz@E|BRtUCbCQ~CWjD??qBvXBl@kBvWOzAc@dDOx@sHv]aIG?q@@c@ZaB\\mA"
+ private static List> isScheduledDeviatedTripCases() {
+ return List.of(
+ List.of(
+ regularStop("10:10"),
+ area("10:20", "10:30"),
+ regularStop("10:40"),
+ area("10:50", "11:00")
+ ),
+ List.of(
+ regularStopWithContinuousStopping("10:10"),
+ area("10:20", "10:30"),
+ regularStopWithContinuousStopping("10:40"),
+ area("10:50", "11:00")
+ )
);
}
- /**
- * We add flex trips, that can potentially not have a departure and arrival time, to the trip.
- *
- * Normally these trip times are interpolated/repaired during the graph build but for flex this is
- * exactly what we don't want. Here we check that the interpolation process is skipped.
- *
- * @see ValidateAndInterpolateStopTimesForEachTrip#interpolateStopTimes(List)
- */
- @Test
- void shouldNotInterpolateFlexTimes() {
- var feedId = timetableRepository.getFeedIds().iterator().next();
- var pattern = timetableRepository.getTripPatternForId(new FeedScopedId(feedId, "090z:0:01"));
-
- assertEquals(3, pattern.numberOfStops());
-
- var tripTimes = pattern.getScheduledTimetable().getTripTimes(0);
- var arrivalTime = tripTimes.getArrivalTime(1);
-
- assertEquals(StopTime.MISSING_VALUE, arrivalTime);
- }
-
- @BeforeAll
- static void setup() {
- TestOtpModel model = FlexIntegrationTestData.cobbFlexGtfs();
- graph = model.graph();
- timetableRepository = model.timetableRepository();
+ @ParameterizedTest
+ @MethodSource("isScheduledDeviatedTripCases")
+ void isScheduledDeviatedTrip(List stopTimes) {
+ assertTrue(ScheduledDeviatedTrip.isScheduledDeviatedFlexTrip(stopTimes));
}
- private static List getItineraries(
- GenericLocation from,
- GenericLocation to,
- OtpServerRequestContext serverContext
- ) {
- var zoneId = ZoneIds.NEW_YORK;
- RouteRequest request = new RouteRequest();
- request.journey().transit().setFilters(List.of(AllowAllTransitFilter.of()));
- var dateTime = LocalDateTime.of(2021, Month.DECEMBER, 16, 12, 0).atZone(zoneId);
- request.setDateTime(dateTime.toInstant());
- request.setFrom(from);
- request.setTo(to);
-
- var transitStartOfTime = ServiceDateUtils.asStartOfService(request.dateTime(), zoneId);
- var additionalSearchDays = AdditionalSearchDays.defaults(dateTime);
- var result = TransitRouter.route(
- request,
- serverContext,
- TransitGroupPriorityService.empty(),
- transitStartOfTime,
- additionalSearchDays,
- new DebugTimingAggregator()
+ private static List> isNotScheduledDeviatedTripCases() {
+ return List.of(
+ List.of(
+ areaWithContinuousStopping("10:10"),
+ regularStop("10:20", "10:30"),
+ areaWithContinuousStopping("10:40"),
+ regularStop("10:50", "11:00")
+ ),
+ List.of(
+ regularStop("10:10"),
+ regularStop("10:20")
+ )
);
-
- return result.getItineraries();
}
- private static NearbyStop getNearbyStop(FlexTrip, ?> trip) {
- return getNearbyStop(trip, "nearby-stop");
+ @ParameterizedTest
+ @MethodSource("isNotScheduledDeviatedTripCases")
+ void isNotScheduledDeviatedTrip(List stopTimes) {
+ assertFalse(ScheduledDeviatedTrip.isScheduledDeviatedFlexTrip(stopTimes));
}
- private static NearbyStop getNearbyStop(FlexTrip, ?> trip, String id) {
- // getStops() returns a set of stops and the order doesn't correspond to the stop times
- // of the trip
- var stopLocation = trip
- .getStops()
- .stream()
- .filter(s -> s instanceof AreaStop)
- .findFirst()
- .orElseThrow();
- return new NearbyStop(
- stopLocation,
- 0,
- List.of(),
- new State(
- new StreetLocation(id, new Coordinate(0, 0), I18NString.of(id)),
- StreetSearchRequest.of().build()
- )
- );
- }
-
- private static FlexTrip, ?> getFlexTrip() {
- var feedId = timetableRepository.getFeedIds().iterator().next();
- var tripId = new FeedScopedId(feedId, "a326c618-d42c-4bd1-9624-c314fbf8ecd8");
- return timetableRepository.getFlexTrip(tripId);
- }
-}
+}
\ No newline at end of file
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java
index cfcbc123642..b7f2706d25a 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/flex/trip/UnscheduledTripTest.java
@@ -17,6 +17,7 @@
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
+import org.opentripplanner.ext.flex.FlexStopTimesForTest;
import org.opentripplanner.model.PickDrop;
import org.opentripplanner.model.StopTime;
import org.opentripplanner.transit.model._data.TimetableRepositoryForTest;
@@ -45,35 +46,14 @@ class UnscheduledTripTest {
@Nested
class IsUnscheduledTrip {
- private static final StopTime SCHEDULED_STOP = new StopTime();
- private static final StopTime UNSCHEDULED_STOP = new StopTime();
- private static final StopTime CONTINUOUS_PICKUP_STOP = new StopTime();
- private static final StopTime CONTINUOUS_DROP_OFF_STOP = new StopTime();
-
- static {
- var trip = TimetableRepositoryForTest.trip("flex").build();
- SCHEDULED_STOP.setArrivalTime(30);
- SCHEDULED_STOP.setDepartureTime(60);
- SCHEDULED_STOP.setStop(AREA_STOP);
- SCHEDULED_STOP.setTrip(trip);
-
- UNSCHEDULED_STOP.setFlexWindowStart(30);
- UNSCHEDULED_STOP.setFlexWindowEnd(300);
- UNSCHEDULED_STOP.setStop(AREA_STOP);
- UNSCHEDULED_STOP.setTrip(trip);
-
- CONTINUOUS_PICKUP_STOP.setFlexContinuousPickup(PickDrop.COORDINATE_WITH_DRIVER);
- CONTINUOUS_PICKUP_STOP.setFlexWindowStart(30);
- CONTINUOUS_PICKUP_STOP.setFlexWindowEnd(300);
- CONTINUOUS_PICKUP_STOP.setStop(AREA_STOP);
- CONTINUOUS_PICKUP_STOP.setTrip(trip);
-
- CONTINUOUS_DROP_OFF_STOP.setFlexContinuousDropOff(PickDrop.COORDINATE_WITH_DRIVER);
- CONTINUOUS_DROP_OFF_STOP.setFlexWindowStart(100);
- CONTINUOUS_DROP_OFF_STOP.setFlexWindowEnd(200);
- CONTINUOUS_DROP_OFF_STOP.setStop(AREA_STOP);
- CONTINUOUS_DROP_OFF_STOP.setTrip(trip);
- }
+ private static final StopTime SCHEDULED_STOP = FlexStopTimesForTest.regularStop("10:00");
+ private static final StopTime UNSCHEDULED_STOP = FlexStopTimesForTest.area("10:10", "10:20");
+ private static final StopTime CONTINUOUS_PICKUP_STOP = FlexStopTimesForTest.regularStopWithContinuousPickup("10:30");
+ private static final StopTime CONTINUOUS_DROP_OFF_STOP = FlexStopTimesForTest.regularStopWithContinuousDropOff("10:40");
+
+ // disallowed by the GTFS spec
+ private static final StopTime FLEX_AND_CONTINUOUS_PICKUP_STOP = FlexStopTimesForTest.areaWithContinuousPickup("10:50");
+ private static final StopTime FLEX_AND_CONTINUOUS_DROP_OFF_STOP = FlexStopTimesForTest.areaWithContinuousDropOff("11:00");
static List> notUnscheduled() {
return List.of(
@@ -82,9 +62,9 @@ static List> notUnscheduled() {
List.of(SCHEDULED_STOP, SCHEDULED_STOP),
List.of(SCHEDULED_STOP, SCHEDULED_STOP, SCHEDULED_STOP),
List.of(UNSCHEDULED_STOP, SCHEDULED_STOP, UNSCHEDULED_STOP),
- List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP),
- List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP),
- List.of(CONTINUOUS_PICKUP_STOP, CONTINUOUS_DROP_OFF_STOP)
+ List.of(UNSCHEDULED_STOP, FLEX_AND_CONTINUOUS_PICKUP_STOP),
+ List.of(UNSCHEDULED_STOP, FLEX_AND_CONTINUOUS_DROP_OFF_STOP),
+ List.of(FLEX_AND_CONTINUOUS_PICKUP_STOP, FLEX_AND_CONTINUOUS_DROP_OFF_STOP)
);
}
@@ -101,7 +81,11 @@ static List> unscheduled() {
List.of(SCHEDULED_STOP, UNSCHEDULED_STOP),
List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP),
List.of(UNSCHEDULED_STOP, UNSCHEDULED_STOP, UNSCHEDULED_STOP),
- Collections.nCopies(10, UNSCHEDULED_STOP)
+ Collections.nCopies(10, UNSCHEDULED_STOP),
+ List.of(UNSCHEDULED_STOP, CONTINUOUS_PICKUP_STOP),
+ List.of(CONTINUOUS_PICKUP_STOP, UNSCHEDULED_STOP),
+ List.of(UNSCHEDULED_STOP, CONTINUOUS_DROP_OFF_STOP),
+ List.of(CONTINUOUS_DROP_OFF_STOP, UNSCHEDULED_STOP)
);
}
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java
index 18d61a3db42..030b7fbfb1c 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/geocoder/LuceneIndexTest.java
@@ -136,8 +136,8 @@ static void setup() {
@Override
public List getModesOfStopLocation(StopLocation stop) {
- if (stop.getGtfsVehicleType() != null) {
- return List.of(stop.getGtfsVehicleType());
+ if (stop.getVehicleType() != null) {
+ return List.of(stop.getVehicleType());
} else {
return List.copyOf(modes.get(stop));
}
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java b/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java
index ad344713a74..7c3bf410192 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/restapi/parameter/QualifiedModeSetTest.java
@@ -231,7 +231,7 @@ void carHail() {
@Test
void carHailWithTransit() {
var modeSet = new QualifiedModeSet("CAR_HAIL,BUS,RAIL");
- assertEquals(Set.of(COACH, BUS, RAIL), Set.copyOf(modeSet.getTransitModes()));
+ assertEquals(Set.of(BUS, RAIL), Set.copyOf(modeSet.getTransitModes()));
assertEquals(WALK, modeSet.getRequestModes().directMode);
assertEquals(CAR_HAILING, modeSet.getRequestModes().accessMode);
diff --git a/application/src/ext-test/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceTest.java b/application/src/ext-test/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceTest.java
index 761971bc56f..84161c00484 100644
--- a/application/src/ext-test/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceTest.java
+++ b/application/src/ext-test/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceTest.java
@@ -7,6 +7,7 @@
import java.util.List;
import org.junit.jupiter.api.Test;
import org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace;
+import org.opentripplanner.updater.vehicle_rental.datasources.params.RentalPickupType;
import org.opentripplanner.updater.spi.HttpHeaders;
class SmooveBikeRentalDataSourceTest {
@@ -18,7 +19,8 @@ void makeStation() {
"file:src/ext-test/resources/smoovebikerental/smoove.json",
null,
true,
- HttpHeaders.empty()
+ HttpHeaders.empty(),
+ RentalPickupType.ALL
)
);
assertTrue(source.update());
@@ -84,7 +86,8 @@ void makeStationWithoutOverloading() {
"file:src/ext-test/resources/smoovebikerental/smoove.json",
null,
false,
- HttpHeaders.empty()
+ HttpHeaders.empty(),
+ RentalPickupType.ALL
)
);
assertTrue(source.update());
diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/FlexTripsMapper.java b/application/src/ext/java/org/opentripplanner/ext/flex/FlexTripsMapper.java
index 696c5df4c2a..17ce735a447 100644
--- a/application/src/ext/java/org/opentripplanner/ext/flex/FlexTripsMapper.java
+++ b/application/src/ext/java/org/opentripplanner/ext/flex/FlexTripsMapper.java
@@ -44,20 +44,20 @@ public class FlexTripsMapper {
.withTimePenalty(timePenalty)
.build()
);
- } else if (ScheduledDeviatedTrip.isScheduledFlexTrip(stopTimes)) {
+ } else if (ScheduledDeviatedTrip.isScheduledDeviatedFlexTrip(stopTimes)) {
result.add(
ScheduledDeviatedTrip.of(trip.getId()).withTrip(trip).withStopTimes(stopTimes).build()
);
- } else if (hasContinuousStops(stopTimes) && FlexTrip.containsFlexStops(stopTimes)) {
+ } else if (stopTimes.stream().anyMatch(StopTime::combinesContinuousStoppingWithFlexWindow)) {
store.add(
- "ContinuousFlexTrip",
- "Trip %s contains both flex stops and continuous pick up/drop off. This is an invalid combination: https://github.com/MobilityData/gtfs-flex/issues/70",
+ "ContinuousFlexStopTime",
+ "Trip %s contains a stop time which combines flex with continuous pick up/drop off. This is an invalid combination: https://github.com/MobilityData/gtfs-flex/issues/70",
trip.getId()
);
// result.add(new ContinuousPickupDropOffTrip(trip, stopTimes));
}
- //Keep lambda! A method-ref would causes incorrect class and line number to be logged
+ //Keep lambda! A method-ref would cause incorrect class and line number to be logged
//noinspection Convert2MethodRef
progress.step(m -> LOG.info(m));
}
diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
index 8dbcf4d785e..aeeab84259c 100644
--- a/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
+++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/AbstractFlexTemplate.java
@@ -199,7 +199,7 @@ private FlexAccessEgress createFlexAccessEgress(
return null;
}
- final var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], transferEdges);
+ final var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState, transferEdges);
return finalStateOpt
.map(finalState -> {
diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java
index f27a502911f..ae35c262a1e 100644
--- a/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java
+++ b/application/src/ext/java/org/opentripplanner/ext/flex/template/FlexDirectPathFactory.java
@@ -113,7 +113,7 @@ private Optional createDirectGraphPath(
final State[] afterFlexState = flexEdge.traverse(accessNearbyStop.state);
- var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState[0], egress.edges);
+ var finalStateOpt = EdgeTraverser.traverseEdges(afterFlexState, egress.edges);
if (finalStateOpt.isEmpty()) {
return Optional.empty();
diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java
index c8484532b68..25eac71ebb6 100644
--- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java
+++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/ScheduledDeviatedTrip.java
@@ -1,6 +1,5 @@
package org.opentripplanner.ext.flex.trip;
-import static org.opentripplanner.model.PickDrop.NONE;
import static org.opentripplanner.model.StopTime.MISSING_VALUE;
import java.io.Serializable;
@@ -35,7 +34,7 @@ public class ScheduledDeviatedTrip
ScheduledDeviatedTrip(ScheduledDeviatedTripBuilder builder) {
super(builder);
List stopTimes = builder.stopTimes();
- if (!isScheduledFlexTrip(stopTimes)) {
+ if (!isScheduledDeviatedFlexTrip(stopTimes)) {
throw new IllegalArgumentException("Incompatible stopTimes for scheduled flex trip");
}
@@ -55,12 +54,10 @@ public static ScheduledDeviatedTripBuilder of(FeedScopedId id) {
return new ScheduledDeviatedTripBuilder(id);
}
- public static boolean isScheduledFlexTrip(List stopTimes) {
- Predicate notStopType = Predicate.not(st -> st.getStop() instanceof RegularStop);
- Predicate notContinuousStop = stopTime ->
- stopTime.getFlexContinuousDropOff() == NONE && stopTime.getFlexContinuousPickup() == NONE;
+ public static boolean isScheduledDeviatedFlexTrip(List stopTimes) {
+ Predicate notFixedStop = Predicate.not(st -> st.getStop() instanceof RegularStop);
return (
- stopTimes.stream().anyMatch(notStopType) && stopTimes.stream().allMatch(notContinuousStop)
+ stopTimes.stream().anyMatch(notFixedStop) && stopTimes.stream().noneMatch(StopTime::combinesContinuousStoppingWithFlexWindow)
);
}
diff --git a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java
index b0286541bd2..83fc22c9975 100644
--- a/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java
+++ b/application/src/ext/java/org/opentripplanner/ext/flex/trip/UnscheduledTrip.java
@@ -1,6 +1,5 @@
package org.opentripplanner.ext.flex.trip;
-import static org.opentripplanner.model.PickDrop.NONE;
import static org.opentripplanner.model.StopTime.MISSING_VALUE;
import java.util.Arrays;
@@ -8,7 +7,6 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.opentripplanner.ext.flex.flexpathcalculator.FlexPathCalculator;
import org.opentripplanner.ext.flex.flexpathcalculator.TimePenaltyCalculator;
@@ -81,11 +79,9 @@ public static UnscheduledTripBuilder of(FeedScopedId id) {
* - One or more stop times with a flexible time window but no fixed stop in between them
*/
public static boolean isUnscheduledTrip(List stopTimes) {
- Predicate hasContinuousStops = stopTime ->
- stopTime.getFlexContinuousDropOff() != NONE || stopTime.getFlexContinuousPickup() != NONE;
if (stopTimes.isEmpty()) {
return false;
- } else if (stopTimes.stream().anyMatch(hasContinuousStops)) {
+ } else if (stopTimes.stream().anyMatch(StopTime::combinesContinuousStoppingWithFlexWindow)) {
return false;
} else if (N_STOPS.contains(stopTimes.size())) {
return stopTimes.stream().anyMatch(StopTime::hasFlexWindow);
diff --git a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java
index 48f87abf2ab..5880a6969fe 100644
--- a/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java
+++ b/application/src/ext/java/org/opentripplanner/ext/interactivelauncher/debug/logging/DebugLoggers.java
@@ -10,7 +10,8 @@ static List list() {
of("All OTP debuggers", "org.opentripplanner"),
of("OTP request/response", "org.opentripplanner.routing.service.DefaultRoutingService"),
of("Raptor request/response", "org.opentripplanner.raptor.RaptorService"),
- of("Transfer Optimization", "org.opentripplanner.routing.algorithm.transferoptimization")
+ of("Transfer Optimization", "org.opentripplanner.routing.algorithm.transferoptimization"),
+ of("Trip Updates", "org.opentripplanner.updater.trip")
);
}
diff --git a/application/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceParameters.java b/application/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceParameters.java
index 5aa834554c7..a04ea004bf0 100644
--- a/application/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceParameters.java
+++ b/application/src/ext/java/org/opentripplanner/ext/smoovebikerental/SmooveBikeRentalDataSourceParameters.java
@@ -1,6 +1,8 @@
package org.opentripplanner.ext.smoovebikerental;
+import java.util.Set;
import javax.annotation.Nullable;
+import org.opentripplanner.updater.vehicle_rental.datasources.params.RentalPickupType;
import org.opentripplanner.updater.spi.HttpHeaders;
import org.opentripplanner.updater.vehicle_rental.VehicleRentalSourceType;
import org.opentripplanner.updater.vehicle_rental.datasources.params.VehicleRentalDataSourceParameters;
@@ -12,7 +14,8 @@ public record SmooveBikeRentalDataSourceParameters(
String url,
String network,
boolean overloadingAllowed,
- HttpHeaders httpHeaders
+ HttpHeaders httpHeaders,
+ Set rentalPickupTypes
)
implements VehicleRentalDataSourceParameters {
/**
@@ -29,4 +32,9 @@ public String getNetwork(String defaultValue) {
public VehicleRentalSourceType sourceType() {
return VehicleRentalSourceType.SMOOVE;
}
+
+ @Override
+ public boolean allowRentalType(RentalPickupType rentalPickupType) {
+ return rentalPickupTypes.contains(rentalPickupType);
+ }
}
diff --git a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java
index 89a3071f975..ba4830f29cf 100644
--- a/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java
+++ b/application/src/ext/java/org/opentripplanner/ext/sorlandsbanen/CoachCostCalculator.java
@@ -1,10 +1,10 @@
package org.opentripplanner.ext.sorlandsbanen;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripSchedule;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.transit.model.basic.TransitMode;
diff --git a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java
index 03b0acd59cd..dc0fa2868ec 100644
--- a/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java
+++ b/application/src/ext/java/org/opentripplanner/ext/vehiclerentalservicedirectory/VehicleRentalServiceDirectoryFetcher.java
@@ -18,6 +18,7 @@
import org.opentripplanner.updater.spi.GraphUpdater;
import org.opentripplanner.updater.vehicle_rental.VehicleRentalUpdater;
import org.opentripplanner.updater.vehicle_rental.datasources.VehicleRentalDataSourceFactory;
+import org.opentripplanner.updater.vehicle_rental.datasources.params.RentalPickupType;
import org.opentripplanner.updater.vehicle_rental.datasources.params.GbfsVehicleRentalDataSourceParameters;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -110,7 +111,9 @@ private static List buildListOfNetworksFr
networkName,
networkParams.geofencingZones(),
// overloadingAllowed - not part of GBFS, not supported here
- false
+ false,
+ // rentalPickupType not supported
+ RentalPickupType.ALL
)
);
} else {
diff --git a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java
index 6e19b106033..7ac2c6f7d86 100644
--- a/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java
+++ b/application/src/main/java/org/opentripplanner/api/parameter/ApiRequestMode.java
@@ -12,7 +12,8 @@ public enum ApiRequestMode {
TRAM(TransitMode.TRAM),
SUBWAY(TransitMode.SUBWAY),
RAIL(TransitMode.RAIL),
- BUS(TransitMode.BUS, TransitMode.COACH),
+ BUS(TransitMode.BUS),
+ COACH(TransitMode.COACH),
FERRY(TransitMode.FERRY),
CABLE_CAR(TransitMode.CABLE_CAR),
GONDOLA(TransitMode.GONDOLA),
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
index 43a8399e70c..34e9b2f8346 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/GtfsGraphQLIndex.java
@@ -52,6 +52,7 @@
import org.opentripplanner.apis.gtfs.datafetchers.PlanConnectionImpl;
import org.opentripplanner.apis.gtfs.datafetchers.PlanImpl;
import org.opentripplanner.apis.gtfs.datafetchers.QueryTypeImpl;
+import org.opentripplanner.apis.gtfs.datafetchers.RentalPlaceTypeResolver;
import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleImpl;
import org.opentripplanner.apis.gtfs.datafetchers.RentalVehicleTypeImpl;
import org.opentripplanner.apis.gtfs.datafetchers.RideHailingEstimateImpl;
@@ -123,6 +124,7 @@ protected static GraphQLSchema buildSchema() {
)
.type("Node", type -> type.typeResolver(new NodeTypeResolver()))
.type("PlaceInterface", type -> type.typeResolver(new PlaceInterfaceTypeResolver()))
+ .type("RentalPlace", type -> type.typeResolver(new RentalPlaceTypeResolver()))
.type("StopPosition", type -> type.typeResolver(new StopPosition() {}))
.type("FareProduct", type -> type.typeResolver(new FareProductTypeResolver()))
.type("AlertEntity", type -> type.typeResolver(new AlertEntityTypeResolver()))
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java
index 90db8ef1605..4f7a3f61a57 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/AlertImpl.java
@@ -23,6 +23,7 @@
import org.opentripplanner.apis.gtfs.model.StopOnRouteModel;
import org.opentripplanner.apis.gtfs.model.StopOnTripModel;
import org.opentripplanner.apis.gtfs.model.UnknownModel;
+import org.opentripplanner.framework.graphql.GraphQLUtils;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.framework.i18n.TranslatedString;
import org.opentripplanner.routing.alertpatch.EntitySelector;
@@ -65,11 +66,11 @@ public DataFetcher alertCause() {
public DataFetcher alertDescriptionText() {
return environment -> {
var alert = getSource(environment);
- return alert
- .descriptionText()
- .or(alert::headerText)
- .map(t -> t.toString(environment.getLocale()))
- .orElse(FALLBACK_EMPTY_STRING);
+ var descriptionText = GraphQLUtils.getTranslation(
+ alert.descriptionText().or(alert::headerText).orElse(null),
+ environment
+ );
+ return descriptionText != null ? descriptionText : FALLBACK_EMPTY_STRING;
};
}
@@ -103,11 +104,11 @@ public DataFetcher alertHash() {
public DataFetcher alertHeaderText() {
return environment -> {
var alert = getSource(environment);
- return alert
- .headerText()
- .or(alert::descriptionText)
- .map(h -> h.toString(environment.getLocale()))
- .orElse(FALLBACK_EMPTY_STRING);
+ var headerText = GraphQLUtils.getTranslation(
+ alert.headerText().or(alert::descriptionText).orElse(null),
+ environment
+ );
+ return headerText != null ? headerText : FALLBACK_EMPTY_STRING;
};
}
@@ -125,7 +126,7 @@ public DataFetcher alertSeverityLevel() {
@Override
public DataFetcher alertUrl() {
return environment ->
- getSource(environment).url().map(u -> u.toString(environment.getLocale())).orElse(null);
+ GraphQLUtils.getTranslation(getSource(environment).url().orElse(null), environment);
}
@Override
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
index 8d84c7f0b03..5e892c06368 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
@@ -28,6 +28,7 @@
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.alternativelegs.AlternativeLegs;
import org.opentripplanner.routing.alternativelegs.AlternativeLegsFilter;
+import org.opentripplanner.routing.alternativelegs.NavigationDirection;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.organization.Agency;
import org.opentripplanner.transit.model.timetable.Trip;
@@ -275,8 +276,17 @@ private Leg getSource(DataFetchingEnvironment environment) {
return environment.getSource();
}
+ @Override
+ public DataFetcher> previousLegs() {
+ return alternativeLegs(NavigationDirection.PREVIOUS);
+ }
+
@Override
public DataFetcher> nextLegs() {
+ return alternativeLegs(NavigationDirection.NEXT);
+ }
+
+ private DataFetcher> alternativeLegs(NavigationDirection direction) {
return environment -> {
if (environment.getSource() instanceof ScheduledTransitLeg originalLeg) {
var args = new GraphQLTypes.GraphQLLegNextLegsArgs(environment.getArguments());
@@ -311,7 +321,7 @@ public DataFetcher> nextLegs() {
environment.getSource(),
numberOfLegs,
environment.getContext().transitService(),
- false,
+ direction,
AlternativeLegsFilter.NO_FILTER,
limitToExactOriginStop,
limitToExactDestinationStop
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
index 9e9d78445f3..fa9623f7c55 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
@@ -56,6 +56,7 @@
import org.opentripplanner.routing.graphfinder.PatternAtStop;
import org.opentripplanner.routing.graphfinder.PlaceAtDistance;
import org.opentripplanner.routing.graphfinder.PlaceType;
+import org.opentripplanner.routing.services.TransitAlertService;
import org.opentripplanner.routing.vehicle_parking.VehicleParking;
import org.opentripplanner.routing.vehicle_parking.VehicleParkingService;
import org.opentripplanner.service.vehiclerental.VehicleRentalService;
@@ -394,7 +395,7 @@ public DataFetcher node() {
case "Agency":
return transitService.getAgencyForId(FeedScopedId.parse(id));
case "Alert":
- return null; //TODO
+ return transitService.getTransitAlertService().getAlertById(FeedScopedId.parse(id));
case "BikePark":
var bikeParkId = FeedScopedId.parse(id);
return vehicleParkingService == null
@@ -928,6 +929,26 @@ public DataFetcher> vehicleRentalStations() {
};
}
+ @Override
+ public DataFetcher> vehicleRentalsByBbox() {
+ return environment -> {
+ VehicleRentalService vehicleRentalService = environment
+ .getContext()
+ .vehicleRentalService();
+
+ var args = new GraphQLTypes.GraphQLQueryTypeVehicleRentalsByBboxArgs(
+ environment.getArguments()
+ );
+
+ return vehicleRentalService.getVehicleRentalPlacesForEnvelope(
+ args.getGraphQLMinimumLongitude(),
+ args.getGraphQLMinimumLatitude(),
+ args.getGraphQLMaximumLongitude(),
+ args.getGraphQLMaximumLatitude()
+ );
+ };
+ }
+
@Override
public DataFetcher viewer() {
return environment -> new Object();
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalPlaceTypeResolver.java b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalPlaceTypeResolver.java
new file mode 100644
index 00000000000..9e4e5fdb109
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/RentalPlaceTypeResolver.java
@@ -0,0 +1,27 @@
+package org.opentripplanner.apis.gtfs.datafetchers;
+
+import graphql.TypeResolutionEnvironment;
+import graphql.schema.GraphQLObjectType;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.TypeResolver;
+import org.opentripplanner.service.vehiclerental.model.VehicleRentalStation;
+import org.opentripplanner.service.vehiclerental.model.VehicleRentalVehicle;
+
+public class RentalPlaceTypeResolver implements TypeResolver {
+
+ @Override
+ public GraphQLObjectType getType(TypeResolutionEnvironment env) {
+ Object o = env.getObject();
+ GraphQLSchema schema = env.getSchema();
+
+ if (o instanceof VehicleRentalStation) {
+ return schema.getObjectType("VehicleRentalStation");
+ }
+
+ if (o instanceof VehicleRentalVehicle) {
+ return schema.getObjectType("RentalVehicle");
+ }
+
+ return null;
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
index dffd892e7ee..f6b32771cc8 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLDataFetchers.java
@@ -503,6 +503,8 @@ public interface GraphQLLeg {
public DataFetcher pickupType();
+ public DataFetcher> previousLegs();
+
public DataFetcher realTime();
public DataFetcher realtimeState();
@@ -832,6 +834,8 @@ public interface GraphQLQueryType {
public DataFetcher> vehicleRentalStations();
+ public DataFetcher> vehicleRentalsByBbox();
+
public DataFetcher viewer();
}
@@ -842,6 +846,9 @@ public interface GraphQLRealTimeEstimate {
public DataFetcher time();
}
+ /** Rental place union that represents either a VehicleRentalStation or a RentalVehicle */
+ public interface GraphQLRentalPlace extends TypeResolver {}
+
/** Rental vehicle represents a vehicle that belongs to a rental network. */
public interface GraphQLRentalVehicle {
public DataFetcher allowPickupNow();
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
index 48d60701a96..b94541d2470 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/GraphQLTypes.java
@@ -75,6 +75,63 @@ public enum GraphQLAgencyAlertType {
ROUTE_TYPES,
}
+ public static class GraphQLAlertAlertDescriptionTextArgs {
+
+ private String language;
+
+ public GraphQLAlertAlertDescriptionTextArgs(Map args) {
+ if (args != null) {
+ this.language = (String) args.get("language");
+ }
+ }
+
+ public String getGraphQLLanguage() {
+ return this.language;
+ }
+
+ public void setGraphQLLanguage(String language) {
+ this.language = language;
+ }
+ }
+
+ public static class GraphQLAlertAlertHeaderTextArgs {
+
+ private String language;
+
+ public GraphQLAlertAlertHeaderTextArgs(Map args) {
+ if (args != null) {
+ this.language = (String) args.get("language");
+ }
+ }
+
+ public String getGraphQLLanguage() {
+ return this.language;
+ }
+
+ public void setGraphQLLanguage(String language) {
+ this.language = language;
+ }
+ }
+
+ public static class GraphQLAlertAlertUrlArgs {
+
+ private String language;
+
+ public GraphQLAlertAlertUrlArgs(Map args) {
+ if (args != null) {
+ this.language = (String) args.get("language");
+ }
+ }
+
+ public String getGraphQLLanguage() {
+ return this.language;
+ }
+
+ public void setGraphQLLanguage(String language) {
+ this.language = language;
+ }
+ }
+
/** Cause of a alert */
public enum GraphQLAlertCauseType {
ACCIDENT,
@@ -1318,6 +1375,69 @@ public void setGraphQLOriginModesWithParentStation(
}
}
+ public static class GraphQLLegPreviousLegsArgs {
+
+ private List destinationModesWithParentStation;
+ private Integer numberOfLegs;
+ private List originModesWithParentStation;
+
+ public GraphQLLegPreviousLegsArgs(Map args) {
+ if (args != null) {
+ if (args.get("destinationModesWithParentStation") != null) {
+ this.destinationModesWithParentStation =
+ ((List) args.get("destinationModesWithParentStation")).stream()
+ .map(item ->
+ item instanceof GraphQLTransitMode
+ ? item
+ : GraphQLTransitMode.valueOf((String) item)
+ )
+ .map(GraphQLTransitMode.class::cast)
+ .collect(Collectors.toList());
+ }
+ this.numberOfLegs = (Integer) args.get("numberOfLegs");
+ if (args.get("originModesWithParentStation") != null) {
+ this.originModesWithParentStation =
+ ((List) args.get("originModesWithParentStation")).stream()
+ .map(item ->
+ item instanceof GraphQLTransitMode
+ ? item
+ : GraphQLTransitMode.valueOf((String) item)
+ )
+ .map(GraphQLTransitMode.class::cast)
+ .collect(Collectors.toList());
+ }
+ }
+ }
+
+ public List getGraphQLDestinationModesWithParentStation() {
+ return this.destinationModesWithParentStation;
+ }
+
+ public Integer getGraphQLNumberOfLegs() {
+ return this.numberOfLegs;
+ }
+
+ public List getGraphQLOriginModesWithParentStation() {
+ return this.originModesWithParentStation;
+ }
+
+ public void setGraphQLDestinationModesWithParentStation(
+ List destinationModesWithParentStation
+ ) {
+ this.destinationModesWithParentStation = destinationModesWithParentStation;
+ }
+
+ public void setGraphQLNumberOfLegs(Integer numberOfLegs) {
+ this.numberOfLegs = numberOfLegs;
+ }
+
+ public void setGraphQLOriginModesWithParentStation(
+ List originModesWithParentStation
+ ) {
+ this.originModesWithParentStation = originModesWithParentStation;
+ }
+ }
+
public static class GraphQLLocalDateRangeInput {
private java.time.LocalDate end;
@@ -1815,6 +1935,35 @@ public void setGraphQLTransitOnly(Boolean transitOnly) {
}
}
+ public static class GraphQLPlanPassThroughViaLocationInput {
+
+ private String label;
+ private List stopLocationIds;
+
+ public GraphQLPlanPassThroughViaLocationInput(Map args) {
+ if (args != null) {
+ this.label = (String) args.get("label");
+ this.stopLocationIds = (List) args.get("stopLocationIds");
+ }
+ }
+
+ public String getGraphQLLabel() {
+ return this.label;
+ }
+
+ public List getGraphQLStopLocationIds() {
+ return this.stopLocationIds;
+ }
+
+ public void setGraphQLLabel(String label) {
+ this.label = label;
+ }
+
+ public void setGraphQLStopLocationIds(List stopLocationIds) {
+ this.stopLocationIds = stopLocationIds;
+ }
+ }
+
public static class GraphQLPlanPreferencesInput {
private GraphQLAccessibilityPreferencesInput accessibility;
@@ -2048,6 +2197,75 @@ public void setGraphQLTransit(List transi
}
}
+ public static class GraphQLPlanViaLocationInput {
+
+ private GraphQLPlanPassThroughViaLocationInput passThrough;
+ private GraphQLPlanVisitViaLocationInput visit;
+
+ public GraphQLPlanViaLocationInput(Map args) {
+ if (args != null) {
+ this.passThrough =
+ new GraphQLPlanPassThroughViaLocationInput((Map) args.get("passThrough"));
+ this.visit = new GraphQLPlanVisitViaLocationInput((Map) args.get("visit"));
+ }
+ }
+
+ public GraphQLPlanPassThroughViaLocationInput getGraphQLPassThrough() {
+ return this.passThrough;
+ }
+
+ public GraphQLPlanVisitViaLocationInput getGraphQLVisit() {
+ return this.visit;
+ }
+
+ public void setGraphQLPassThrough(GraphQLPlanPassThroughViaLocationInput passThrough) {
+ this.passThrough = passThrough;
+ }
+
+ public void setGraphQLVisit(GraphQLPlanVisitViaLocationInput visit) {
+ this.visit = visit;
+ }
+ }
+
+ public static class GraphQLPlanVisitViaLocationInput {
+
+ private String label;
+ private java.time.Duration minimumWaitTime;
+ private List stopLocationIds;
+
+ public GraphQLPlanVisitViaLocationInput(Map args) {
+ if (args != null) {
+ this.label = (String) args.get("label");
+ this.minimumWaitTime = (java.time.Duration) args.get("minimumWaitTime");
+ this.stopLocationIds = (List) args.get("stopLocationIds");
+ }
+ }
+
+ public String getGraphQLLabel() {
+ return this.label;
+ }
+
+ public java.time.Duration getGraphQLMinimumWaitTime() {
+ return this.minimumWaitTime;
+ }
+
+ public List getGraphQLStopLocationIds() {
+ return this.stopLocationIds;
+ }
+
+ public void setGraphQLLabel(String label) {
+ this.label = label;
+ }
+
+ public void setGraphQLMinimumWaitTime(java.time.Duration minimumWaitTime) {
+ this.minimumWaitTime = minimumWaitTime;
+ }
+
+ public void setGraphQLStopLocationIds(List stopLocationIds) {
+ this.stopLocationIds = stopLocationIds;
+ }
+ }
+
public enum GraphQLPropulsionType {
COMBUSTION,
COMBUSTION_DIESEL,
@@ -2745,6 +2963,7 @@ public static class GraphQLQueryTypePlanArgs {
private List transportModes;
private GraphQLInputTriangleInput triangle;
private GraphQLInputUnpreferredInput unpreferred;
+ private List via;
private Double waitAtBeginningFactor;
private Double waitReluctance;
private Integer walkBoardCost;
@@ -2826,6 +3045,9 @@ public GraphQLQueryTypePlanArgs(Map args) {
this.triangle = new GraphQLInputTriangleInput((Map) args.get("triangle"));
this.unpreferred =
new GraphQLInputUnpreferredInput((Map) args.get("unpreferred"));
+ if (args.get("via") != null) {
+ this.via = (List) args.get("via");
+ }
this.waitAtBeginningFactor = (Double) args.get("waitAtBeginningFactor");
this.waitReluctance = (Double) args.get("waitReluctance");
this.walkBoardCost = (Integer) args.get("walkBoardCost");
@@ -3057,6 +3279,10 @@ public GraphQLInputUnpreferredInput getGraphQLUnpreferred() {
return this.unpreferred;
}
+ public List getGraphQLVia() {
+ return this.via;
+ }
+
public Double getGraphQLWaitAtBeginningFactor() {
return this.waitAtBeginningFactor;
}
@@ -3315,6 +3541,10 @@ public void setGraphQLUnpreferred(GraphQLInputUnpreferredInput unpreferred) {
this.unpreferred = unpreferred;
}
+ public void setGraphQLVia(List via) {
+ this.via = via;
+ }
+
public void setGraphQLWaitAtBeginningFactor(Double waitAtBeginningFactor) {
this.waitAtBeginningFactor = waitAtBeginningFactor;
}
@@ -3362,6 +3592,7 @@ public static class GraphQLQueryTypePlanConnectionArgs {
private GraphQLPlanLabeledLocationInput origin;
private GraphQLPlanPreferencesInput preferences;
private java.time.Duration searchWindow;
+ private List via;
public GraphQLQueryTypePlanConnectionArgs(Map args) {
if (args != null) {
@@ -3380,6 +3611,9 @@ public GraphQLQueryTypePlanConnectionArgs(Map args) {
this.preferences =
new GraphQLPlanPreferencesInput((Map) args.get("preferences"));
this.searchWindow = (java.time.Duration) args.get("searchWindow");
+ if (args.get("via") != null) {
+ this.via = (List) args.get("via");
+ }
}
}
@@ -3431,6 +3665,10 @@ public java.time.Duration getGraphQLSearchWindow() {
return this.searchWindow;
}
+ public List getGraphQLVia() {
+ return this.via;
+ }
+
public void setGraphQLAfter(String after) {
this.after = after;
}
@@ -3478,6 +3716,10 @@ public void setGraphQLPreferences(GraphQLPlanPreferencesInput preferences) {
public void setGraphQLSearchWindow(java.time.Duration searchWindow) {
this.searchWindow = searchWindow;
}
+
+ public void setGraphQLVia(List via) {
+ this.via = via;
+ }
}
public static class GraphQLQueryTypeRentalVehicleArgs {
@@ -3979,6 +4221,55 @@ public void setGraphQLIds(List ids) {
}
}
+ public static class GraphQLQueryTypeVehicleRentalsByBboxArgs {
+
+ private Double maximumLatitude;
+ private Double maximumLongitude;
+ private Double minimumLatitude;
+ private Double minimumLongitude;
+
+ public GraphQLQueryTypeVehicleRentalsByBboxArgs(Map args) {
+ if (args != null) {
+ this.maximumLatitude = (Double) args.get("maximumLatitude");
+ this.maximumLongitude = (Double) args.get("maximumLongitude");
+ this.minimumLatitude = (Double) args.get("minimumLatitude");
+ this.minimumLongitude = (Double) args.get("minimumLongitude");
+ }
+ }
+
+ public Double getGraphQLMaximumLatitude() {
+ return this.maximumLatitude;
+ }
+
+ public Double getGraphQLMaximumLongitude() {
+ return this.maximumLongitude;
+ }
+
+ public Double getGraphQLMinimumLatitude() {
+ return this.minimumLatitude;
+ }
+
+ public Double getGraphQLMinimumLongitude() {
+ return this.minimumLongitude;
+ }
+
+ public void setGraphQLMaximumLatitude(Double maximumLatitude) {
+ this.maximumLatitude = maximumLatitude;
+ }
+
+ public void setGraphQLMaximumLongitude(Double maximumLongitude) {
+ this.maximumLongitude = maximumLongitude;
+ }
+
+ public void setGraphQLMinimumLatitude(Double minimumLatitude) {
+ this.minimumLatitude = minimumLatitude;
+ }
+
+ public void setGraphQLMinimumLongitude(Double minimumLongitude) {
+ this.minimumLongitude = minimumLongitude;
+ }
+ }
+
public enum GraphQLRealtimeState {
ADDED,
CANCELED,
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
index 2a7f2a95ba0..bf6b010bf2c 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/generated/graphql-codegen.yml
@@ -125,4 +125,5 @@ config:
FareMedium: org.opentripplanner.model.fare.FareMedium#FareMedium
RiderCategory: org.opentripplanner.model.fare.RiderCategory#RiderCategory
StopPosition: org.opentripplanner.apis.gtfs.model.StopPosition#StopPosition
+ RentalPlace: org.opentripplanner.service.vehiclerental.model.VehicleRentalPlace#VehicleRentalPlace
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java
index 696ce9c45b5..19bee5e430d 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/LegacyRouteRequestMapper.java
@@ -54,6 +54,8 @@ public static RouteRequest toRouteRequest(
callWith.argument("from", (Map v) -> request.setFrom(toGenericLocation(v)));
callWith.argument("to", (Map v) -> request.setTo(toGenericLocation(v)));
+ mapViaLocations(request, environment);
+
request.setDateTime(
environment.getArgument("date"),
environment.getArgument("time"),
@@ -255,6 +257,12 @@ public static RouteRequest toRouteRequest(
return request;
}
+ static void mapViaLocations(RouteRequest request, DataFetchingEnvironment env) {
+ var args = env.getArgument("via");
+ var locs = ViaLocationMapper.mapToViaLocations((List>>) args);
+ request.setViaLocations(locs);
+ }
+
private static boolean hasArgument(Map m, String name) {
return m.containsKey(name) && m.get(name) != null;
}
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java
index 95752548ca8..23e42f9a70c 100644
--- a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/RouteRequestMapper.java
@@ -9,6 +9,8 @@
import graphql.schema.DataFetchingEnvironment;
import java.time.Instant;
+import java.util.List;
+import java.util.Map;
import org.opentripplanner.apis.gtfs.GraphQLRequestContext;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
import org.opentripplanner.framework.graphql.GraphQLUtils;
@@ -63,6 +65,8 @@ public static RouteRequest toRouteRequest(
setModes(request.journey(), args.getGraphQLModes(), environment);
+ // sadly we need to use the raw collection because it is cast to the wrong type
+ mapViaPoints(request, environment.getArgument("via"));
return request;
}
@@ -178,4 +182,8 @@ private static GenericLocation parseGenericLocation(
coordinate.getGraphQLLongitude()
);
}
+
+ static void mapViaPoints(RouteRequest request, List>> via) {
+ request.setViaLocations(ViaLocationMapper.mapToViaLocations(via));
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java
new file mode 100644
index 00000000000..984f67855e8
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/apis/gtfs/mapping/routerequest/ViaLocationMapper.java
@@ -0,0 +1,56 @@
+package org.opentripplanner.apis.gtfs.mapping.routerequest;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.opentripplanner.routing.api.request.via.PassThroughViaLocation;
+import org.opentripplanner.routing.api.request.via.ViaLocation;
+import org.opentripplanner.routing.api.request.via.VisitViaLocation;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+import org.opentripplanner.utils.collection.ListUtils;
+
+/**
+ * Maps the input data to the data structure needed for via routing.
+ */
+class ViaLocationMapper {
+
+ static final String FIELD_STOP_LOCATION_IDS = "stopLocationIds";
+ static final String FIELD_LABEL = "label";
+ static final String FIELD_MINIMUM_WAIT_TIME = "minimumWaitTime";
+ static final String FIELD_VISIT = "visit";
+ static final String FIELD_PASS_THROUGH = "passThrough";
+
+ static List mapToViaLocations(@Nullable List>> via) {
+ return ListUtils
+ .nullSafeImmutableList(via)
+ .stream()
+ .map(ViaLocationMapper::mapViaLocation)
+ .toList();
+ }
+
+ private static ViaLocation mapViaLocation(Map> via) {
+ var passThrough = via.get(FIELD_PASS_THROUGH);
+ var visit = via.get(FIELD_VISIT);
+
+ if (passThrough != null && passThrough.get(FIELD_STOP_LOCATION_IDS) != null) {
+ return new PassThroughViaLocation(
+ (String) passThrough.get(FIELD_LABEL),
+ mapStopLocationIds((List) passThrough.get(FIELD_STOP_LOCATION_IDS))
+ );
+ } else if (visit != null) {
+ return new VisitViaLocation(
+ (String) visit.get(FIELD_LABEL),
+ (Duration) visit.get(FIELD_MINIMUM_WAIT_TIME),
+ mapStopLocationIds((List) visit.get(FIELD_STOP_LOCATION_IDS)),
+ List.of()
+ );
+ } else {
+ throw new IllegalArgumentException("ViaLocation must define either pass-through or visit.");
+ }
+ }
+
+ private static List mapStopLocationIds(List ids) {
+ return ids.stream().map(FeedScopedId::parse).toList();
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java
index 6541b48a44d..e0472a2ccf8 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/framework/InfoLinkType.java
@@ -20,7 +20,7 @@ public static GraphQLObjectType create() {
.description("URI")
.dataFetcher(environment -> {
AlertUrl source = environment.getSource();
- return source.uri;
+ return source.uri();
})
.build()
)
@@ -32,7 +32,7 @@ public static GraphQLObjectType create() {
.description("Label")
.dataFetcher(environment -> {
AlertUrl source = environment.getSource();
- return source.label;
+ return source.label();
})
.build()
)
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java
index 5bf56f75e4b..4b405f50f66 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/plan/LegType.java
@@ -33,6 +33,7 @@
import org.opentripplanner.model.plan.TransitLeg;
import org.opentripplanner.model.plan.legreference.LegReferenceSerializer;
import org.opentripplanner.routing.alternativelegs.AlternativeLegs;
+import org.opentripplanner.routing.alternativelegs.NavigationDirection;
public class LegType {
@@ -485,7 +486,7 @@ public static GraphQLObjectType create(
leg,
env.getArgument("previous"),
GqlUtil.getTransitService(env),
- true,
+ NavigationDirection.PREVIOUS,
env.getArgument("filter")
);
})
@@ -525,7 +526,7 @@ public static GraphQLObjectType create(
leg,
env.getArgument("next"),
GqlUtil.getTransitService(env),
- false,
+ NavigationDirection.NEXT,
env.getArgument("filter")
);
})
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java
index 1567cd977c9..4fd9505b73e 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/siri/sx/PtSituationElementType.java
@@ -241,7 +241,7 @@ public static GraphQLObjectType create(
if (!siriUrls.isEmpty()) {
return siriUrls;
}
- return null;
+ return emptyList();
})
.build()
)
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
index 00ac5b0e11b..8dbc6291136 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/stop/StopPlaceType.java
@@ -183,7 +183,7 @@ public static GraphQLObjectType create(
.dataFetcher(environment ->
((MonoOrMultiModalStation) environment.getSource()).getChildStops()
.stream()
- .map(StopLocation::getGtfsVehicleType)
+ .map(StopLocation::getVehicleType)
.filter(Objects::nonNull)
.collect(Collectors.toSet())
)
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java
index 7fcce88c4ee..1ad05118e11 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/DatedServiceJourneyType.java
@@ -125,14 +125,16 @@ public static GraphQLObjectType create(
if (first != null && last != null) {
throw new AssertException("Both first and last can't be defined simultaneously.");
- } else if (first != null) {
- if (first > stops.size()) {
- return stops.subList(0, first);
- }
- } else if (last != null) {
- if (last > stops.size()) {
- return stops.subList(stops.size() - last, stops.size());
- }
+ }
+
+ if ((first != null && first < 0) || (last != null && last < 0)) {
+ throw new AssertException("first and last must be positive integers.");
+ }
+
+ if (first != null && first < stops.size()) {
+ return stops.subList(0, first);
+ } else if (last != null && last < stops.size()) {
+ return stops.subList(stops.size() - last, stops.size());
}
return stops;
})
diff --git a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java
index e61d0a12edc..20a290863df 100644
--- a/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java
+++ b/application/src/main/java/org/opentripplanner/apis/transmodel/model/timetable/ServiceJourneyType.java
@@ -75,13 +75,6 @@ public static GraphQLObjectType create(
)
.build()
)
- // .field(GraphQLFieldDefinition.newFieldDefinition()
- // .name("serviceAlteration")
- // .type(serviceAlterationEnum)
- // .description("Whether journey is as planned, a cancellation or an extra journey. Default is as planned")
- // .dataFetcher(environment -> (((Trip) trip(environment)).getServiceAlteration()))
- // .build())
-
.field(
GraphQLFieldDefinition
.newFieldDefinition()
@@ -218,14 +211,16 @@ public static GraphQLObjectType create(
if (first != null && last != null) {
throw new AssertException("Both first and last can't be defined simultaneously.");
- } else if (first != null) {
- if (first > stops.size()) {
- return stops.subList(0, first);
- }
- } else if (last != null) {
- if (last > stops.size()) {
- return stops.subList(stops.size() - last, stops.size());
- }
+ }
+
+ if ((first != null && first < 0) || (last != null && last < 0)) {
+ throw new AssertException("first and last must be positive integers.");
+ }
+
+ if (first != null && first < stops.size()) {
+ return stops.subList(0, first);
+ } else if (last != null && last < stops.size()) {
+ return stops.subList(stops.size() - last, stops.size());
}
return stops;
})
@@ -329,17 +324,6 @@ public static GraphQLObjectType create(
)
.build()
)
- // .field(GraphQLFieldDefinition.newFieldDefinition()
- // .name("keyValues")
- // .description("List of keyValue pairs for the service journey.")
- // .type(new GraphQLList(keyValueType))
- // .dataFetcher(environment -> ((Trip) trip(environment)).getKeyValues())
- // .build())
- // .field(GraphQLFieldDefinition.newFieldDefinition()
- // .name("flexibleServiceType")
- // .description("Type of flexible service, or null if service is not flexible.")
- // .type(flexibleServiceTypeEnum)
- // .build())
.field(
GraphQLFieldDefinition
.newFieldDefinition()
diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
index 3c8423c5270..f27d252f250 100644
--- a/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
+++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/DebugStyleSpec.java
@@ -1,5 +1,7 @@
package org.opentripplanner.apis.vectortiles;
+import static org.opentripplanner.inspector.vector.edge.EdgePropertyMapper.streetPermissionAsString;
+
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@@ -30,8 +32,8 @@
import org.opentripplanner.utils.collection.ListUtils;
/**
- * A Mapbox/Mapblibre style specification for rendering debug information about transit and
- * street data.
+ * A Mapbox/Mapblibre style specification for rendering debug information about transit and street
+ * data.
*/
public class DebugStyleSpec {
@@ -45,15 +47,20 @@ public class DebugStyleSpec {
private static final String MAGENTA = "#f21d52";
private static final String BRIGHT_GREEN = "#22DD9E";
private static final String DARK_GREEN = "#136b04";
+ private static final String RED = "#fc0f2a";
private static final String PURPLE = "#BC55F2";
private static final String BLACK = "#140d0e";
private static final int MAX_ZOOM = 23;
+ private static final int LINE_DETAIL_ZOOM = 13;
private static final ZoomDependentNumber LINE_OFFSET = new ZoomDependentNumber(
- List.of(new ZoomStop(13, 0.3f), new ZoomStop(MAX_ZOOM, 6))
+ List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.4f), new ZoomStop(MAX_ZOOM, 7))
);
private static final ZoomDependentNumber LINE_WIDTH = new ZoomDependentNumber(
- List.of(new ZoomStop(13, 0.2f), new ZoomStop(MAX_ZOOM, 8))
+ List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.2f), new ZoomStop(MAX_ZOOM, 8))
+ );
+ private static final ZoomDependentNumber LINE_HALF_WIDTH = new ZoomDependentNumber(
+ List.of(new ZoomStop(LINE_DETAIL_ZOOM, 0.1f), new ZoomStop(MAX_ZOOM, 6))
);
private static final ZoomDependentNumber CIRCLE_STROKE = new ZoomDependentNumber(
List.of(new ZoomStop(15, 0.2f), new ZoomStop(MAX_ZOOM, 3))
@@ -70,7 +77,15 @@ public class DebugStyleSpec {
private static final String EDGES_GROUP = "Edges";
private static final String STOPS_GROUP = "Stops";
private static final String VERTICES_GROUP = "Vertices";
- private static final String TRAVERSAL_PERMISSIONS_GROUP = "Traversal permissions";
+ private static final String PERMISSIONS_GROUP = "Permissions";
+ private static final String NO_THRU_TRAFFIC_GROUP = "No-thru traffic";
+
+ private static final StreetTraversalPermission[] streetModes = new StreetTraversalPermission[] {
+ StreetTraversalPermission.PEDESTRIAN,
+ StreetTraversalPermission.BICYCLE,
+ StreetTraversalPermission.CAR,
+ };
+ private static final String WHEELCHAIR_GROUP = "Wheelchair accessibility";
static StyleSpec build(
VectorSourceLayer regularStops,
@@ -90,6 +105,8 @@ static StyleSpec build(
allSources,
ListUtils.combine(
List.of(StyleBuilder.ofId("background").typeRaster().source(BACKGROUND_SOURCE).minZoom(0)),
+ wheelchair(edges),
+ noThruTraffic(edges),
traversalPermissions(edges),
edges(edges),
vertices(vertices),
@@ -183,7 +200,7 @@ private static List edges(VectorSourceLayer edges) {
.vectorSourceLayer(edges)
.lineColor(MAGENTA)
.edgeFilter(EDGES_TO_DISPLAY)
- .lineWidth(LINE_WIDTH)
+ .lineWidth(LINE_HALF_WIDTH)
.lineOffset(LINE_OFFSET)
.minZoom(6)
.maxZoom(MAX_ZOOM)
@@ -222,26 +239,32 @@ private static List edges(VectorSourceLayer edges) {
private static List traversalPermissions(VectorSourceLayer edges) {
var permissionStyles = Arrays
- .stream(StreetTraversalPermission.values())
- .map(p ->
+ .stream(streetModes)
+ .map(streetTraversalPermission ->
StyleBuilder
- .ofId(p.name())
+ .ofId("permission " + streetTraversalPermission)
.vectorSourceLayer(edges)
- .group(TRAVERSAL_PERMISSIONS_GROUP)
+ .group(PERMISSIONS_GROUP)
.typeLine()
- .lineColor(permissionColor(p))
- .permissionsFilter(p)
+ .filterValueInProperty(
+ "permission",
+ streetTraversalPermission.name(),
+ StreetTraversalPermission.ALL.name()
+ )
+ .lineCap("butt")
+ .lineColorMatch("permission", permissionColors(), BLACK)
.lineWidth(LINE_WIDTH)
.lineOffset(LINE_OFFSET)
- .minZoom(6)
+ .minZoom(LINE_DETAIL_ZOOM)
.maxZoom(MAX_ZOOM)
.intiallyHidden()
)
.toList();
+
var textStyle = StyleBuilder
.ofId("permission-text")
.vectorSourceLayer(edges)
- .group(TRAVERSAL_PERMISSIONS_GROUP)
+ .group(PERMISSIONS_GROUP)
.typeSymbol()
.lineText("permission")
.textOffset(1)
@@ -249,12 +272,88 @@ private static List traversalPermissions(VectorSourceLayer edges)
.minZoom(17)
.maxZoom(MAX_ZOOM)
.intiallyHidden();
+
return ListUtils.combine(permissionStyles, List.of(textStyle));
}
+ private static List noThruTraffic(VectorSourceLayer edges) {
+ var noThruTrafficStyles = Arrays
+ .stream(streetModes)
+ .map(streetTraversalPermission ->
+ StyleBuilder
+ .ofId("no-thru-traffic " + streetTraversalPermission)
+ .vectorSourceLayer(edges)
+ .group(NO_THRU_TRAFFIC_GROUP)
+ .typeLine()
+ .filterValueInProperty(
+ "noThruTraffic",
+ streetTraversalPermission.name(),
+ StreetTraversalPermission.ALL.name()
+ )
+ .lineCap("butt")
+ .lineColorMatch("noThruTraffic", permissionColors(), BLACK)
+ .lineWidth(LINE_WIDTH)
+ .lineOffset(LINE_OFFSET)
+ .minZoom(LINE_DETAIL_ZOOM)
+ .maxZoom(MAX_ZOOM)
+ .intiallyHidden()
+ )
+ .toList();
+
+ var textStyle = StyleBuilder
+ .ofId("no-thru-traffic-text")
+ .vectorSourceLayer(edges)
+ .group(NO_THRU_TRAFFIC_GROUP)
+ .typeSymbol()
+ .lineText("noThruTraffic")
+ .textOffset(1)
+ .edgeFilter(EDGES_TO_DISPLAY)
+ .minZoom(17)
+ .maxZoom(MAX_ZOOM)
+ .intiallyHidden();
+
+ return ListUtils.combine(noThruTrafficStyles, List.of(textStyle));
+ }
+
+ private static List permissionColors() {
+ return Arrays
+ .stream(StreetTraversalPermission.values())
+ .flatMap(p -> Stream.of(streetPermissionAsString(p), permissionColor(p)))
+ .toList();
+ }
+
+ private static List wheelchair(VectorSourceLayer edges) {
+ return List.of(
+ StyleBuilder
+ .ofId("wheelchair-accessible")
+ .vectorSourceLayer(edges)
+ .group(WHEELCHAIR_GROUP)
+ .typeLine()
+ .lineColor(DARK_GREEN)
+ .booleanFilter("wheelchairAccessible", true)
+ .lineWidth(LINE_WIDTH)
+ .lineOffset(LINE_OFFSET)
+ .minZoom(6)
+ .maxZoom(MAX_ZOOM)
+ .intiallyHidden(),
+ StyleBuilder
+ .ofId("wheelchair-inaccessible")
+ .vectorSourceLayer(edges)
+ .group(WHEELCHAIR_GROUP)
+ .typeLine()
+ .lineColor(RED)
+ .booleanFilter("wheelchairAccessible", false)
+ .lineWidth(LINE_WIDTH)
+ .lineOffset(LINE_OFFSET)
+ .minZoom(6)
+ .maxZoom(MAX_ZOOM)
+ .intiallyHidden()
+ );
+ }
+
private static String permissionColor(StreetTraversalPermission p) {
return switch (p) {
- case NONE -> "#000";
+ case NONE -> BLACK;
case PEDESTRIAN -> "#2ba812";
case BICYCLE, PEDESTRIAN_AND_BICYCLE -> "#10d3b6";
case CAR -> "#f92e13";
diff --git a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java
index 6f81e7fd998..a70acbb8685 100644
--- a/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java
+++ b/application/src/main/java/org/opentripplanner/apis/vectortiles/model/StyleBuilder.java
@@ -2,7 +2,9 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -10,7 +12,6 @@
import java.util.stream.Stream;
import org.opentripplanner.apis.vectortiles.model.ZoomDependentNumber.ZoomStop;
import org.opentripplanner.framework.json.ObjectMappers;
-import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.utils.collection.ListUtils;
@@ -29,7 +30,7 @@ public class StyleBuilder {
private final Map layout = new LinkedHashMap<>();
private final Map metadata = new LinkedHashMap<>();
private final Map line = new LinkedHashMap<>();
- private List filter = List.of();
+ private List extends Object> filter = List.of();
public static StyleBuilder ofId(String id) {
return new StyleBuilder(id);
@@ -167,12 +168,42 @@ public StyleBuilder circleRadius(ZoomDependentNumber radius) {
}
// Line styling
+ public StyleBuilder lineCap(String lineCap) {
+ layout.put("line-cap", lineCap);
+ return this;
+ }
public StyleBuilder lineColor(String color) {
paint.put("line-color", validateColor(color));
return this;
}
+ public StyleBuilder lineColorMatch(
+ String propertyName,
+ Collection values,
+ String defaultValue
+ ) {
+ paint.put(
+ "line-color",
+ ListUtils.combine(
+ List.of("match", List.of("get", propertyName)),
+ (Collection) values,
+ List.of(defaultValue)
+ )
+ );
+ return this;
+ }
+
+ public StyleBuilder lineOpacity(float lineOpacity) {
+ paint.put("line-opacity", lineOpacity);
+ return this;
+ }
+
+ public StyleBuilder lineDasharray(float... dashArray) {
+ paint.put("line-dasharray", dashArray);
+ return this;
+ }
+
public StyleBuilder lineWidth(float width) {
paint.put("line-width", width);
return this;
@@ -220,10 +251,10 @@ public final StyleBuilder edgeFilter(Class extends Edge>... classToFilter) {
}
/**
- * Filter the entities by their "permission" property.
+ * Filter the entities by a boolean property.
*/
- public final StyleBuilder permissionsFilter(StreetTraversalPermission p) {
- filter = List.of("==", "permission", p.name());
+ public final StyleBuilder booleanFilter(String propertyName, boolean value) {
+ filter = List.of("==", propertyName, value);
return this;
}
@@ -235,6 +266,16 @@ public final StyleBuilder vertexFilter(Class extends Vertex>... classToFilter)
return filterClasses(classToFilter);
}
+ public StyleBuilder filterValueInProperty(String propertyName, String... values) {
+ var newFilter = new ArrayList<>();
+ newFilter.add("any");
+ for (String value : values) {
+ newFilter.add(List.of("in", value, List.of("string", List.of("get", propertyName))));
+ }
+ filter = newFilter;
+ return this;
+ }
+
public JsonNode toJson() {
validate();
@@ -257,7 +298,7 @@ public JsonNode toJson() {
private StyleBuilder filterClasses(Class... classToFilter) {
var clazzes = Arrays.stream(classToFilter).map(Class::getSimpleName).toList();
- filter = ListUtils.combine(List.of("in", "class"), clazzes);
+ filter = new ArrayList<>(ListUtils.combine(List.of("in", "class"), clazzes));
return this;
}
diff --git a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java
deleted file mode 100644
index 0369e3e29db..00000000000
--- a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountSkipEdgeStrategy.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package org.opentripplanner.astar.strategy;
-
-import java.util.function.Predicate;
-import org.opentripplanner.astar.spi.AStarEdge;
-import org.opentripplanner.astar.spi.AStarState;
-import org.opentripplanner.astar.spi.SkipEdgeStrategy;
-
-/**
- * Skips edges when the specified number of desired vertices have been visited.
- */
-public class MaxCountSkipEdgeStrategy<
- State extends AStarState, Edge extends AStarEdge
->
- implements SkipEdgeStrategy {
-
- private final int maxCount;
- private final Predicate shouldIncreaseCount;
-
- private int visited;
-
- public MaxCountSkipEdgeStrategy(int count, Predicate shouldIncreaseCount) {
- this.maxCount = count;
- this.shouldIncreaseCount = shouldIncreaseCount;
- this.visited = 0;
- }
-
- @Override
- public boolean shouldSkipEdge(State current, Edge edge) {
- if (shouldIncreaseCount.test(current)) {
- visited++;
- }
- return visited > maxCount;
- }
-}
diff --git a/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java
new file mode 100644
index 00000000000..66c5496c923
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/astar/strategy/MaxCountTerminationStrategy.java
@@ -0,0 +1,36 @@
+package org.opentripplanner.astar.strategy;
+
+import java.util.function.Predicate;
+import org.opentripplanner.astar.spi.AStarState;
+import org.opentripplanner.astar.spi.SearchTerminationStrategy;
+
+/**
+ * This termination strategy is used to terminate an a-star search after a number of states matching
+ * some criteria has been found. For example it can be used to limit a search to a maximum number of
+ * stops.
+ */
+public class MaxCountTerminationStrategy>
+ implements SearchTerminationStrategy {
+
+ private final int maxCount;
+ private final Predicate shouldIncreaseCount;
+ private int count;
+
+ /**
+ * @param maxCount Terminate the search after this many matching states have been reached.
+ * @param shouldIncreaseCount A predicate to check if a state should increase the count or not.
+ */
+ public MaxCountTerminationStrategy(int maxCount, Predicate shouldIncreaseCount) {
+ this.maxCount = maxCount;
+ this.shouldIncreaseCount = shouldIncreaseCount;
+ this.count = 0;
+ }
+
+ @Override
+ public boolean shouldSearchTerminate(State current) {
+ if (shouldIncreaseCount.test(current)) {
+ count++;
+ }
+ return count >= maxCount;
+ }
+}
diff --git a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
index 41921d42c62..6631287613b 100644
--- a/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
+++ b/application/src/main/java/org/opentripplanner/framework/application/OTPFeature.java
@@ -18,6 +18,17 @@ public enum OTPFeature {
APIBikeRental(true, false, "Enable the bike rental endpoint."),
APIServerInfo(true, false, "Enable the server info endpoint."),
APIUpdaterStatus(true, false, "Enable endpoint for graph updaters status."),
+ IncludeEmptyRailStopsInTransfers(
+ false,
+ false,
+ """
+ Turning this on guarantees that Rail stops without scheduled departures still get included
+ when generating transfers using `ConsiderPatternsForDirectTransfers`. It is common for stops
+ to be assign at real-time for Rail. Turning this on will help to avoid dropping transfers which
+ are needed, when the stop is in use later. Turning this on, if
+ ConsiderPatternsForDirectTransfers is off has no effect.
+ """
+ ),
ConsiderPatternsForDirectTransfers(
true,
false,
diff --git a/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java b/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java
index 4f75d214c91..101342c7a6f 100644
--- a/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java
+++ b/application/src/main/java/org/opentripplanner/framework/i18n/I18NString.java
@@ -1,6 +1,7 @@
package org.opentripplanner.framework.i18n;
import java.util.Locale;
+import javax.annotation.Nullable;
/**
* This interface is used when providing translations on server side. Sources: OSM tags with
@@ -9,9 +10,20 @@
* @author mabu
*/
public interface I18NString {
- /** true if the given value is not {@code null} or has at least one none white-space character. */
- public static boolean hasValue(I18NString value) {
- return value != null && !value.toString().isBlank();
+ /**
+ * Return {@code true} if the given value is not {@code null} or has at least one none
+ * white-space character.
+ */
+ static boolean hasValue(@Nullable I18NString value) {
+ return !hasNoValue(value);
+ }
+
+ /**
+ * Return {@code true} if the given value has at least one none white-space character.
+ * Return {@code false} if the value is {@code null} or blank.
+ */
+ static boolean hasNoValue(@Nullable I18NString value) {
+ return value == null || value.toString().isBlank();
}
/**
@@ -26,8 +38,8 @@ public static boolean hasValue(I18NString value) {
*/
String toString(Locale locale);
- static I18NString assertHasValue(I18NString value) {
- if (value == null || value.toString().isBlank()) {
+ static I18NString assertHasValue(@Nullable I18NString value) {
+ if (hasNoValue(value)) {
throw new IllegalArgumentException(
"Value can not be null, empty or just whitespace: " +
(value == null ? "null" : "'" + value + "'")
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
index bc83ad2a8af..bf3447391bd 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/GraphBuilder.java
@@ -207,7 +207,7 @@ private void addModule(GraphBuilderModule module) {
graphBuilderModules.add(module);
}
- private void addModuleOptional(GraphBuilderModule module) {
+ private void addModuleOptional(@Nullable GraphBuilderModule module) {
if (module != null) {
graphBuilderModules.add(module);
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/PatternConsideringNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/PatternConsideringNearbyStopFinder.java
index 9abf759d076..70d1aac3483 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/PatternConsideringNearbyStopFinder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/PatternConsideringNearbyStopFinder.java
@@ -1,10 +1,9 @@
package org.opentripplanner.graph_builder.module.nearbystops;
-import java.time.Duration;
+import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.ext.flex.trip.FlexTrip;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -49,6 +48,9 @@ public List findNearbyStops(
/* Track the closest stop on each flex trip nearby. */
MinMap, NearbyStop> closestStopForFlexTrip = new MinMap<>();
+ /* The end result */
+ Set uniqueStopsResult = new HashSet<>();
+
/* Iterate over nearby stops via the street network or using straight-line distance. */
for (NearbyStop nearbyStop : delegateNearbyStopFinder.findNearbyStops(
vertex,
@@ -58,9 +60,17 @@ public List findNearbyStops(
)) {
StopLocation ts1 = nearbyStop.stop;
- if (ts1 instanceof RegularStop) {
+ if (ts1 instanceof RegularStop regularStop) {
/* Consider this destination stop as a candidate for every trip pattern passing through it. */
- for (TripPattern pattern : transitService.getPatternsForStop(ts1)) {
+ Collection patternsForStop = transitService.getPatternsForStop(ts1);
+
+ if (OTPFeature.IncludeEmptyRailStopsInTransfers.isOn()) {
+ if (patternsForStop.isEmpty() && regularStop.isRailStop()) {
+ uniqueStopsResult.add(nearbyStop);
+ }
+ }
+
+ for (TripPattern pattern : patternsForStop) {
if (
reverseDirection
? pattern.canAlight(nearbyStop.stop)
@@ -85,10 +95,9 @@ public List findNearbyStops(
}
/* Make a transfer from the origin stop to each destination stop that was the closest stop on any pattern. */
- Set uniqueStops = new HashSet<>();
- uniqueStops.addAll(closestStopForFlexTrip.values());
- uniqueStops.addAll(closestStopForPattern.values());
+ uniqueStopsResult.addAll(closestStopForFlexTrip.values());
+ uniqueStopsResult.addAll(closestStopForPattern.values());
// TODO: don't convert to list
- return uniqueStops.stream().toList();
+ return uniqueStopsResult.stream().toList();
}
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java
index e54c27249e1..8277bd47e4c 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/nearbystops/StreetNearbyStopFinder.java
@@ -11,10 +11,8 @@
import java.util.List;
import java.util.Set;
import org.opentripplanner.astar.model.ShortestPathTree;
-import org.opentripplanner.astar.spi.SkipEdgeStrategy;
-import org.opentripplanner.astar.strategy.ComposingSkipEdgeStrategy;
import org.opentripplanner.astar.strategy.DurationSkipEdgeStrategy;
-import org.opentripplanner.astar.strategy.MaxCountSkipEdgeStrategy;
+import org.opentripplanner.astar.strategy.MaxCountTerminationStrategy;
import org.opentripplanner.ext.dataoverlay.routing.DataOverlayContext;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
@@ -30,8 +28,6 @@
import org.opentripplanner.street.model.vertex.Vertex;
import org.opentripplanner.street.search.StreetSearchBuilder;
import org.opentripplanner.street.search.TraverseMode;
-import org.opentripplanner.street.search.request.StreetSearchRequest;
-import org.opentripplanner.street.search.request.StreetSearchRequestMapper;
import org.opentripplanner.street.search.state.State;
import org.opentripplanner.street.search.strategy.DominanceFunctions;
import org.opentripplanner.transit.model.site.AreaStop;
@@ -46,8 +42,8 @@ public class StreetNearbyStopFinder implements NearbyStopFinder {
/**
* Construct a NearbyStopFinder for the given graph and search radius.
*
- * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the maxStopCount
- * we will always return all the directly connected stops.
+ * @param maxStopCount The maximum stops to return. 0 means no limit. Regardless of the
+ * maxStopCount we will always return all the directly connected stops.
*/
public StreetNearbyStopFinder(
Duration durationLimit,
@@ -117,23 +113,30 @@ public Collection findNearbyStops(
// Return only the origin vertices if there are no valid street modes
if (
streetRequest.mode() == StreetMode.NOT_SET ||
- (maxStopCount != 0 && stopsFound.size() >= maxStopCount)
+ (maxStopCount > 0 && stopsFound.size() >= maxStopCount)
) {
return stopsFound;
}
stopsFound = new ArrayList<>(stopsFound);
- ShortestPathTree spt = StreetSearchBuilder
+ var streetSearch = StreetSearchBuilder
.of()
- .setSkipEdgeStrategy(getSkipEdgeStrategy())
+ .setSkipEdgeStrategy(new DurationSkipEdgeStrategy<>(durationLimit))
.setDominanceFunction(new DominanceFunctions.MinimumWeight())
.setRequest(request)
.setArriveBy(reverseDirection)
.setStreetRequest(streetRequest)
.setFrom(reverseDirection ? null : originVertices)
.setTo(reverseDirection ? originVertices : null)
- .setDataOverlayContext(dataOverlayContext)
- .getShortestPathTree();
+ .setDataOverlayContext(dataOverlayContext);
+
+ if (maxStopCount > 0) {
+ streetSearch.setTerminationStrategy(
+ new MaxCountTerminationStrategy<>(maxStopCount, this::hasReachedStop)
+ );
+ }
+
+ ShortestPathTree spt = streetSearch.getShortestPathTree();
// Only used if OTPFeature.FlexRouting.isOn()
Multimap locationsMap = ArrayListMultimap.create();
@@ -186,16 +189,6 @@ public Collection findNearbyStops(
return stopsFound;
}
- private SkipEdgeStrategy getSkipEdgeStrategy() {
- var durationSkipEdgeStrategy = new DurationSkipEdgeStrategy(durationLimit);
-
- if (maxStopCount > 0) {
- var strategy = new MaxCountSkipEdgeStrategy<>(maxStopCount, this::hasReachedStop);
- return new ComposingSkipEdgeStrategy<>(strategy, durationSkipEdgeStrategy);
- }
- return durationSkipEdgeStrategy;
- }
-
private boolean canBoardFlex(State state, boolean reverse) {
Collection edges = reverse
? state.getVertex().getIncoming()
@@ -212,13 +205,13 @@ private boolean canBoardFlex(State state, boolean reverse) {
*
* This is important because there can be cases where states that cannot actually board the vehicle
* can dominate those that can thereby leading to zero found stops when this predicate is used with
- * the {@link MaxCountSkipEdgeStrategy}.
+ * the {@link MaxCountTerminationStrategy}.
*
* An example of this would be an egress/reverse search with a very high walk reluctance where the
* states that speculatively rent a vehicle move the walk states down the A* priority queue until
* the required number of stops are reached to abort the search, leading to zero egress results.
*/
- public boolean hasReachedStop(State state) {
+ private boolean hasReachedStop(State state) {
var vertex = state.getVertex();
return (
vertex instanceof TransitStopVertex && state.isFinal() && !ignoreVertices.contains(vertex)
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java
index bb958fd9414..23999d1fec5 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/OsmDatabase.java
@@ -688,7 +688,7 @@ private void processSingleWayAreas() {
}
}
try {
- newArea(new Area(way, List.of(way), Collections.emptyList(), nodesById));
+ addArea(new Area(way, List.of(way), Collections.emptyList(), nodesById));
} catch (Area.AreaConstructionException | Ring.RingConstructionException e) {
// this area cannot be constructed, but we already have all the
// necessary nodes to construct it. So, something must be wrong with
@@ -751,7 +751,7 @@ private void processMultipolygonRelations() {
}
processedAreas.add(relation);
try {
- newArea(new Area(relation, outerWays, innerWays, nodesById));
+ addArea(new Area(relation, outerWays, innerWays, nodesById));
} catch (Area.AreaConstructionException | Ring.RingConstructionException e) {
issueStore.add(new InvalidOsmGeometry(relation));
continue;
@@ -786,10 +786,12 @@ private void processMultipolygonRelations() {
/**
* Handler for a new Area (single way area or multipolygon relations)
*/
- private void newArea(Area area) {
- StreetTraversalPermission permissions = area.parent.overridePermissions(
- StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE
- );
+ private void addArea(Area area) {
+ StreetTraversalPermission permissions = area.parent
+ .getOsmProvider()
+ .getWayPropertySet()
+ .getDataForWay(area.parent)
+ .getPermission();
if (area.parent.isRoutable() && permissions != StreetTraversalPermission.NONE) {
walkableAreas.add(area);
}
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java
index f372f0c82e2..d2bbbe7e27f 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/ParkingProcessor.java
@@ -312,7 +312,7 @@ private List createArtificialEntra
);
}
- private VehicleParking createVehicleParkingObjectFromOsmEntity(
+ VehicleParking createVehicleParkingObjectFromOsmEntity(
boolean isCarParkAndRide,
Coordinate coordinate,
OsmWithTags entity,
@@ -421,7 +421,7 @@ private OptionalInt parseCapacity(OsmWithTags element) {
}
private OptionalInt parseCapacity(OsmWithTags element, String capacityTag) {
- return element.getTagAsInt(
+ return element.parseIntOrBoolean(
capacityTag,
v -> issueStore.add(new InvalidVehicleParkingCapacity(element, v))
);
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java
index 2f070f272c6..2cb5b01dcc6 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/SafetyValueNormalizer.java
@@ -86,8 +86,8 @@ void applyWayProperties(
boolean motorVehicleNoThrough = tagMapperForWay.isMotorVehicleThroughTrafficExplicitlyDisallowed(
way
);
- boolean bicycleNoThrough = tagMapperForWay.isBicycleNoThroughTrafficExplicitlyDisallowed(way);
- boolean walkNoThrough = tagMapperForWay.isWalkNoThroughTrafficExplicitlyDisallowed(way);
+ boolean bicycleNoThrough = tagMapperForWay.isBicycleThroughTrafficExplicitlyDisallowed(way);
+ boolean walkNoThrough = tagMapperForWay.isWalkThroughTrafficExplicitlyDisallowed(way);
if (street != null) {
double bicycleSafety = wayData.bicycleSafety().forward();
diff --git a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
index f27d80d5617..2802ee70a89 100644
--- a/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
+++ b/application/src/main/java/org/opentripplanner/graph_builder/module/osm/WalkableAreaBuilder.java
@@ -499,9 +499,14 @@ private Set createSegments(
Area area = intersects.getFirst();
OsmWithTags areaEntity = area.parent;
- StreetTraversalPermission areaPermissions = areaEntity.overridePermissions(
- StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE
- );
+ WayProperties wayData;
+ if (!wayPropertiesCache.containsKey(areaEntity)) {
+ wayData = areaEntity.getOsmProvider().getWayPropertySet().getDataForWay(areaEntity);
+ wayPropertiesCache.put(areaEntity, wayData);
+ } else {
+ wayData = wayPropertiesCache.get(areaEntity);
+ }
+ StreetTraversalPermission areaPermissions = wayData.getPermission();
float carSpeed = areaEntity
.getOsmProvider()
@@ -520,8 +525,8 @@ private Set createSegments(
startEndpoint.getLabel() +
" to " +
endEndpoint.getLabel();
- I18NString name = namer.getNameForWay(areaEntity, label);
+ I18NString name = namer.getNameForWay(areaEntity, label);
AreaEdgeBuilder streetEdgeBuilder = new AreaEdgeBuilder()
.withFromVertex(startEndpoint)
.withToVertex(endEndpoint)
@@ -543,8 +548,8 @@ private Set createSegments(
endEndpoint.getLabel() +
" to " +
startEndpoint.getLabel();
- name = namer.getNameForWay(areaEntity, label);
+ name = namer.getNameForWay(areaEntity, label);
AreaEdgeBuilder backStreetEdgeBuilder = new AreaEdgeBuilder()
.withFromVertex(endEndpoint)
.withToVertex(startEndpoint)
@@ -559,22 +564,10 @@ private Set createSegments(
.withWheelchairAccessible(areaEntity.isWheelchairAccessible())
.withLink(areaEntity.isLink());
- if (!wayPropertiesCache.containsKey(areaEntity)) {
- WayProperties wayData = areaEntity
- .getOsmProvider()
- .getWayPropertySet()
- .getDataForWay(areaEntity);
- wayPropertiesCache.put(areaEntity, wayData);
- }
-
AreaEdge street = streetEdgeBuilder.buildAndConnect();
+
AreaEdge backStreet = backStreetEdgeBuilder.buildAndConnect();
- normalizer.applyWayProperties(
- street,
- backStreet,
- wayPropertiesCache.get(areaEntity),
- areaEntity
- );
+ normalizer.applyWayProperties(street, backStreet, wayData, areaEntity);
return Set.of(street, backStreet);
} else {
// take the part that intersects with the start vertex
@@ -640,12 +633,12 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection
I18NString name = namer.getNameForWay(areaEntity, id);
namedArea.setName(name);
+ WayProperties wayData;
if (!wayPropertiesCache.containsKey(areaEntity)) {
- WayProperties wayData = areaEntity
- .getOsmProvider()
- .getWayPropertySet()
- .getDataForWay(areaEntity);
+ wayData = areaEntity.getOsmProvider().getWayPropertySet().getDataForWay(areaEntity);
wayPropertiesCache.put(areaEntity, wayData);
+ } else {
+ wayData = wayPropertiesCache.get(areaEntity);
}
double bicycleSafety = wayPropertiesCache.get(areaEntity).bicycleSafety().forward();
@@ -653,14 +646,8 @@ private void createNamedAreas(AreaEdgeList edgeList, Ring ring, Collection
double walkSafety = wayPropertiesCache.get(areaEntity).walkSafety().forward();
namedArea.setWalkSafetyMultiplier(walkSafety);
-
namedArea.setOriginalEdges(intersection);
-
- StreetTraversalPermission permission = areaEntity.overridePermissions(
- StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE
- );
- namedArea.setPermission(permission);
-
+ namedArea.setPermission(wayData.getPermission());
edgeList.addArea(namedArea);
}
}
diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/BikeAccessMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/BikeAccessMapper.java
index 12f7edcf8c1..4175dabcbc4 100644
--- a/application/src/main/java/org/opentripplanner/gtfs/mapping/BikeAccessMapper.java
+++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/BikeAccessMapper.java
@@ -5,64 +5,23 @@
import org.opentripplanner.transit.model.network.BikeAccess;
/**
- * Model bike access for GTFS trips.
- *
- * The GTFS bike extensions is originally discussed at: https://groups.google.com/d/msg/gtfs-changes/QqaGOuNmG7o/xyqORy-T4y0J
- *
- * It proposes "route_bikes_allowed" in routes.txt and "trip_bikes_allowed" in trips.txt with the
- * following semantics:
- *
- * 2: bikes allowed 1: no bikes allowed 0: no information (same as field omitted)
- *
- * The values in trips.txt override the values in routes.txt.
- *
- * An alternative proposal is discussed in: https://groups.google.com/d/msg/gtfs-changes/rEiSeKNc4cs/gTTnQ_yXtPgJ
- *
- * Here, the field "bikes_allowed" is used in both routes.txt and trip.txt with the following
- * semantics:
- *
- * 2: no bikes allowed 1: bikes allowed 0: no information (same as field omitted)
- *
- * Here, the 0,1,2 semantics have been changed to match the convention used in the
- * "wheelchair_accessible" field in trips.txt.
- *
- * A number of feeds are still using the original proposal and a number of feeds have been updated
- * to use the new proposal. For now, we support both, using "bikes_allowed" if specified and then
- * "trip_bikes_allowed".
+ * Model bike access for GTFS trips by using the bikes_allowed fields from route and trip.
*/
class BikeAccessMapper {
public static BikeAccess mapForTrip(Trip rhs) {
- //noinspection deprecation
- return mapValues(rhs.getBikesAllowed(), rhs.getTripBikesAllowed());
+ return mapValues(rhs.getBikesAllowed());
}
public static BikeAccess mapForRoute(Route rhs) {
- //noinspection deprecation
- return mapValues(rhs.getBikesAllowed(), rhs.getRouteBikesAllowed());
+ return mapValues(rhs.getBikesAllowed());
}
- private static BikeAccess mapValues(int bikesAllowed, int legacyBikesAllowed) {
- if (bikesAllowed != 0) {
- switch (bikesAllowed) {
- case 1:
- return BikeAccess.ALLOWED;
- case 2:
- return BikeAccess.NOT_ALLOWED;
- default:
- return BikeAccess.UNKNOWN;
- }
- } else if (legacyBikesAllowed != 0) {
- switch (legacyBikesAllowed) {
- case 1:
- return BikeAccess.NOT_ALLOWED;
- case 2:
- return BikeAccess.ALLOWED;
- default:
- return BikeAccess.UNKNOWN;
- }
- }
-
- return BikeAccess.UNKNOWN;
+ private static BikeAccess mapValues(int bikesAllowed) {
+ return switch (bikesAllowed) {
+ case 1 -> BikeAccess.ALLOWED;
+ case 2 -> BikeAccess.NOT_ALLOWED;
+ default -> BikeAccess.UNKNOWN;
+ };
}
}
diff --git a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java
index 0640011bb3b..919d71455a2 100644
--- a/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java
+++ b/application/src/main/java/org/opentripplanner/gtfs/mapping/TransitModeMapper.java
@@ -20,7 +20,7 @@ public static TransitMode mapMode(int routeType) {
// Railway Service
return TransitMode.RAIL;
} else if (routeType >= 200 && routeType < 300) { //Coach Service
- return TransitMode.BUS;
+ return TransitMode.COACH;
} else if (routeType >= 300 && routeType < 500) { //Suburban Railway Service and Urban Railway service
if (routeType >= 401 && routeType <= 402) {
return TransitMode.SUBWAY;
diff --git a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java
index 5dc68fc335f..30763edca9e 100644
--- a/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java
+++ b/application/src/main/java/org/opentripplanner/inspector/vector/edge/EdgePropertyMapper.java
@@ -8,6 +8,7 @@
import java.util.List;
import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.inspector.vector.KeyValue;
+import org.opentripplanner.street.model.StreetTraversalPermission;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.model.edge.EscalatorEdge;
import org.opentripplanner.street.model.edge.StreetEdge;
@@ -29,8 +30,10 @@ protected Collection map(Edge input) {
private static List mapStreetEdge(StreetEdge se) {
var props = Lists.newArrayList(
- kv("permission", se.getPermission().toString()),
- kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor()))
+ kv("permission", streetPermissionAsString(se.getPermission())),
+ kv("bicycleSafetyFactor", roundTo2Decimals(se.getBicycleSafetyFactor())),
+ kv("noThruTraffic", noThruTrafficAsString(se)),
+ kv("wheelchairAccessible", se.isWheelchairAccessible())
);
if (se.hasBogusName()) {
props.addFirst(kv("name", "%s (generated)".formatted(se.getName().toString())));
@@ -39,4 +42,22 @@ private static List mapStreetEdge(StreetEdge se) {
}
return props;
}
+
+ public static String streetPermissionAsString(StreetTraversalPermission permission) {
+ return permission.name().replace("_AND_", " ");
+ }
+
+ private static String noThruTrafficAsString(StreetEdge se) {
+ var noThruPermission = StreetTraversalPermission.NONE;
+ if (se.isWalkNoThruTraffic()) {
+ noThruPermission = noThruPermission.add(StreetTraversalPermission.PEDESTRIAN);
+ }
+ if (se.isBicycleNoThruTraffic()) {
+ noThruPermission = noThruPermission.add(StreetTraversalPermission.BICYCLE);
+ }
+ if (se.isMotorVehicleNoThruTraffic()) {
+ noThruPermission = noThruPermission.add(StreetTraversalPermission.CAR);
+ }
+ return streetPermissionAsString(noThruPermission);
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java
index e5bcc6c0322..28fc16a98ac 100644
--- a/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java
+++ b/application/src/main/java/org/opentripplanner/model/RealTimeTripUpdate.java
@@ -9,12 +9,19 @@
/**
* Represents the real-time update of a single trip.
- * @param pattern the pattern to which belongs the updated trip. This can be a new pattern created in real-time.
- * @param updatedTripTimes the new trip times for the updated trip.
- * @param serviceDate the service date for which this update applies (updates are valid only for one service date)
- * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip.
- * @param tripCreation true if this update creates a new trip, not present in scheduled data.
- * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created.
+ *
+ * @param pattern the pattern to which belongs the updated trip. This can be a new
+ * pattern created in real-time.
+ * @param updatedTripTimes the new trip times for the updated trip.
+ * @param serviceDate the service date for which this update applies (updates are valid
+ * only for one service date)
+ * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the
+ * TripOnServiceDate corresponding to this new trip.
+ * @param tripCreation true if this update creates a new trip, not present in scheduled
+ * data.
+ * @param routeCreation true if an added trip cannot be registered under an existing route
+ * and a new route must be created.
+ * @param producer the producer of the real-time update.
*/
public record RealTimeTripUpdate(
TripPattern pattern,
@@ -22,7 +29,8 @@ public record RealTimeTripUpdate(
LocalDate serviceDate,
@Nullable TripOnServiceDate addedTripOnServiceDate,
boolean tripCreation,
- boolean routeCreation
+ boolean routeCreation,
+ @Nullable String producer
) {
public RealTimeTripUpdate {
Objects.requireNonNull(pattern);
@@ -38,6 +46,25 @@ public RealTimeTripUpdate(
TripTimes updatedTripTimes,
LocalDate serviceDate
) {
- this(pattern, updatedTripTimes, serviceDate, null, false, false);
+ this(pattern, updatedTripTimes, serviceDate, null, false, false, null);
+ }
+
+ public RealTimeTripUpdate(
+ TripPattern pattern,
+ TripTimes updatedTripTimes,
+ LocalDate serviceDate,
+ @Nullable TripOnServiceDate addedTripOnServiceDate,
+ boolean tripCreation,
+ boolean routeCreation
+ ) {
+ this(
+ pattern,
+ updatedTripTimes,
+ serviceDate,
+ addedTripOnServiceDate,
+ tripCreation,
+ routeCreation,
+ null
+ );
}
}
diff --git a/application/src/main/java/org/opentripplanner/model/StopTime.java b/application/src/main/java/org/opentripplanner/model/StopTime.java
index e31350192e1..0754dbce671 100644
--- a/application/src/main/java/org/opentripplanner/model/StopTime.java
+++ b/application/src/main/java/org/opentripplanner/model/StopTime.java
@@ -1,6 +1,8 @@
/* This file is based on code copied from project OneBusAway, see the LICENSE file for further information. */
package org.opentripplanner.model;
+import static org.opentripplanner.model.PickDrop.NONE;
+
import java.util.List;
import org.opentripplanner.framework.i18n.I18NString;
import org.opentripplanner.transit.model.site.StopLocation;
@@ -305,4 +307,16 @@ private static int getAvailableTime(int... times) {
public boolean hasFlexWindow() {
return flexWindowStart != MISSING_VALUE || flexWindowEnd != MISSING_VALUE;
}
+
+ /**
+ * Checks if this stop time combines flex windows with continuous stopping, which is against the
+ * GTFS spec.
+ */
+ public boolean combinesContinuousStoppingWithFlexWindow() {
+ return hasContinuousStopping() && hasFlexWindow();
+ }
+
+ public boolean hasContinuousStopping() {
+ return this.flexContinuousPickup != NONE || flexContinuousDropOff != NONE;
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
index 0a54fc964d8..94b490c48a0 100644
--- a/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
+++ b/application/src/main/java/org/opentripplanner/model/TimetableSnapshot.java
@@ -347,8 +347,7 @@ public Result update(RealTimeTripUpdate realTimeTrip
}
// The time tables are finished during the commit
-
- return Result.success(UpdateSuccess.noWarnings());
+ return Result.success(UpdateSuccess.noWarnings(realTimeTripUpdate.producer()));
}
/**
diff --git a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java
index f707b97c131..5ff936f3118 100644
--- a/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java
+++ b/application/src/main/java/org/opentripplanner/model/plan/Itinerary.java
@@ -17,8 +17,8 @@
import org.opentripplanner.model.SystemNotice;
import org.opentripplanner.model.fare.ItineraryFares;
import org.opentripplanner.raptor.api.model.RaptorConstants;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.path.PathStringBuilder;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.preference.ItineraryFilterPreferences;
import org.opentripplanner.utils.lang.DoubleUtils;
diff --git a/application/src/main/java/org/opentripplanner/model/projectinfo/OtpProjectInfo.java b/application/src/main/java/org/opentripplanner/model/projectinfo/OtpProjectInfo.java
index 37651d4522f..c49fbc1ae4f 100644
--- a/application/src/main/java/org/opentripplanner/model/projectinfo/OtpProjectInfo.java
+++ b/application/src/main/java/org/opentripplanner/model/projectinfo/OtpProjectInfo.java
@@ -80,7 +80,7 @@ public String toString() {
* dev-2.x}
*/
public String getVersionString() {
- String format = "version: %s, ser.ver.id: %s, commit: %s, branch: %s";
+ String format = "Version: %s, ser.ver.id: %s, commit: %s, branch: %s";
return String.format(
format,
version.version,
@@ -91,8 +91,8 @@ public String getVersionString() {
}
/**
- * This method compare the maven project version, an return {@code true} if both are the same. Two
- * different SNAPSHOT versions are considered the same - work in progress.
+ * This method compares the maven project version, and return {@code true} if both are the same.
+ * Two different SNAPSHOT versions are considered the same version - they are work in progress.
*/
public boolean sameVersion(OtpProjectInfo other) {
return this.version.sameVersion(other.version);
@@ -100,8 +100,8 @@ public boolean sameVersion(OtpProjectInfo other) {
/**
* The OTP Serialization version id is used to determine if OTP and a serialized blob(Graph.obj)
- * of the otp internal model are compatible. This filed is writen into the Graph.obj file header
- * and checked when loading the graph later.
+ * of the otp internal model are compatible. This field is written into the Graph.obj
+ * file header and checked when loading the graph later.
*/
public String getOtpSerializationVersionId() {
return graphFileHeaderInfo.otpSerializationVersionId();
diff --git a/application/src/main/java/org/opentripplanner/netex/mapping/FlexStopsMapper.java b/application/src/main/java/org/opentripplanner/netex/mapping/FlexStopsMapper.java
index 1bbe76c0e7c..f53eb67b01a 100644
--- a/application/src/main/java/org/opentripplanner/netex/mapping/FlexStopsMapper.java
+++ b/application/src/main/java/org/opentripplanner/netex/mapping/FlexStopsMapper.java
@@ -140,7 +140,7 @@ List findStopsInFlexArea(
List stops = stopsSpatialIndex
.query(geometry.getEnvelopeInternal())
.stream()
- .filter(stop -> flexibleStopTransitMode == stop.getGtfsVehicleType())
+ .filter(stop -> flexibleStopTransitMode == stop.getVehicleType())
.filter(stop -> geometry.contains(stop.getGeometry()))
.toList();
diff --git a/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java b/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
index 3c9a3f537ee..67f737e4c79 100644
--- a/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
+++ b/application/src/main/java/org/opentripplanner/osm/model/OsmWithTags.java
@@ -49,6 +49,7 @@ public class OsmWithTags {
private static final Set LEVEL_TAGS = Set.of("level", "layer");
private static final Set DEFAULT_LEVEL = Set.of("0");
+ private static final Consumer NO_OP = i -> {};
/* To save memory this is only created when an entity actually has tags. */
private Map tags;
@@ -220,6 +221,33 @@ public OptionalInt getTagAsInt(String tag, Consumer errorHandler) {
return OptionalInt.empty();
}
+ /**
+ * Some tags are allowed to have values like 55, "true" or "false".
+ *
+ * "true", "yes" is returned as 1.
+ *
+ * "false", "no" is returned as 0
+ *
+ * Everything else is returned as an emtpy optional.
+ */
+ public OptionalInt parseIntOrBoolean(String tag, Consumer errorHandler) {
+ var maybeInt = getTagAsInt(tag, NO_OP);
+ if (maybeInt.isPresent()) {
+ return maybeInt;
+ } else {
+ if (isTagTrue(tag)) {
+ return OptionalInt.of(1);
+ } else if (isTagFalse(tag)) {
+ return OptionalInt.of(0);
+ } else if (hasTag(tag)) {
+ errorHandler.accept(getTag(tag));
+ return OptionalInt.empty();
+ } else {
+ return OptionalInt.empty();
+ }
+ }
+ }
+
/**
* Checks is a tag contains the specified value.
*/
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java
index 4d190c0b667..34ba62a1daa 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/AtlantaMapper.java
@@ -13,10 +13,9 @@
*
* @author demory
* @see OsmTagMapper
- * @see DefaultMapper
*/
-class AtlantaMapper implements OsmTagMapper {
+class AtlantaMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -27,7 +26,6 @@ public void populateProperties(WayPropertySet props) {
// Max speed limit in Georgia is 70 mph ~= 113kmh ~= 31.3m/s
props.maxPossibleCarSpeed = 31.4f;
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java
index 9f1b1ac0ade..e2ecb998159 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/ConstantSpeedMapper.java
@@ -6,7 +6,7 @@
/**
* OSM way properties for optimizing distance (not traveling time) in routing.
*/
-class ConstantSpeedFinlandMapper implements OsmTagMapper {
+class ConstantSpeedFinlandMapper extends FinlandMapper {
private float speed;
@@ -23,8 +23,7 @@ public ConstantSpeedFinlandMapper(float speed) {
@Override
public void populateProperties(WayPropertySet props) {
props.setCarSpeed("highway=*", speed);
- // Read the rest from the default set
- new FinlandMapper().populateProperties(props);
+ super.populateProperties(props);
props.maxPossibleCarSpeed = speed;
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java
deleted file mode 100644
index faa666c750a..00000000000
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/DefaultMapper.java
+++ /dev/null
@@ -1,721 +0,0 @@
-package org.opentripplanner.osm.tagmapping;
-
-import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety;
-import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofWalkSafety;
-import static org.opentripplanner.osm.wayproperty.WayPropertiesBuilder.withModes;
-import static org.opentripplanner.street.model.StreetTraversalPermission.ALL;
-import static org.opentripplanner.street.model.StreetTraversalPermission.BICYCLE_AND_CAR;
-import static org.opentripplanner.street.model.StreetTraversalPermission.CAR;
-import static org.opentripplanner.street.model.StreetTraversalPermission.NONE;
-import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN;
-import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE;
-
-import org.opentripplanner.osm.wayproperty.WayProperties;
-import org.opentripplanner.osm.wayproperty.WayPropertySet;
-import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier;
-import org.opentripplanner.osm.wayproperty.specifier.LogicalOrSpecifier;
-import org.opentripplanner.routing.services.notes.StreetNotesService;
-
-/**
- * This factory class provides a default collection of {@link WayProperties} that determine how OSM
- * streets can be traversed in various modes.
- *
- * Circa January 2011, Grant and Mele at TriMet undertook proper testing of bike (and transit)
- * routing, and worked with David Turner on assigning proper weights to different facility types.
- * The weights in this file grew organically from trial and error, and are the result of months of
- * testing and tweaking the routes that OTP returned, as well as actually walking/biking these
- * routes and making changes based on those experiences. This set of weights should be a great
- * starting point for others to use, but they are to some extent tailored to the situation in
- * Portland and people shouldn't hesitate to adjust them to for their own instance.
- *
- * The rules for assigning WayProperties to OSM ways are explained in. The final tie breaker if two
- * Pickers both match is the sequence that the properties are added in this file: if all else is
- * equal the 'props.setProperties' statement that is closer to the top of the page will prevail over
- * those lower down the page.
- *
- * Foot and bicycle permissions are also addressed in OpenStreetMapGraphBuilderImpl.Handler#getPermissionsForEntity().
- * For instance, if a way that normally does not permit walking based on its tag matches (the
- * prevailing 'props.setProperties' statement) has a 'foot=yes' tag the permissions are overridden
- * and walking is allowed on that way.
- *
- * TODO clarify why this needs a separate factory interface.
- *
- * @author bdferris, novalis
- * @see OsmTagMapper
- */
-class DefaultMapper implements OsmTagMapper {
-
- /* Populate properties on existing WayPropertySet */
- public void populateProperties(WayPropertySet props) {
- WayProperties allWayProperties = withModes(ALL).build();
- WayProperties noneWayProperties = withModes(NONE).build();
- WayProperties pedestrianWayProperties = withModes(PEDESTRIAN).build();
- WayProperties pedestrianAndBicycleWayProperties = withModes(PEDESTRIAN_AND_BICYCLE).build();
- /* no bicycle tags */
-
- /* NONE */
- props.setProperties("mtb:scale=3", noneWayProperties);
- props.setProperties("mtb:scale=4", noneWayProperties);
- props.setProperties("mtb:scale=5", noneWayProperties);
- props.setProperties("mtb:scale=6", noneWayProperties);
-
- /* PEDESTRIAN */
- props.setProperties("highway=corridor", pedestrianWayProperties);
- props.setProperties("highway=steps", pedestrianWayProperties);
- props.setProperties("highway=crossing", pedestrianWayProperties);
- props.setProperties("highway=platform", pedestrianWayProperties);
- props.setProperties("public_transport=platform", pedestrianWayProperties);
- props.setProperties("railway=platform", pedestrianWayProperties);
- props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties);
- props.setProperties("mtb:scale=1", pedestrianWayProperties);
- props.setProperties("mtb:scale=2", pedestrianWayProperties);
-
- /* PEDESTRIAN_AND_BICYCLE */
- props.setProperties("mtb:scale=0", pedestrianAndBicycleWayProperties);
- props.setProperties("highway=cycleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.6));
- props.setProperties("highway=path", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75));
- props.setProperties("highway=pedestrian", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9));
- props.setProperties("highway=footway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1));
- props.setProperties("highway=bridleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3));
-
- /* ALL */
- props.setProperties("highway=living_street", withModes(ALL).bicycleSafety(0.9));
- props.setProperties("highway=unclassified", allWayProperties);
- props.setProperties("highway=road", allWayProperties);
- props.setProperties("highway=byway", withModes(ALL).bicycleSafety(1.3));
- props.setProperties("highway=track", withModes(ALL).bicycleSafety(1.3));
- props.setProperties("highway=service", withModes(ALL).bicycleSafety(1.1));
- props.setProperties("highway=residential", withModes(ALL).bicycleSafety(0.98));
- props.setProperties("highway=residential_link", withModes(ALL).bicycleSafety(0.98));
- props.setProperties("highway=tertiary", allWayProperties);
- props.setProperties("highway=tertiary_link", allWayProperties);
- props.setProperties("highway=secondary", withModes(ALL).bicycleSafety(1.5));
- props.setProperties("highway=secondary_link", withModes(ALL).bicycleSafety(1.5));
- props.setProperties("highway=primary", withModes(ALL).bicycleSafety(2.06));
- props.setProperties("highway=primary_link", withModes(ALL).bicycleSafety(2.06));
-
- /* DRIVING ONLY */
- // trunk and motorway links are often short distances and necessary connections
- props.setProperties("highway=trunk_link", withModes(CAR).bicycleSafety(2.06));
- props.setProperties("highway=motorway_link", withModes(CAR).bicycleSafety(2.06));
-
- props.setProperties("highway=trunk", withModes(CAR).bicycleSafety(7.47));
- props.setProperties("highway=motorway", withModes(CAR).bicycleSafety(8));
-
- /* cycleway=lane */
- props.setProperties(
- "highway=*;cycleway=lane",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.87)
- );
- props.setProperties("highway=service;cycleway=lane", withModes(ALL).bicycleSafety(0.77));
- props.setProperties("highway=residential;cycleway=lane", withModes(ALL).bicycleSafety(0.77));
- props.setProperties(
- "highway=residential_link;cycleway=lane",
- withModes(ALL).bicycleSafety(0.77)
- );
- props.setProperties("highway=tertiary;cycleway=lane", withModes(ALL).bicycleSafety(0.87));
- props.setProperties("highway=tertiary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.87));
- props.setProperties("highway=secondary;cycleway=lane", withModes(ALL).bicycleSafety(0.96));
- props.setProperties("highway=secondary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.96));
- props.setProperties("highway=primary;cycleway=lane", withModes(ALL).bicycleSafety(1.15));
- props.setProperties("highway=primary_link;cycleway=lane", withModes(ALL).bicycleSafety(1.15));
-
- /* BICYCLE_AND_CAR */
- props.setProperties(
- "highway=trunk;cycleway=lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.5)
- );
- props.setProperties(
- "highway=trunk_link;cycleway=lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.15)
- );
- props.setProperties(
- "highway=motorway;cycleway=lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2)
- );
- props.setProperties(
- "highway=motorway_link;cycleway=lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.15)
- );
-
- /* cycleway=share_busway */
- props.setProperties(
- "highway=*;cycleway=share_busway",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.92)
- );
- props.setProperties(
- "highway=service;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.85)
- );
- props.setProperties(
- "highway=residential;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.85)
- );
- props.setProperties(
- "highway=residential_link;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.85)
- );
- props.setProperties(
- "highway=tertiary;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.92)
- );
- props.setProperties(
- "highway=tertiary_link;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.92)
- );
- props.setProperties(
- "highway=secondary;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.99)
- );
- props.setProperties(
- "highway=secondary_link;cycleway=share_busway",
- withModes(ALL).bicycleSafety(0.99)
- );
- props.setProperties(
- "highway=primary;cycleway=share_busway",
- withModes(ALL).bicycleSafety(1.25)
- );
- props.setProperties(
- "highway=primary_link;cycleway=share_busway",
- withModes(ALL).bicycleSafety(1.25)
- );
- props.setProperties(
- "highway=trunk;cycleway=share_busway",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.75)
- );
- props.setProperties(
- "highway=trunk_link;cycleway=share_busway",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.25)
- );
- props.setProperties(
- "highway=motorway;cycleway=share_busway",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2.5)
- );
- props.setProperties(
- "highway=motorway_link;cycleway=share_busway",
- withModes(BICYCLE_AND_CAR).bicycleSafety(1.25)
- );
-
- /* cycleway=opposite_lane */
- props.setProperties(
- "highway=*;cycleway=opposite_lane",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 0.87)
- );
- props.setProperties(
- "highway=service;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(1.1, 0.77)
- );
- props.setProperties(
- "highway=residential;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(0.98, 0.77)
- );
- props.setProperties(
- "highway=residential_link;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(0.98, 0.77)
- );
- props.setProperties(
- "highway=tertiary;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(1, 0.87)
- );
- props.setProperties(
- "highway=tertiary_link;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(1, 0.87)
- );
- props.setProperties(
- "highway=secondary;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(1.5, 0.96)
- );
- props.setProperties(
- "highway=secondary_link;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(1.5, 0.96)
- );
- props.setProperties(
- "highway=primary;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(2.06, 1.15)
- );
- props.setProperties(
- "highway=primary_link;cycleway=opposite_lane",
- withModes(ALL).bicycleSafety(2.06, 1.15)
- );
- props.setProperties(
- "highway=trunk;cycleway=opposite_lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 1.5)
- );
- props.setProperties(
- "highway=trunk_link;cycleway=opposite_lane",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 1.15)
- );
-
- /* cycleway=track */
- props.setProperties(
- "highway=*;cycleway=track",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
- );
- props.setProperties("highway=service;cycleway=track", withModes(ALL).bicycleSafety(0.65));
- props.setProperties("highway=residential;cycleway=track", withModes(ALL).bicycleSafety(0.65));
- props.setProperties(
- "highway=residential_link;cycleway=track",
- withModes(ALL).bicycleSafety(0.65)
- );
- props.setProperties("highway=tertiary;cycleway=track", withModes(ALL).bicycleSafety(0.75));
- props.setProperties("highway=tertiary_link;cycleway=track", withModes(ALL).bicycleSafety(0.75));
- props.setProperties("highway=secondary;cycleway=track", withModes(ALL).bicycleSafety(0.8));
- props.setProperties("highway=secondary_link;cycleway=track", withModes(ALL).bicycleSafety(0.8));
- props.setProperties("highway=primary;cycleway=track", withModes(ALL).bicycleSafety(0.85));
- props.setProperties("highway=primary_link;cycleway=track", withModes(ALL).bicycleSafety(0.85));
- props.setProperties(
- "highway=trunk;cycleway=track",
- withModes(BICYCLE_AND_CAR).bicycleSafety(0.95)
- );
- props.setProperties(
- "highway=trunk_link;cycleway=track",
- withModes(BICYCLE_AND_CAR).bicycleSafety(0.85)
- );
-
- /* cycleway=opposite_track */
- props.setProperties(
- "highway=*;cycleway=opposite_track",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.0, 0.75)
- );
- props.setProperties(
- "highway=service;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(1.1, 0.65)
- );
- props.setProperties(
- "highway=residential;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(0.98, 0.65)
- );
- props.setProperties(
- "highway=residential_link;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(0.98, 0.65)
- );
- props.setProperties(
- "highway=tertiary;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(1, 0.75)
- );
- props.setProperties(
- "highway=tertiary_link;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(1, 0.75)
- );
- props.setProperties(
- "highway=secondary;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(1.5, 0.8)
- );
- props.setProperties(
- "highway=secondary_link;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(1.5, 0.8)
- );
- props.setProperties(
- "highway=primary;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(2.06, 0.85)
- );
- props.setProperties(
- "highway=primary_link;cycleway=opposite_track",
- withModes(ALL).bicycleSafety(2.06, 0.85)
- );
- props.setProperties(
- "highway=trunk;cycleway=opposite_track",
- withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 0.95)
- );
- props.setProperties(
- "highway=trunk_link;cycleway=opposite_track",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 0.85)
- );
-
- /* cycleway=shared_lane a.k.a. bike boulevards or neighborhood greenways */
- props.setProperties(
- "highway=*;cycleway=shared_lane",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.77)
- );
- props.setProperties("highway=service;cycleway=shared_lane", withModes(ALL).bicycleSafety(0.73));
- props.setProperties(
- "highway=residential;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(0.77)
- );
- props.setProperties(
- "highway=residential_link;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(0.77)
- );
- props.setProperties(
- "highway=tertiary;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(0.83)
- );
- props.setProperties(
- "highway=tertiary_link;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(0.83)
- );
- props.setProperties(
- "highway=secondary;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(1.25)
- );
- props.setProperties(
- "highway=secondary_link;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(1.25)
- );
- props.setProperties("highway=primary;cycleway=shared_lane", withModes(ALL).bicycleSafety(1.75));
- props.setProperties(
- "highway=primary_link;cycleway=shared_lane",
- withModes(ALL).bicycleSafety(1.75)
- );
-
- /* cycleway=opposite */
- props.setProperties(
- "highway=*;cycleway=opposite",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 1.4)
- );
- props.setProperties("highway=service;cycleway=opposite", withModes(ALL).bicycleSafety(1.1));
- props.setProperties(
- "highway=residential;cycleway=opposite",
- withModes(ALL).bicycleSafety(0.98)
- );
- props.setProperties(
- "highway=residential_link;cycleway=opposite",
- withModes(ALL).bicycleSafety(0.98)
- );
- props.setProperties("highway=tertiary;cycleway=opposite", allWayProperties);
- props.setProperties("highway=tertiary_link;cycleway=opposite", allWayProperties);
- props.setProperties(
- "highway=secondary;cycleway=opposite",
- withModes(ALL).bicycleSafety(1.5, 1.71)
- );
- props.setProperties(
- "highway=secondary_link;cycleway=opposite",
- withModes(ALL).bicycleSafety(1.5, 1.71)
- );
- props.setProperties(
- "highway=primary;cycleway=opposite",
- withModes(ALL).bicycleSafety(2.06, 2.99)
- );
- props.setProperties(
- "highway=primary_link;cycleway=opposite",
- withModes(ALL).bicycleSafety(2.06, 2.99)
- );
-
- /*
- * path designed for bicycles (should be treated exactly as a cycleway is), this is a multi-use path (MUP)
- */
- props.setProperties(
- "highway=path;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.60)
- );
-
- /* special cases for footway, pedestrian and bicycles */
- props.setProperties(
- "highway=footway;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
- );
- props.setProperties(
- "highway=footway;bicycle=yes;area=yes",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9)
- );
- props.setProperties(
- "highway=pedestrian;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
- );
-
- /* sidewalk and crosswalk */
- props.setProperties(
- "footway=sidewalk;highway=footway;bicycle=yes",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5)
- );
- props.setProperties(
- "footway=sidewalk;highway=footway;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)
- );
- props.setProperties(
- "highway=footway;footway=crossing",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5)
- );
- props.setProperties(
- "highway=footway;footway=crossing;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)
- );
-
- /*
- * bicycles on tracks (tracks are defined in OSM as: Roads for agricultural use, gravel roads in the forest etc.; usually unpaved/unsealed but
- * may occasionally apply to paved tracks as well.)
- */
- props.setProperties(
- "highway=track;bicycle=yes",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18)
- );
- props.setProperties(
- "highway=track;bicycle=designated",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99)
- );
- props.setProperties(
- "highway=track;bicycle=yes;surface=*",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18)
- );
- props.setProperties(
- "highway=track;bicycle=designated;surface=*",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99)
- );
- /* this is to avoid double counting since tracks are almost of surface type that is penalized */
- props.setProperties(
- "highway=track;surface=*",
- withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3)
- );
-
- /* bicycle=designated, but no bike infrastructure is present */
- props.setProperties("highway=*;bicycle=designated", withModes(ALL).bicycleSafety(0.97));
- props.setProperties("highway=service;bicycle=designated", withModes(ALL).bicycleSafety(0.84));
- props.setProperties(
- "highway=residential;bicycle=designated",
- withModes(ALL).bicycleSafety(0.95)
- );
- props.setProperties(
- "highway=unclassified;bicycle=designated",
- withModes(ALL).bicycleSafety(0.95)
- );
- props.setProperties(
- "highway=residential_link;bicycle=designated",
- withModes(ALL).bicycleSafety(0.95)
- );
- props.setProperties("highway=tertiary;bicycle=designated", withModes(ALL).bicycleSafety(0.97));
- props.setProperties(
- "highway=tertiary_link;bicycle=designated",
- withModes(ALL).bicycleSafety(0.97)
- );
- props.setProperties("highway=secondary;bicycle=designated", withModes(ALL).bicycleSafety(1.46));
- props.setProperties(
- "highway=secondary_link;bicycle=designated",
- withModes(ALL).bicycleSafety(1.46)
- );
- props.setProperties("highway=primary;bicycle=designated", withModes(ALL).bicycleSafety(2));
- props.setProperties("highway=primary_link;bicycle=designated", withModes(ALL).bicycleSafety(2));
- props.setProperties(
- "highway=trunk;bicycle=designated",
- withModes(BICYCLE_AND_CAR).bicycleSafety(7.25)
- );
- props.setProperties(
- "highway=trunk_link;bicycle=designated",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2)
- );
- props.setProperties(
- "highway=motorway;bicycle=designated",
- withModes(BICYCLE_AND_CAR).bicycleSafety(7.76)
- );
- props.setProperties(
- "highway=motorway_link;bicycle=designated",
- withModes(BICYCLE_AND_CAR).bicycleSafety(2)
- );
-
- // We assume highway/cycleway of a cycle network to be safer (for bicycle network relations, their network is copied to way in postLoad)
- // this uses a OR since you don't want to apply the safety multiplier more than once.
- // Signed bicycle_roads and cyclestreets exist in traffic codes of some european countries.
- // Tagging in OSM and on-the-ground use is varied, so just assume they are "somehow safer", too.
- // In my test area ways often, but not always, have both tags.
- // For simplicity these two concepts are handled together.
- props.setMixinProperties(
- new LogicalOrSpecifier(
- "lcn=yes",
- "rcn=yes",
- "ncn=yes",
- "bicycle_road=yes",
- "cyclestreet=yes"
- ),
- ofBicycleSafety(0.7)
- );
-
- /*
- * Automobile speeds in the United States: Based on my (mattwigway) personal experience, primarily in California
- */
- props.setCarSpeed("highway=motorway", 29); // 29 m/s ~= 65 mph
- props.setCarSpeed("highway=motorway_link", 15); // ~= 35 mph
- props.setCarSpeed("highway=trunk", 24.6f); // ~= 55 mph
- props.setCarSpeed("highway=trunk_link", 15); // ~= 35 mph
- props.setCarSpeed("highway=primary", 20); // ~= 45 mph
- props.setCarSpeed("highway=primary_link", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=secondary", 15); // ~= 35 mph
- props.setCarSpeed("highway=secondary_link", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=tertiary", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=tertiary_link", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=living_street", 2.2f); // ~= 5 mph
-
- // generally, these will not allow cars at all, but the docs say
- // "For roads used mainly/exclusively for pedestrians . . . which may allow access by
- // motorised vehicles only for very limited periods of the day."
- // http://wiki.openstreetmap.org/wiki/Key:highway
- // This of course makes the street network time-dependent
- props.setCarSpeed("highway=pedestrian", 2.2f); // ~= 5 mph
-
- props.setCarSpeed("highway=residential", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=unclassified", 11.2f); // ~= 25 mph
- props.setCarSpeed("highway=service", 6.7f); // ~= 15 mph
- props.setCarSpeed("highway=track", 4.5f); // ~= 10 mph
- props.setCarSpeed("highway=road", 11.2f); // ~= 25 mph
-
- // default ~= 25 mph
- props.defaultCarSpeed = 11.2f;
- // 38 m/s ~= 85 mph ~= 137 kph
- props.maxPossibleCarSpeed = 38f;
-
- /* special situations */
-
- /*
- * cycleway:left/right=lane/track/shared_lane permutations - no longer needed because left/right matching algorithm does this
- */
-
- /* cycleway:left=lane */
- /* cycleway:right=track */
- /* cycleway:left=track */
- /* cycleway:right=shared_lane */
- /* cycleway:left=shared_lane */
- /* cycleway:right=lane, cycleway:left=track */
- /* cycleway:right=lane, cycleway:left=shared_lane */
- /* cycleway:right=track, cycleway:left=lane */
- /* cycleway:right=track, cycleway:left=shared_lane */
- /* cycleway:right=shared_lane, cycleway:left=lane */
- /* cycleway:right=shared_lane, cycleway:left=track */
-
- /* surface=* mixins */
-
- /*
- * The following tags have been removed from surface weights because they are no more of an impedence to bicycling than a paved surface
- * surface=paving_stones surface=fine_gravel (sounds counter-intuitive but see the definition on the OSM Wiki) surface=tartan (this what
- * running tracks are usually made of)
- */
-
- props.setMixinProperties("surface=unpaved", ofBicycleSafety(1.18));
- props.setMixinProperties("surface=compacted", ofBicycleSafety(1.18));
- props.setMixinProperties("surface=wood", ofBicycleSafety(1.18));
-
- props.setMixinProperties("surface=cobblestone", ofBicycleSafety(1.3));
- props.setMixinProperties("surface=sett", ofBicycleSafety(1.3));
- props.setMixinProperties("surface=unhewn_cobblestone", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=grass_paver", ofBicycleSafety(1.3));
- props.setMixinProperties("surface=pebblestone", ofBicycleSafety(1.3));
- // Can be slick if wet, but otherwise not unfavorable to bikes
- props.setMixinProperties("surface=metal", ofBicycleSafety(1.3));
- props.setMixinProperties("surface=ground", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=dirt", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=earth", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=grass", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=mud", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=woodchip", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=gravel", ofBicycleSafety(1.5));
- props.setMixinProperties("surface=artifical_turf", ofBicycleSafety(1.5));
-
- /* sand is deadly for bikes */
- props.setMixinProperties("surface=sand", ofBicycleSafety(100));
-
- /* Portland-local mixins */
-
- props.setMixinProperties("foot=discouraged", ofWalkSafety(3));
- props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3));
-
- props.setMixinProperties("foot=use_sidepath", ofWalkSafety(5));
- props.setMixinProperties("bicycle=use_sidepath", ofBicycleSafety(5));
-
- populateNotesAndNames(props);
-
- // slope overrides
- props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true);
- props.setSlopeOverride(new BestMatchSpecifier("embankment=*"), true);
- props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true);
- props.setSlopeOverride(new BestMatchSpecifier("tunnel=*"), true);
- props.setSlopeOverride(new BestMatchSpecifier("location=underground"), true);
- props.setSlopeOverride(new BestMatchSpecifier("indoor=yes"), true);
- }
-
- public void populateNotesAndNames(WayPropertySet props) {
- /* and the notes */
- // TODO: The curly brackets in the string below mean that the CreativeNamer should substitute in OSM tag values.
- // However they are not taken into account when passed to the translation function.
- // props.createNotes("wheelchair:description=*", "{wheelchair:description}", StreetNotesService.WHEELCHAIR_MATCHER);
- // TODO: The two entries below produce lots of spurious notes (because of OSM mapper comments)
- // props.createNotes("note=*", "{note}", StreetNotesService.ALWAYS_MATCHER);
- // props.createNotes("notes=*", "{notes}", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes(
- "RLIS:bicycle=caution_area",
- "note.caution",
- StreetNotesService.BICYCLE_MATCHER
- );
- props.createNotes(
- "CCGIS:bicycle=caution_area",
- "note.caution",
- StreetNotesService.BICYCLE_MATCHER
- );
- // TODO: Maybe we should apply the following notes only for car/bike
- props.createNotes("surface=unpaved", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes(
- "surface=compacted",
- "note.unpaved_surface",
- StreetNotesService.ALWAYS_MATCHER
- );
- props.createNotes("surface=ground", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes("surface=dirt", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes("surface=earth", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes("surface=grass", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes("surface=mud", "note.muddy_surface", StreetNotesService.ALWAYS_MATCHER);
- props.createNotes("toll=yes", "note.toll", StreetNotesService.DRIVING_MATCHER);
- props.createNotes("toll:motorcar=yes", "note.toll", StreetNotesService.DRIVING_MATCHER);
-
- /* and some names */
- // Basics
- props.createNames("highway=cycleway", "name.bike_path");
- props.createNames("cycleway=track", "name.bike_path");
- props.createNames("highway=pedestrian", "name.pedestrian_path");
- props.createNames("highway=pedestrian;area=yes", "name.pedestrian_area");
- props.createNames("highway=path", "name.path");
- props.createNames("highway=footway", "name.pedestrian_path");
- props.createNames("highway=bridleway", "name.bridleway");
- props.createNames("highway=footway;bicycle=no", "name.pedestrian_path");
-
- // Platforms
- props.createNames("otp:route_ref=*", "name.otp_route_ref");
- props.createNames("highway=platform;ref=*", "name.platform_ref");
- props.createNames("railway=platform;ref=*", "name.platform_ref");
- props.createNames("railway=platform;highway=footway;footway=sidewalk", "name.platform");
- props.createNames("railway=platform;highway=path;path=sidewalk", "name.platform");
- props.createNames("railway=platform;highway=pedestrian", "name.platform");
- props.createNames("railway=platform;highway=path", "name.platform");
- props.createNames("railway=platform;highway=footway", "name.platform");
- props.createNames("highway=platform", "name.platform");
- props.createNames("railway=platform", "name.platform");
- props.createNames("railway=platform;highway=footway;bicycle=no", "name.platform");
-
- // Bridges/Tunnels
- props.createNames("highway=pedestrian;bridge=*", "name.footbridge");
- props.createNames("highway=path;bridge=*", "name.footbridge");
- props.createNames("highway=footway;bridge=*", "name.footbridge");
-
- props.createNames("highway=pedestrian;tunnel=*", "name.underpass");
- props.createNames("highway=path;tunnel=*", "name.underpass");
- props.createNames("highway=footway;tunnel=*", "name.underpass");
-
- // Basic Mappings
- props.createNames("highway=motorway", "name.road");
- props.createNames("highway=motorway_link", "name.ramp");
- props.createNames("highway=trunk", "name.road");
- props.createNames("highway=trunk_link", "name.ramp");
-
- props.createNames("highway=primary", "name.road");
- props.createNames("highway=primary_link", "name.link");
- props.createNames("highway=secondary", "name.road");
- props.createNames("highway=secondary_link", "name.link");
- props.createNames("highway=tertiary", "name.road");
- props.createNames("highway=tertiary_link", "name.link");
- props.createNames("highway=unclassified", "name.road");
- props.createNames("highway=residential", "name.road");
- props.createNames("highway=living_street", "name.road");
- props.createNames("highway=road", "name.road");
- props.createNames("highway=service", "name.service_road");
- props.createNames("highway=service;service=alley", "name.alley");
- props.createNames("highway=service;service=parking_aisle", "name.parking_aisle");
- props.createNames("highway=byway", "name.byway");
- props.createNames("highway=track", "name.track");
-
- props.createNames("highway=footway;footway=sidewalk", "name.sidewalk");
- props.createNames("highway=path;path=sidewalk", "name.sidewalk");
-
- props.createNames("highway=steps", "name.steps");
-
- props.createNames("amenity=bicycle_parking;name=*", "name.bicycle_parking_name");
- props.createNames("amenity=bicycle_parking", "name.bicycle_parking");
-
- props.createNames("amenity=parking;name=*", "name.park_and_ride_name");
- props.createNames("amenity=parking", "name.park_and_ride_station");
- }
-}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java
index e796ebff17f..e9ac9478552 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/FinlandMapper.java
@@ -8,6 +8,7 @@
import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN;
import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE;
+import java.util.Set;
import org.opentripplanner.framework.functional.FunctionUtils.TriFunction;
import org.opentripplanner.osm.model.OsmWithTags;
import org.opentripplanner.osm.wayproperty.WayPropertySet;
@@ -23,9 +24,16 @@
*
* @author juusokor
* @see OsmTagMapper
- * @see DefaultMapper
*/
-class FinlandMapper implements OsmTagMapper {
+class FinlandMapper extends OsmTagMapper {
+
+ private static final Set NOTHROUGH_DRIVING_TAGS = Set.of(
+ "parking_aisle",
+ "driveway",
+ "alley",
+ "emergency_access",
+ "drive-through"
+ );
@Override
public void populateProperties(WayPropertySet props) {
@@ -203,12 +211,11 @@ else if (speedLimit <= 16.65f) {
// ~= 16 kph
props.setCarSpeed("highway=track", 4.5f);
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
@Override
- public boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isBicycleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String bicycle = way.getTag("bicycle");
return (
isVehicleThroughTrafficExplicitlyDisallowed(way) ||
@@ -217,8 +224,16 @@ public boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
}
@Override
- public boolean isWalkNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isWalkThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String foot = way.getTag("foot");
return isGeneralNoThroughTraffic(way) || doesTagValueDisallowThroughTraffic(foot);
}
+
+ @Override
+ public boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ if (super.isMotorVehicleThroughTrafficExplicitlyDisallowed(way)) {
+ return true;
+ }
+ return way.isOneOfTags("service", NOTHROUGH_DRIVING_TAGS);
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java
index 70a5bd593aa..af56b572bd8 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/GermanyMapper.java
@@ -15,9 +15,8 @@
* networks.
*
* @see OsmTagMapper
- * @see DefaultMapper
*/
-class GermanyMapper implements OsmTagMapper {
+class GermanyMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -88,7 +87,6 @@ public void populateProperties(WayPropertySet props) {
props.setProperties("highway=unclassified;cycleway=lane", withModes(ALL).bicycleSafety(0.87));
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java
index 47bd5164d1f..755f5864ba2 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/HamburgMapper.java
@@ -7,7 +7,6 @@
*
* @see GermanyMapper
* @see OsmTagMapper
- * @see DefaultMapper
*
* @author Maintained by HBT (geofox-team@hbt.de)
*/
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java
index 7e4aba9da4e..1d24dbafffb 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/HoustonMapper.java
@@ -14,7 +14,7 @@
* 1. In Houston we want to disallow usage of downtown pedestrian tunnel system.
*/
-class HoustonMapper implements OsmTagMapper {
+class HoustonMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -26,11 +26,9 @@ public void populateProperties(WayPropertySet props) {
new ExactMatchSpecifier("highway=footway;layer=-1;tunnel=yes;indoor=yes"),
withModes(NONE)
);
-
// Max speed limit in Texas is 38 m/s ~= 85 mph ~= 137 kph
props.maxPossibleCarSpeed = 38f;
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java
index 9e06c0aa591..c37de8533c6 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/NorwayMapper.java
@@ -24,9 +24,9 @@
*
* @author seime
* @see OsmTagMapper
- * @see DefaultMapper
+ * @see OsmTagMapper
*/
-class NorwayMapper implements OsmTagMapper {
+class NorwayMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -621,7 +621,7 @@ else if (speedLimit >= 11.1f) {
props.defaultCarSpeed = 22.22f; // 80 km/h
props.maxPossibleCarSpeed = 30.56f; // 110 km/h
- new DefaultMapper().populateNotesAndNames(props);
+ super.populateNotesAndNames(props);
props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true);
props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true);
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java
index 9bf0ed2d20d..2df3c22d6a9 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapper.java
@@ -1,18 +1,727 @@
package org.opentripplanner.osm.tagmapping;
+import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofBicycleSafety;
+import static org.opentripplanner.osm.wayproperty.MixinPropertiesBuilder.ofWalkSafety;
+import static org.opentripplanner.osm.wayproperty.WayPropertiesBuilder.withModes;
+import static org.opentripplanner.street.model.StreetTraversalPermission.ALL;
+import static org.opentripplanner.street.model.StreetTraversalPermission.BICYCLE_AND_CAR;
+import static org.opentripplanner.street.model.StreetTraversalPermission.CAR;
+import static org.opentripplanner.street.model.StreetTraversalPermission.NONE;
+import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN;
+import static org.opentripplanner.street.model.StreetTraversalPermission.PEDESTRIAN_AND_BICYCLE;
+
import org.opentripplanner.osm.model.OsmWithTags;
+import org.opentripplanner.osm.wayproperty.WayProperties;
import org.opentripplanner.osm.wayproperty.WayPropertySet;
+import org.opentripplanner.osm.wayproperty.specifier.BestMatchSpecifier;
+import org.opentripplanner.osm.wayproperty.specifier.Condition;
+import org.opentripplanner.osm.wayproperty.specifier.ExactMatchSpecifier;
+import org.opentripplanner.osm.wayproperty.specifier.LogicalOrSpecifier;
+import org.opentripplanner.routing.services.notes.StreetNotesService;
/**
- * Interface for populating a {@link WayPropertySet} that determine how OSM streets can be traversed
- * in various modes and named.
+ * This factory class provides a default collection of {@link WayProperties} that determine how OSM
+ * streets can be traversed in various modes.
+ *
+ * Circa January 2011, Grant and Mele at TriMet undertook proper testing of bike (and transit)
+ * routing, and worked with David Turner on assigning proper weights to different facility types.
+ * The weights in this file grew organically from trial and error, and are the result of months of
+ * testing and tweaking the routes that OTP returned, as well as actually walking/biking these
+ * routes and making changes based on those experiences. This set of weights should be a great
+ * starting point for others to use, but they are to some extent tailored to the situation in
+ * Portland and people shouldn't hesitate to adjust them to for their own instance.
+ *
+ * The rules for assigning WayProperties to OSM ways are explained in. The final tie breaker if two
+ * Pickers both match is the sequence that the properties are added in this file: if all else is
+ * equal the 'props.setProperties' statement that is closer to the top of the page will prevail over
+ * those lower down the page.
+ *
+ * Foot and bicycle permissions are also addressed in OpenStreetMapGraphBuilderImpl.Handler#getPermissionsForEntity().
+ * For instance, if a way that normally does not permit walking based on its tag matches (the
+ * prevailing 'props.setProperties' statement) has a 'foot=yes' tag the permissions are overridden
+ * and walking is allowed on that way.
+ *
*
- * @author bdferris, novalis, seime
+ * @author bdferris, novalis
*/
-public interface OsmTagMapper {
- void populateProperties(WayPropertySet wayPropertySet);
- default boolean doesTagValueDisallowThroughTraffic(String tagValue) {
+public class OsmTagMapper {
+
+ /* Populate properties on existing WayPropertySet */
+ public void populateProperties(WayPropertySet props) {
+ WayProperties allWayProperties = withModes(ALL).build();
+ WayProperties noneWayProperties = withModes(NONE).build();
+ WayProperties pedestrianWayProperties = withModes(PEDESTRIAN).build();
+ WayProperties pedestrianAndBicycleWayProperties = withModes(PEDESTRIAN_AND_BICYCLE).build();
+ /* no bicycle tags */
+
+ /* NONE */
+ props.setProperties("mtb:scale=3", noneWayProperties);
+ props.setProperties("mtb:scale=4", noneWayProperties);
+ props.setProperties("mtb:scale=5", noneWayProperties);
+ props.setProperties("mtb:scale=6", noneWayProperties);
+
+ /* PEDESTRIAN */
+ props.setProperties("highway=corridor", pedestrianWayProperties);
+ props.setProperties("highway=steps", pedestrianWayProperties);
+ props.setProperties("highway=crossing", pedestrianWayProperties);
+ props.setProperties("highway=platform", pedestrianWayProperties);
+ props.setProperties("public_transport=platform", pedestrianWayProperties);
+ props.setProperties("railway=platform", pedestrianWayProperties);
+ props.setProperties("footway=sidewalk;highway=footway", pedestrianWayProperties);
+ props.setProperties("mtb:scale=1", pedestrianWayProperties);
+ props.setProperties("mtb:scale=2", pedestrianWayProperties);
+
+ /* PEDESTRIAN_AND_BICYCLE */
+ props.setProperties("mtb:scale=0", pedestrianAndBicycleWayProperties);
+ props.setProperties("highway=cycleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.6));
+ props.setProperties("highway=path", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75));
+ props.setProperties("highway=pedestrian", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9));
+ props.setProperties("highway=footway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1));
+ props.setProperties("highway=bridleway", withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3));
+
+ /* ALL */
+ props.setProperties("highway=living_street", withModes(ALL).bicycleSafety(0.9));
+ props.setProperties("highway=unclassified", allWayProperties);
+ props.setProperties("highway=road", allWayProperties);
+ props.setProperties("highway=byway", withModes(ALL).bicycleSafety(1.3));
+ props.setProperties("highway=track", withModes(ALL).bicycleSafety(1.3));
+ props.setProperties("highway=service", withModes(ALL).bicycleSafety(1.1));
+ props.setProperties("highway=residential", withModes(ALL).bicycleSafety(0.98));
+ props.setProperties("highway=residential_link", withModes(ALL).bicycleSafety(0.98));
+ props.setProperties("highway=tertiary", allWayProperties);
+ props.setProperties("highway=tertiary_link", allWayProperties);
+ props.setProperties("highway=secondary", withModes(ALL).bicycleSafety(1.5));
+ props.setProperties("highway=secondary_link", withModes(ALL).bicycleSafety(1.5));
+ props.setProperties("highway=primary", withModes(ALL).bicycleSafety(2.06));
+ props.setProperties("highway=primary_link", withModes(ALL).bicycleSafety(2.06));
+
+ /* DRIVING ONLY */
+ // trunk and motorway links are often short distances and necessary connections
+ props.setProperties("highway=trunk_link", withModes(CAR).bicycleSafety(2.06));
+ props.setProperties("highway=motorway_link", withModes(CAR).bicycleSafety(2.06));
+
+ props.setProperties("highway=trunk", withModes(CAR).bicycleSafety(7.47));
+ props.setProperties("highway=motorway", withModes(CAR).bicycleSafety(8));
+
+ /* cycleway=lane */
+ props.setProperties(
+ "highway=*;cycleway=lane",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.87)
+ );
+ props.setProperties("highway=service;cycleway=lane", withModes(ALL).bicycleSafety(0.77));
+ props.setProperties("highway=residential;cycleway=lane", withModes(ALL).bicycleSafety(0.77));
+ props.setProperties(
+ "highway=residential_link;cycleway=lane",
+ withModes(ALL).bicycleSafety(0.77)
+ );
+ props.setProperties("highway=tertiary;cycleway=lane", withModes(ALL).bicycleSafety(0.87));
+ props.setProperties("highway=tertiary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.87));
+ props.setProperties("highway=secondary;cycleway=lane", withModes(ALL).bicycleSafety(0.96));
+ props.setProperties("highway=secondary_link;cycleway=lane", withModes(ALL).bicycleSafety(0.96));
+ props.setProperties("highway=primary;cycleway=lane", withModes(ALL).bicycleSafety(1.15));
+ props.setProperties("highway=primary_link;cycleway=lane", withModes(ALL).bicycleSafety(1.15));
+
+ /* BICYCLE_AND_CAR */
+ props.setProperties(
+ "highway=trunk;cycleway=lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.5)
+ );
+ props.setProperties(
+ "highway=trunk_link;cycleway=lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.15)
+ );
+ props.setProperties(
+ "highway=motorway;cycleway=lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2)
+ );
+ props.setProperties(
+ "highway=motorway_link;cycleway=lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.15)
+ );
+
+ /* cycleway=share_busway */
+ props.setProperties(
+ "highway=*;cycleway=share_busway",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.92)
+ );
+ props.setProperties(
+ "highway=service;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.85)
+ );
+ props.setProperties(
+ "highway=residential;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.85)
+ );
+ props.setProperties(
+ "highway=residential_link;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.85)
+ );
+ props.setProperties(
+ "highway=tertiary;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.92)
+ );
+ props.setProperties(
+ "highway=tertiary_link;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.92)
+ );
+ props.setProperties(
+ "highway=secondary;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.99)
+ );
+ props.setProperties(
+ "highway=secondary_link;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(0.99)
+ );
+ props.setProperties(
+ "highway=primary;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(1.25)
+ );
+ props.setProperties(
+ "highway=primary_link;cycleway=share_busway",
+ withModes(ALL).bicycleSafety(1.25)
+ );
+ props.setProperties(
+ "highway=trunk;cycleway=share_busway",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.75)
+ );
+ props.setProperties(
+ "highway=trunk_link;cycleway=share_busway",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.25)
+ );
+ props.setProperties(
+ "highway=motorway;cycleway=share_busway",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2.5)
+ );
+ props.setProperties(
+ "highway=motorway_link;cycleway=share_busway",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(1.25)
+ );
+
+ /* cycleway=opposite_lane */
+ props.setProperties(
+ "highway=*;cycleway=opposite_lane",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 0.87)
+ );
+ props.setProperties(
+ "highway=service;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(1.1, 0.77)
+ );
+ props.setProperties(
+ "highway=residential;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(0.98, 0.77)
+ );
+ props.setProperties(
+ "highway=residential_link;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(0.98, 0.77)
+ );
+ props.setProperties(
+ "highway=tertiary;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(1, 0.87)
+ );
+ props.setProperties(
+ "highway=tertiary_link;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(1, 0.87)
+ );
+ props.setProperties(
+ "highway=secondary;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(1.5, 0.96)
+ );
+ props.setProperties(
+ "highway=secondary_link;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(1.5, 0.96)
+ );
+ props.setProperties(
+ "highway=primary;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(2.06, 1.15)
+ );
+ props.setProperties(
+ "highway=primary_link;cycleway=opposite_lane",
+ withModes(ALL).bicycleSafety(2.06, 1.15)
+ );
+ props.setProperties(
+ "highway=trunk;cycleway=opposite_lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 1.5)
+ );
+ props.setProperties(
+ "highway=trunk_link;cycleway=opposite_lane",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 1.15)
+ );
+
+ /* cycleway=track */
+ props.setProperties(
+ "highway=*;cycleway=track",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
+ );
+ props.setProperties("highway=service;cycleway=track", withModes(ALL).bicycleSafety(0.65));
+ props.setProperties("highway=residential;cycleway=track", withModes(ALL).bicycleSafety(0.65));
+ props.setProperties(
+ "highway=residential_link;cycleway=track",
+ withModes(ALL).bicycleSafety(0.65)
+ );
+ props.setProperties("highway=tertiary;cycleway=track", withModes(ALL).bicycleSafety(0.75));
+ props.setProperties("highway=tertiary_link;cycleway=track", withModes(ALL).bicycleSafety(0.75));
+ props.setProperties("highway=secondary;cycleway=track", withModes(ALL).bicycleSafety(0.8));
+ props.setProperties("highway=secondary_link;cycleway=track", withModes(ALL).bicycleSafety(0.8));
+ props.setProperties("highway=primary;cycleway=track", withModes(ALL).bicycleSafety(0.85));
+ props.setProperties("highway=primary_link;cycleway=track", withModes(ALL).bicycleSafety(0.85));
+ props.setProperties(
+ "highway=trunk;cycleway=track",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(0.95)
+ );
+ props.setProperties(
+ "highway=trunk_link;cycleway=track",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(0.85)
+ );
+
+ /* cycleway=opposite_track */
+ props.setProperties(
+ "highway=*;cycleway=opposite_track",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.0, 0.75)
+ );
+ props.setProperties(
+ "highway=service;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(1.1, 0.65)
+ );
+ props.setProperties(
+ "highway=residential;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(0.98, 0.65)
+ );
+ props.setProperties(
+ "highway=residential_link;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(0.98, 0.65)
+ );
+ props.setProperties(
+ "highway=tertiary;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(1, 0.75)
+ );
+ props.setProperties(
+ "highway=tertiary_link;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(1, 0.75)
+ );
+ props.setProperties(
+ "highway=secondary;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(1.5, 0.8)
+ );
+ props.setProperties(
+ "highway=secondary_link;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(1.5, 0.8)
+ );
+ props.setProperties(
+ "highway=primary;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(2.06, 0.85)
+ );
+ props.setProperties(
+ "highway=primary_link;cycleway=opposite_track",
+ withModes(ALL).bicycleSafety(2.06, 0.85)
+ );
+ props.setProperties(
+ "highway=trunk;cycleway=opposite_track",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(7.47, 0.95)
+ );
+ props.setProperties(
+ "highway=trunk_link;cycleway=opposite_track",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2.06, 0.85)
+ );
+
+ /* cycleway=shared_lane a.k.a. bike boulevards or neighborhood greenways */
+ props.setProperties(
+ "highway=*;cycleway=shared_lane",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.77)
+ );
+ props.setProperties("highway=service;cycleway=shared_lane", withModes(ALL).bicycleSafety(0.73));
+ props.setProperties(
+ "highway=residential;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(0.77)
+ );
+ props.setProperties(
+ "highway=residential_link;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(0.77)
+ );
+ props.setProperties(
+ "highway=tertiary;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(0.83)
+ );
+ props.setProperties(
+ "highway=tertiary_link;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(0.83)
+ );
+ props.setProperties(
+ "highway=secondary;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(1.25)
+ );
+ props.setProperties(
+ "highway=secondary_link;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(1.25)
+ );
+ props.setProperties("highway=primary;cycleway=shared_lane", withModes(ALL).bicycleSafety(1.75));
+ props.setProperties(
+ "highway=primary_link;cycleway=shared_lane",
+ withModes(ALL).bicycleSafety(1.75)
+ );
+
+ /* cycleway=opposite */
+ props.setProperties(
+ "highway=*;cycleway=opposite",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1, 1.4)
+ );
+ props.setProperties("highway=service;cycleway=opposite", withModes(ALL).bicycleSafety(1.1));
+ props.setProperties(
+ "highway=residential;cycleway=opposite",
+ withModes(ALL).bicycleSafety(0.98)
+ );
+ props.setProperties(
+ "highway=residential_link;cycleway=opposite",
+ withModes(ALL).bicycleSafety(0.98)
+ );
+ props.setProperties("highway=tertiary;cycleway=opposite", allWayProperties);
+ props.setProperties("highway=tertiary_link;cycleway=opposite", allWayProperties);
+ props.setProperties(
+ "highway=secondary;cycleway=opposite",
+ withModes(ALL).bicycleSafety(1.5, 1.71)
+ );
+ props.setProperties(
+ "highway=secondary_link;cycleway=opposite",
+ withModes(ALL).bicycleSafety(1.5, 1.71)
+ );
+ props.setProperties(
+ "highway=primary;cycleway=opposite",
+ withModes(ALL).bicycleSafety(2.06, 2.99)
+ );
+ props.setProperties(
+ "highway=primary_link;cycleway=opposite",
+ withModes(ALL).bicycleSafety(2.06, 2.99)
+ );
+
+ /*
+ * path designed for bicycles (should be treated exactly as a cycleway is), this is a multi-use path (MUP)
+ */
+ props.setProperties(
+ "highway=path;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.60)
+ );
+
+ /* special cases for footway, pedestrian and bicycles */
+ props.setProperties(
+ "highway=footway;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
+ );
+ props.setProperties(
+ new ExactMatchSpecifier("highway=footway;bicycle=yes;area=yes"),
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.9)
+ );
+ props.setProperties(
+ "highway=pedestrian;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.75)
+ );
+
+ /* sidewalk and crosswalk */
+ props.setProperties(
+ "footway=sidewalk;highway=footway;bicycle=yes",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5)
+ );
+ props.setProperties(
+ "footway=sidewalk;highway=footway;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)
+ );
+ props.setProperties(
+ "highway=footway;footway=crossing",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(2.5)
+ );
+ props.setProperties(
+ "highway=footway;footway=crossing;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.1)
+ );
+
+ /*
+ * bicycles on tracks (tracks are defined in OSM as: Roads for agricultural use, gravel roads in the forest etc.; usually unpaved/unsealed but
+ * may occasionally apply to paved tracks as well.)
+ */
+ props.setProperties(
+ "highway=track;bicycle=yes",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18)
+ );
+ props.setProperties(
+ "highway=track;bicycle=designated",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99)
+ );
+ props.setProperties(
+ "highway=track;bicycle=yes;surface=*",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.18)
+ );
+ props.setProperties(
+ "highway=track;bicycle=designated;surface=*",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(0.99)
+ );
+ /* this is to avoid double counting since tracks are almost of surface type that is penalized */
+ props.setProperties(
+ "highway=track;surface=*",
+ withModes(PEDESTRIAN_AND_BICYCLE).bicycleSafety(1.3)
+ );
+
+ /* bicycle=designated, but no bike infrastructure is present */
+ props.setProperties("highway=*;bicycle=designated", withModes(ALL).bicycleSafety(0.97));
+ props.setProperties("highway=service;bicycle=designated", withModes(ALL).bicycleSafety(0.84));
+ props.setProperties(
+ "highway=residential;bicycle=designated",
+ withModes(ALL).bicycleSafety(0.95)
+ );
+ props.setProperties(
+ "highway=unclassified;bicycle=designated",
+ withModes(ALL).bicycleSafety(0.95)
+ );
+ props.setProperties(
+ "highway=residential_link;bicycle=designated",
+ withModes(ALL).bicycleSafety(0.95)
+ );
+ props.setProperties("highway=tertiary;bicycle=designated", withModes(ALL).bicycleSafety(0.97));
+ props.setProperties(
+ "highway=tertiary_link;bicycle=designated",
+ withModes(ALL).bicycleSafety(0.97)
+ );
+ props.setProperties("highway=secondary;bicycle=designated", withModes(ALL).bicycleSafety(1.46));
+ props.setProperties(
+ "highway=secondary_link;bicycle=designated",
+ withModes(ALL).bicycleSafety(1.46)
+ );
+ props.setProperties("highway=primary;bicycle=designated", withModes(ALL).bicycleSafety(2));
+ props.setProperties("highway=primary_link;bicycle=designated", withModes(ALL).bicycleSafety(2));
+ props.setProperties(
+ "highway=trunk;bicycle=designated",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(7.25)
+ );
+ props.setProperties(
+ "highway=trunk_link;bicycle=designated",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2)
+ );
+ props.setProperties(
+ "highway=motorway;bicycle=designated",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(7.76)
+ );
+ props.setProperties(
+ "highway=motorway_link;bicycle=designated",
+ withModes(BICYCLE_AND_CAR).bicycleSafety(2)
+ );
+
+ // We assume highway/cycleway of a cycle network to be safer (for bicycle network relations, their network is copied to way in postLoad)
+ // this uses a OR since you don't want to apply the safety multiplier more than once.
+ // Signed bicycle_roads and cyclestreets exist in traffic codes of some european countries.
+ // Tagging in OSM and on-the-ground use is varied, so just assume they are "somehow safer", too.
+ // In my test area ways often, but not always, have both tags.
+ // For simplicity these two concepts are handled together.
+ props.setMixinProperties(
+ new LogicalOrSpecifier(
+ "lcn=yes",
+ "rcn=yes",
+ "ncn=yes",
+ "bicycle_road=yes",
+ "cyclestreet=yes"
+ ),
+ ofBicycleSafety(0.7)
+ );
+
+ /*
+ * Automobile speeds in the United States: Based on my (mattwigway) personal experience, primarily in California
+ */
+ props.setCarSpeed("highway=motorway", 29); // 29 m/s ~= 65 mph
+ props.setCarSpeed("highway=motorway_link", 15); // ~= 35 mph
+ props.setCarSpeed("highway=trunk", 24.6f); // ~= 55 mph
+ props.setCarSpeed("highway=trunk_link", 15); // ~= 35 mph
+ props.setCarSpeed("highway=primary", 20); // ~= 45 mph
+ props.setCarSpeed("highway=primary_link", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=secondary", 15); // ~= 35 mph
+ props.setCarSpeed("highway=secondary_link", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=tertiary", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=tertiary_link", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=living_street", 2.2f); // ~= 5 mph
+
+ // generally, these will not allow cars at all, but the docs say
+ // "For roads used mainly/exclusively for pedestrians . . . which may allow access by
+ // motorised vehicles only for very limited periods of the day."
+ // http://wiki.openstreetmap.org/wiki/Key:highway
+ // This of course makes the street network time-dependent
+ props.setCarSpeed("highway=pedestrian", 2.2f); // ~= 5 mph
+
+ props.setCarSpeed("highway=residential", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=unclassified", 11.2f); // ~= 25 mph
+ props.setCarSpeed("highway=service", 6.7f); // ~= 15 mph
+ props.setCarSpeed("highway=track", 4.5f); // ~= 10 mph
+ props.setCarSpeed("highway=road", 11.2f); // ~= 25 mph
+
+ // default ~= 25 mph
+ props.defaultCarSpeed = 11.2f;
+ // 38 m/s ~= 85 mph ~= 137 kph
+ props.maxPossibleCarSpeed = 38f;
+
+ /* special situations */
+
+ /*
+ * cycleway:left/right=lane/track/shared_lane permutations - no longer needed because left/right matching algorithm does this
+ */
+
+ /* cycleway:left=lane */
+ /* cycleway:right=track */
+ /* cycleway:left=track */
+ /* cycleway:right=shared_lane */
+ /* cycleway:left=shared_lane */
+ /* cycleway:right=lane, cycleway:left=track */
+ /* cycleway:right=lane, cycleway:left=shared_lane */
+ /* cycleway:right=track, cycleway:left=lane */
+ /* cycleway:right=track, cycleway:left=shared_lane */
+ /* cycleway:right=shared_lane, cycleway:left=lane */
+ /* cycleway:right=shared_lane, cycleway:left=track */
+
+ /* surface=* mixins */
+
+ /*
+ * The following tags have been removed from surface weights because they are no more of an impedence to bicycling than a paved surface
+ * surface=paving_stones surface=fine_gravel (sounds counter-intuitive but see the definition on the OSM Wiki) surface=tartan (this what
+ * running tracks are usually made of)
+ */
+
+ props.setMixinProperties("surface=unpaved", ofBicycleSafety(1.18));
+ props.setMixinProperties("surface=compacted", ofBicycleSafety(1.18));
+ props.setMixinProperties("surface=wood", ofBicycleSafety(1.18));
+
+ props.setMixinProperties("surface=cobblestone", ofBicycleSafety(1.3));
+ props.setMixinProperties("surface=sett", ofBicycleSafety(1.3));
+ props.setMixinProperties("surface=unhewn_cobblestone", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=grass_paver", ofBicycleSafety(1.3));
+ props.setMixinProperties("surface=pebblestone", ofBicycleSafety(1.3));
+ // Can be slick if wet, but otherwise not unfavorable to bikes
+ props.setMixinProperties("surface=metal", ofBicycleSafety(1.3));
+ props.setMixinProperties("surface=ground", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=dirt", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=earth", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=grass", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=mud", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=woodchip", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=gravel", ofBicycleSafety(1.5));
+ props.setMixinProperties("surface=artifical_turf", ofBicycleSafety(1.5));
+
+ /* sand is deadly for bikes */
+ props.setMixinProperties("surface=sand", ofBicycleSafety(100));
+
+ /* Portland-local mixins */
+
+ props.setMixinProperties("foot=discouraged", ofWalkSafety(3));
+ props.setMixinProperties("bicycle=discouraged", ofBicycleSafety(3));
+
+ props.setMixinProperties("foot=use_sidepath", ofWalkSafety(5));
+ props.setMixinProperties("bicycle=use_sidepath", ofBicycleSafety(5));
+
+ populateNotesAndNames(props);
+
+ // slope overrides
+ props.setSlopeOverride(new BestMatchSpecifier("bridge=*"), true);
+ props.setSlopeOverride(new BestMatchSpecifier("embankment=*"), true);
+ props.setSlopeOverride(new BestMatchSpecifier("cutting=*"), true);
+ props.setSlopeOverride(new BestMatchSpecifier("tunnel=*"), true);
+ props.setSlopeOverride(new BestMatchSpecifier("location=underground"), true);
+ props.setSlopeOverride(new BestMatchSpecifier("indoor=yes"), true);
+ }
+
+ public void populateNotesAndNames(WayPropertySet props) {
+ /* and the notes */
+ // TODO: The curly brackets in the string below mean that the CreativeNamer should substitute in OSM tag values.
+ // However they are not taken into account when passed to the translation function.
+ // props.createNotes("wheelchair:description=*", "{wheelchair:description}", StreetNotesService.WHEELCHAIR_MATCHER);
+ // TODO: The two entries below produce lots of spurious notes (because of OSM mapper comments)
+ // props.createNotes("note=*", "{note}", StreetNotesService.ALWAYS_MATCHER);
+ // props.createNotes("notes=*", "{notes}", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes(
+ "RLIS:bicycle=caution_area",
+ "note.caution",
+ StreetNotesService.BICYCLE_MATCHER
+ );
+ props.createNotes(
+ "CCGIS:bicycle=caution_area",
+ "note.caution",
+ StreetNotesService.BICYCLE_MATCHER
+ );
+ // TODO: Maybe we should apply the following notes only for car/bike
+ props.createNotes("surface=unpaved", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes(
+ "surface=compacted",
+ "note.unpaved_surface",
+ StreetNotesService.ALWAYS_MATCHER
+ );
+ props.createNotes("surface=ground", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes("surface=dirt", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes("surface=earth", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes("surface=grass", "note.unpaved_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes("surface=mud", "note.muddy_surface", StreetNotesService.ALWAYS_MATCHER);
+ props.createNotes("toll=yes", "note.toll", StreetNotesService.DRIVING_MATCHER);
+ props.createNotes("toll:motorcar=yes", "note.toll", StreetNotesService.DRIVING_MATCHER);
+
+ /* and some names */
+ // Basics
+ props.createNames("highway=cycleway", "name.bike_path");
+ props.createNames("cycleway=track", "name.bike_path");
+ props.createNames("highway=pedestrian", "name.pedestrian_path");
+ props.createNames("highway=pedestrian;area=yes", "name.pedestrian_area");
+ props.createNames("highway=path", "name.path");
+ props.createNames("highway=footway", "name.pedestrian_path");
+ props.createNames("highway=bridleway", "name.bridleway");
+ props.createNames("highway=footway;bicycle=no", "name.pedestrian_path");
+
+ // Platforms
+ props.createNames("otp:route_ref=*", "name.otp_route_ref");
+ props.createNames("highway=platform;ref=*", "name.platform_ref");
+ props.createNames("railway=platform;ref=*", "name.platform_ref");
+ props.createNames("railway=platform;highway=footway;footway=sidewalk", "name.platform");
+ props.createNames("railway=platform;highway=path;path=sidewalk", "name.platform");
+ props.createNames("railway=platform;highway=pedestrian", "name.platform");
+ props.createNames("railway=platform;highway=path", "name.platform");
+ props.createNames("railway=platform;highway=footway", "name.platform");
+ props.createNames("highway=platform", "name.platform");
+ props.createNames("railway=platform", "name.platform");
+ props.createNames("railway=platform;highway=footway;bicycle=no", "name.platform");
+
+ // Bridges/Tunnels
+ props.createNames("highway=pedestrian;bridge=*", "name.footbridge");
+ props.createNames("highway=path;bridge=*", "name.footbridge");
+ props.createNames("highway=footway;bridge=*", "name.footbridge");
+
+ props.createNames("highway=pedestrian;tunnel=*", "name.underpass");
+ props.createNames("highway=path;tunnel=*", "name.underpass");
+ props.createNames("highway=footway;tunnel=*", "name.underpass");
+
+ // Basic Mappings
+ props.createNames("highway=motorway", "name.road");
+ props.createNames("highway=motorway_link", "name.ramp");
+ props.createNames("highway=trunk", "name.road");
+ props.createNames("highway=trunk_link", "name.ramp");
+
+ props.createNames("highway=primary", "name.road");
+ props.createNames("highway=primary_link", "name.link");
+ props.createNames("highway=secondary", "name.road");
+ props.createNames("highway=secondary_link", "name.link");
+ props.createNames("highway=tertiary", "name.road");
+ props.createNames("highway=tertiary_link", "name.link");
+ props.createNames("highway=unclassified", "name.road");
+ props.createNames("highway=residential", "name.road");
+ props.createNames("highway=living_street", "name.road");
+ props.createNames("highway=road", "name.road");
+ props.createNames("highway=service", "name.service_road");
+ props.createNames("highway=service;service=alley", "name.alley");
+ props.createNames("highway=service;service=parking_aisle", "name.parking_aisle");
+ props.createNames("highway=byway", "name.byway");
+ props.createNames("highway=track", "name.track");
+
+ props.createNames("highway=footway;footway=sidewalk", "name.sidewalk");
+ props.createNames("highway=path;path=sidewalk", "name.sidewalk");
+
+ props.createNames("highway=steps", "name.steps");
+
+ props.createNames("amenity=bicycle_parking;name=*", "name.bicycle_parking_name");
+ props.createNames("amenity=bicycle_parking", "name.bicycle_parking");
+
+ props.createNames("amenity=parking;name=*", "name.park_and_ride_name");
+ props.createNames("amenity=parking", "name.park_and_ride_station");
+ }
+
+ public boolean doesTagValueDisallowThroughTraffic(String tagValue) {
return (
"no".equals(tagValue) ||
"destination".equals(tagValue) ||
@@ -22,20 +731,20 @@ default boolean doesTagValueDisallowThroughTraffic(String tagValue) {
);
}
- default float getCarSpeedForWay(OsmWithTags way, boolean backward) {
+ public float getCarSpeedForWay(OsmWithTags way, boolean backward) {
return way.getOsmProvider().getWayPropertySet().getCarSpeedForWay(way, backward);
}
- default Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) {
+ public Float getMaxUsedCarSpeed(WayPropertySet wayPropertySet) {
return wayPropertySet.maxUsedCarSpeed;
}
- default boolean isGeneralNoThroughTraffic(OsmWithTags way) {
+ public boolean isGeneralNoThroughTraffic(OsmWithTags way) {
String access = way.getTag("access");
return doesTagValueDisallowThroughTraffic(access);
}
- default boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String vehicle = way.getTag("vehicle");
if (vehicle != null) {
return doesTagValueDisallowThroughTraffic(vehicle);
@@ -47,7 +756,7 @@ default boolean isVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
/**
* Returns true if through traffic for motor vehicles is not allowed.
*/
- default boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String motorVehicle = way.getTag("motor_vehicle");
if (motorVehicle != null) {
return doesTagValueDisallowThroughTraffic(motorVehicle);
@@ -59,7 +768,7 @@ default boolean isMotorVehicleThroughTrafficExplicitlyDisallowed(OsmWithTags way
/**
* Returns true if through traffic for bicycle is not allowed.
*/
- default boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isBicycleThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String bicycle = way.getTag("bicycle");
if (bicycle != null) {
return doesTagValueDisallowThroughTraffic(bicycle);
@@ -71,7 +780,7 @@ default boolean isBicycleNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
/**
* Returns true if through traffic for walk is not allowed.
*/
- default boolean isWalkNoThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
+ public boolean isWalkThroughTrafficExplicitlyDisallowed(OsmWithTags way) {
String foot = way.getTag("foot");
if (foot != null) {
return doesTagValueDisallowThroughTraffic(foot);
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java
index b40c2e7f75a..98593ef70d0 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/OsmTagMapperSource.java
@@ -18,7 +18,7 @@ public enum OsmTagMapperSource {
public OsmTagMapper getInstance() {
return switch (this) {
- case DEFAULT -> new DefaultMapper();
+ case DEFAULT -> new OsmTagMapper();
case NORWAY -> new NorwayMapper();
case UK -> new UKMapper();
case FINLAND -> new FinlandMapper();
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java
index 98379852689..7da8f8ca886 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/PortlandMapper.java
@@ -9,7 +9,7 @@
import org.opentripplanner.osm.wayproperty.specifier.Condition.GreaterThan;
import org.opentripplanner.osm.wayproperty.specifier.ExactMatchSpecifier;
-class PortlandMapper implements OsmTagMapper {
+class PortlandMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -57,7 +57,6 @@ public void populateProperties(WayPropertySet props) {
// Max speed limit in Oregon is 70 mph ~= 113kmh ~= 31.3m/s
props.maxPossibleCarSpeed = 31.4f;
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
}
diff --git a/application/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java b/application/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java
index 08531ce051d..35a575b00c8 100644
--- a/application/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java
+++ b/application/src/main/java/org/opentripplanner/osm/tagmapping/UKMapper.java
@@ -21,9 +21,9 @@
*
* @author marcusyoung
* @see OsmTagMapper
- * @see DefaultMapper
+ * @see OsmTagMapper
*/
-class UKMapper implements OsmTagMapper {
+class UKMapper extends OsmTagMapper {
@Override
public void populateProperties(WayPropertySet props) {
@@ -79,7 +79,6 @@ public void populateProperties(WayPropertySet props) {
props.setProperties("indoor=area", pedestrianWayProperties);
props.setProperties("indoor=corridor", pedestrianWayProperties);
- // Read the rest from the default set
- new DefaultMapper().populateProperties(props);
+ super.populateProperties(props);
}
}
diff --git a/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java b/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java
index 94dbab99b26..7dacc61e6b8 100644
--- a/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java
+++ b/application/src/main/java/org/opentripplanner/osm/wayproperty/specifier/BestMatchSpecifier.java
@@ -26,6 +26,10 @@ public class BestMatchSpecifier implements OsmSpecifier {
public static final int NO_MATCH_SCORE = 0;
private final Condition[] conditions;
+ /**
+ * @deprecated Logic is fuzzy and unpredictable, use ExactMatchSpecifier instead
+ */
+ @Deprecated
public BestMatchSpecifier(String spec) {
conditions = OsmSpecifier.parseConditions(spec, ";");
}
diff --git a/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java b/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java
index b13fad6e97b..fcce3720538 100644
--- a/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java
+++ b/application/src/main/java/org/opentripplanner/routing/alertpatch/AlertUrl.java
@@ -1,7 +1,10 @@
package org.opentripplanner.routing.alertpatch;
-public class AlertUrl {
+import java.util.Objects;
+import javax.annotation.Nullable;
- public String uri;
- public String label;
+public record AlertUrl(String uri, @Nullable String label) {
+ public AlertUrl {
+ Objects.requireNonNull(uri);
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
index a81dd4e7083..0fef72f7b3e 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/mapping/RaptorPathToItineraryMapper.java
@@ -1,11 +1,12 @@
package org.opentripplanner.routing.algorithm.mapping;
-import static org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter.toOtpDomainCost;
+import static org.opentripplanner.raptor.api.model.RaptorCostConverter.toOtpDomainCost;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import org.opentripplanner.astar.model.GraphPath;
import org.opentripplanner.framework.application.OTPFeature;
import org.opentripplanner.framework.geometry.GeometryUtils;
@@ -38,8 +39,8 @@
import org.opentripplanner.street.search.TraverseMode;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.request.StreetSearchRequestMapper;
+import org.opentripplanner.street.search.state.EdgeTraverser;
import org.opentripplanner.street.search.state.State;
-import org.opentripplanner.street.search.state.StateEditor;
import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate;
import org.opentripplanner.transit.model.timetable.TripOnServiceDate;
import org.opentripplanner.transit.service.TransitService;
@@ -360,24 +361,15 @@ private List mapNonTransitLeg(
.build()
);
} else {
- StateEditor se = new StateEditor(edges.get(0).getFromVertex(), transferStreetRequest);
- se.setTimeSeconds(createZonedDateTime(pathLeg.fromTime()).toEpochSecond());
-
- State s = se.makeState();
- ArrayList transferStates = new ArrayList<>();
- transferStates.add(s);
- for (Edge e : edges) {
- var states = e.traverse(s);
- if (State.isEmpty(states)) {
- s = null;
- } else {
- transferStates.add(states[0]);
- s = states[0];
- }
- }
-
- State[] states = transferStates.toArray(new State[0]);
- var graphPath = new GraphPath<>(states[states.length - 1]);
+ var legTransferSearchRequest = transferStreetRequest
+ .copyOf(createZonedDateTime(pathLeg.fromTime()).toInstant())
+ .build();
+ var initialStates = State.getInitialStates(
+ Set.of(edges.getFirst().getFromVertex()),
+ legTransferSearchRequest
+ );
+ var state = EdgeTraverser.traverseEdges(initialStates, edges);
+ var graphPath = new GraphPath<>(state.get());
Itinerary subItinerary = graphPathToItineraryMapper.generateItinerary(graphPath);
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
index 3f68d91321e..584e690d3ee 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/DefaultAccessEgress.java
@@ -3,7 +3,7 @@
import java.util.Objects;
import org.opentripplanner.framework.model.TimeAndCost;
import org.opentripplanner.raptor.api.model.RaptorConstants;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.street.search.state.State;
/**
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
index 175c782ab22..32d54787e6f 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/Transfer.java
@@ -4,14 +4,15 @@
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import org.locationtech.jts.geom.Coordinate;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.model.RaptorTransfer;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.routing.api.request.preference.WalkPreferences;
import org.opentripplanner.street.model.edge.Edge;
import org.opentripplanner.street.search.request.StreetSearchRequest;
import org.opentripplanner.street.search.state.EdgeTraverser;
-import org.opentripplanner.street.search.state.StateEditor;
+import org.opentripplanner.street.search.state.State;
import org.opentripplanner.utils.logging.Throttle;
import org.opentripplanner.utils.tostring.ToStringBuilder;
import org.slf4j.Logger;
@@ -84,10 +85,8 @@ public Optional asRaptorTransfer(StreetSearchRequest request) {
);
}
- StateEditor se = new StateEditor(edges.get(0).getFromVertex(), request);
- se.setTimeSeconds(0);
-
- var state = EdgeTraverser.traverseEdges(se.makeState(), edges);
+ var initialStates = State.getInitialStates(Set.of(edges.getFirst().getFromVertex()), request);
+ var state = EdgeTraverser.traverseEdges(initialStates, edges);
return state.map(s ->
new DefaultRaptorTransfer(
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
index 43faa3f0c40..1d4827a4ef6 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/DefaultCostCalculator.java
@@ -3,6 +3,7 @@
import javax.annotation.Nullable;
import org.opentripplanner.model.transfer.TransferConstraint;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java
index 152f199248c..9fac19cd940 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/IndexBasedFactorStrategy.java
@@ -1,6 +1,7 @@
package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost;
import java.util.Arrays;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
/**
* This class keep a facto for each index and the minimum factor for fast retrieval during Raptor
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostLinearFunction.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostLinearFunction.java
index b636b26bebe..f231acf9270 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostLinearFunction.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/RaptorCostLinearFunction.java
@@ -2,6 +2,7 @@
import java.util.Objects;
import org.opentripplanner.framework.model.Cost;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;
import org.opentripplanner.routing.api.request.framework.LinearFunctionSerialization;
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java
index 59cf04eb2db..3e5b868afdb 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/SingleValueFactorStrategy.java
@@ -1,5 +1,7 @@
package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
+
/**
* This {@link FactorStrategy} keep a single value and use it every time the factor is needed. The
* {@link #minFactor()} return the same value.
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java
index d68d2230a5c..693cac45031 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/cost/WheelchairCostCalculator.java
@@ -1,6 +1,7 @@
package org.opentripplanner.routing.algorithm.raptoradapter.transit.cost;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.model.RaptorTransferConstraint;
import org.opentripplanner.raptor.spi.RaptorCostCalculator;
import org.opentripplanner.routing.api.request.preference.AccessibilityPreferences;
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java
index 1074724a90c..c5a75b02bbc 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/RaptorRequestMapper.java
@@ -12,9 +12,11 @@
import org.opentripplanner.raptor.api.model.GeneralizedCostRelaxFunction;
import org.opentripplanner.raptor.api.model.RaptorAccessEgress;
import org.opentripplanner.raptor.api.model.RaptorConstants;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.raptor.api.model.RaptorTripSchedule;
import org.opentripplanner.raptor.api.model.RelaxFunction;
import org.opentripplanner.raptor.api.request.DebugRequestBuilder;
+import org.opentripplanner.raptor.api.request.MultiCriteriaRequest;
import org.opentripplanner.raptor.api.request.Optimization;
import org.opentripplanner.raptor.api.request.PassThroughPoint;
import org.opentripplanner.raptor.api.request.RaptorRequest;
@@ -22,10 +24,10 @@
import org.opentripplanner.raptor.api.request.RaptorViaLocation;
import org.opentripplanner.raptor.rangeraptor.SystemErrDebugLogger;
import org.opentripplanner.routing.algorithm.raptoradapter.router.performance.PerformanceTimersForRaptor;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.routing.api.request.DebugEventType;
import org.opentripplanner.routing.api.request.RouteRequest;
import org.opentripplanner.routing.api.request.framework.CostLinearFunction;
+import org.opentripplanner.routing.api.request.preference.TransitPreferences;
import org.opentripplanner.routing.api.request.via.ViaLocation;
import org.opentripplanner.transit.model.network.grouppriority.DefaultTransitGroupPriorityCalculator;
@@ -128,15 +130,20 @@ private RaptorRequest doMap() {
var pt = preferences.transit();
var r = pt.raptor();
- // Note! If a pass-through-point exists, then the transit-group-priority feature is disabled
-
- // TODO - We need to handle via locations that are not pass-through-points here
if (hasPassThroughOnly()) {
mcBuilder.withPassThroughPoints(mapPassThroughPoints());
+ } else if (hasViaLocationsOnly()) {
+ builder.searchParams().addViaLocations(mapViaLocations());
+ // relax transit group priority can be used with via-visit-stop, but not with pass-through
+ if (pt.isRelaxTransitGroupPrioritySet()) {
+ mapRelaxTransitGroupPriority(mcBuilder, pt);
+ }
+ } else if (pt.isRelaxTransitGroupPrioritySet()) {
+ mapRelaxTransitGroupPriority(mcBuilder, pt);
+ } else {
+ // The deprecated relaxGeneralizedCostAtDestination is only enabled, if there is no
+ // via location and the relaxTransitGroupPriority is not used (Normal).
r.relaxGeneralizedCostAtDestination().ifPresent(mcBuilder::withRelaxCostAtDestination);
- } else if (!pt.relaxTransitGroupPriority().isNormal()) {
- mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator());
- mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority()));
}
});
@@ -160,10 +167,6 @@ private RaptorRequest doMap() {
.addAccessPaths(accessPaths)
.addEgressPaths(egressPaths);
- if (hasViaLocationsOnly()) {
- builder.searchParams().addViaLocations(mapViaLocations());
- }
-
var raptorDebugging = request.journey().transit().raptorDebugging();
if (raptorDebugging.isEnabled()) {
@@ -199,11 +202,17 @@ private RaptorRequest doMap() {
}
private boolean hasPassThroughOnly() {
- return request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation);
+ return (
+ request.isViaSearch() &&
+ request.getViaLocations().stream().allMatch(ViaLocation::isPassThroughLocation)
+ );
}
private boolean hasViaLocationsOnly() {
- return request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation);
+ return (
+ request.isViaSearch() &&
+ request.getViaLocations().stream().noneMatch(ViaLocation::isPassThroughLocation)
+ );
}
private boolean hasViaLocationsAndPassThroughLocations() {
@@ -265,6 +274,14 @@ private int relativeTime(Instant time) {
return (int) (time.getEpochSecond() - transitSearchTimeZeroEpocSecond);
}
+ private static void mapRelaxTransitGroupPriority(
+ MultiCriteriaRequest.Builder> mcBuilder,
+ TransitPreferences pt
+ ) {
+ mcBuilder.withTransitPriorityCalculator(new DefaultTransitGroupPriorityCalculator());
+ mcBuilder.withRelaxC1(mapRelaxCost(pt.relaxTransitGroupPriority()));
+ }
+
private static void addLogListenerForEachEventTypeRequested(
DebugRequestBuilder target,
DebugEventType type,
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
index b0d2b009a0f..a056efe87df 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/raptoradapter/transit/mappers/TransitLayerMapper.java
@@ -13,13 +13,13 @@
import java.util.Set;
import javax.annotation.Nullable;
import org.opentripplanner.framework.application.OTPFeature;
+import org.opentripplanner.raptor.api.model.RaptorCostConverter;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.Transfer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitLayer;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TransitTuningParameters;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.TripPatternForDate;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.ConstrainedTransfersForPatterns;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.constrainedtransfer.TransferIndexGenerator;
-import org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter;
import org.opentripplanner.routing.algorithm.raptoradapter.transit.request.RaptorRequestTransferCache;
import org.opentripplanner.transit.model.network.TripPattern;
import org.opentripplanner.transit.model.site.StopTransferPriority;
diff --git a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TransferWaitTimeCostCalculator.java b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TransferWaitTimeCostCalculator.java
index a5336999b09..ee33f1d86c3 100644
--- a/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TransferWaitTimeCostCalculator.java
+++ b/application/src/main/java/org/opentripplanner/routing/algorithm/transferoptimization/model/TransferWaitTimeCostCalculator.java
@@ -1,6 +1,6 @@
package org.opentripplanner.routing.algorithm.transferoptimization.model;
-import static org.opentripplanner.routing.algorithm.raptoradapter.transit.cost.RaptorCostConverter.toRaptorCost;
+import static org.opentripplanner.raptor.api.model.RaptorCostConverter.toRaptorCost;
/**
* This calculator uses the {@code minSafeTransferTime}(t0) and an inverse log function to calculate
diff --git a/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java b/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java
index 14dc7ade982..51af4a27f79 100644
--- a/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java
+++ b/application/src/main/java/org/opentripplanner/routing/alternativelegs/AlternativeLegs.java
@@ -45,18 +45,10 @@ public static List getAlternativeLegs(
Leg leg,
Integer numberLegs,
TransitService transitService,
- boolean searchBackward,
+ NavigationDirection direction,
AlternativeLegsFilter filter
) {
- return getAlternativeLegs(
- leg,
- numberLegs,
- transitService,
- searchBackward,
- filter,
- false,
- false
- );
+ return getAlternativeLegs(leg, numberLegs, transitService, direction, filter, false, false);
}
/**
@@ -66,9 +58,8 @@ public static List getAlternativeLegs(
* @param numberLegs The number of alternative legs requested. If fewer legs are found,
* only the found legs are returned.
* @param transitService The transit service used for the search
- * @param includeDepartBefore Boolean indicating whether the alternative legs should depart
- * earlier or later than the original leg True if earlier, false if
- * later.
+ * @param direction Indicating whether the alternative legs should depart before or
+ * after than the original.
* @param filter AlternativeLegsFilter indicating which properties of the original
* leg should not change in the alternative legs
* @param exactOriginStop Boolean indicating whether the exact departure stop of the original
@@ -82,7 +73,7 @@ public static List getAlternativeLegs(
Leg leg,
Integer numberLegs,
TransitService transitService,
- boolean includeDepartBefore,
+ NavigationDirection direction,
AlternativeLegsFilter filter,
boolean exactOriginStop,
boolean exactDestinationStop
@@ -105,7 +96,7 @@ public static List getAlternativeLegs(
ScheduledTransitLeg::getStartTime
);
- if (includeDepartBefore) {
+ if (direction == NavigationDirection.PREVIOUS) {
legComparator = legComparator.reversed();
}
@@ -119,13 +110,7 @@ public static List getAlternativeLegs(
.distinct()
.flatMap(tripPattern -> withBoardingAlightingPositions(origins, destinations, tripPattern))
.flatMap(t ->
- generateLegs(
- transitService,
- t,
- leg.getStartTime(),
- leg.getServiceDate(),
- includeDepartBefore
- )
+ generateLegs(transitService, t, leg.getStartTime(), leg.getServiceDate(), direction)
)
.filter(Predicate.not(leg::isPartiallySameTransitLeg))
.sorted(legComparator)
@@ -142,7 +127,7 @@ private static Stream generateLegs(
TripPatternBetweenStops tripPatternBetweenStops,
ZonedDateTime departureTime,
LocalDate originalDate,
- boolean includeDepartBefore
+ NavigationDirection direction
) {
TripPattern pattern = tripPatternBetweenStops.tripPattern;
int boardingPosition = tripPatternBetweenStops.positions.boardingPosition;
@@ -155,7 +140,7 @@ private static Stream generateLegs(
tts.getServiceDayMidnight() + tts.getRealtimeDeparture()
);
- if (includeDepartBefore) {
+ if (direction == NavigationDirection.PREVIOUS) {
comparator = comparator.reversed();
}
@@ -185,7 +170,7 @@ private static Stream generateLegs(
continue;
}
- boolean departureTimeInRange = includeDepartBefore
+ boolean departureTimeInRange = direction == NavigationDirection.PREVIOUS
? tripTimes.getDepartureTime(boardingPosition) <= secondsSinceMidnight
: tripTimes.getDepartureTime(boardingPosition) >= secondsSinceMidnight;
diff --git a/application/src/main/java/org/opentripplanner/routing/alternativelegs/NavigationDirection.java b/application/src/main/java/org/opentripplanner/routing/alternativelegs/NavigationDirection.java
new file mode 100644
index 00000000000..8254e3e28b7
--- /dev/null
+++ b/application/src/main/java/org/opentripplanner/routing/alternativelegs/NavigationDirection.java
@@ -0,0 +1,16 @@
+package org.opentripplanner.routing.alternativelegs;
+
+/**
+ * This enum describes how the user navigates on a list of items.
+ */
+public enum NavigationDirection {
+ /**
+ * Get the next set of items.
+ */
+ NEXT,
+
+ /**
+ * Get the previous set of items.
+ */
+ PREVIOUS,
+}
diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
index 8e649b3be49..4e36b1b58db 100644
--- a/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
+++ b/application/src/main/java/org/opentripplanner/routing/api/request/RouteRequest.java
@@ -292,8 +292,9 @@ public List getViaLocations() {
return via;
}
- public void setViaLocations(final List via) {
+ public RouteRequest setViaLocations(final List via) {
this.via = via;
+ return this;
}
/**
diff --git a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
index f15b47216e5..a237dd53527 100644
--- a/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
+++ b/application/src/main/java/org/opentripplanner/routing/api/request/preference/TransitPreferences.java
@@ -137,6 +137,10 @@ public CostLinearFunction relaxTransitGroupPriority() {
return relaxTransitGroupPriority;
}
+ public boolean isRelaxTransitGroupPrioritySet() {
+ return !relaxTransitGroupPriority.isNormal();
+ }
+
/**
* When true, real-time updates are ignored during this search.
*/
diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/VehicleRentalService.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/VehicleRentalService.java
index 93fe12a1c7c..fa65dd86987 100644
--- a/application/src/main/java/org/opentripplanner/service/vehiclerental/VehicleRentalService.java
+++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/VehicleRentalService.java
@@ -38,4 +38,14 @@ List getVehicleRentalStationForEnvelope(
double maxLon,
double maxLat
);
+
+ /**
+ * Gets all vehicle rental places inside an envelope.
+ */
+ List getVehicleRentalPlacesForEnvelope(
+ double minLon,
+ double minLat,
+ double maxLon,
+ double maxLat
+ );
}
diff --git a/application/src/main/java/org/opentripplanner/service/vehiclerental/internal/DefaultVehicleRentalService.java b/application/src/main/java/org/opentripplanner/service/vehiclerental/internal/DefaultVehicleRentalService.java
index 996f4cbcc9d..1f66d654cdb 100644
--- a/application/src/main/java/org/opentripplanner/service/vehiclerental/internal/DefaultVehicleRentalService.java
+++ b/application/src/main/java/org/opentripplanner/service/vehiclerental/internal/DefaultVehicleRentalService.java
@@ -119,4 +119,23 @@ private Stream getVehicleRentalStationsAsStream() {
.filter(VehicleRentalStation.class::isInstance)
.map(VehicleRentalStation.class::cast);
}
+
+ @Override
+ public List getVehicleRentalPlacesForEnvelope(
+ double minLon,
+ double minLat,
+ double maxLon,
+ double maxLat
+ ) {
+ Envelope envelope = new Envelope(
+ new Coordinate(minLon, minLat),
+ new Coordinate(maxLon, maxLat)
+ );
+
+ Stream vehicleRentalPlaceStream = getVehicleRentalPlaces()
+ .stream()
+ .filter(vr -> envelope.contains(new Coordinate(vr.getLongitude(), vr.getLatitude())));
+
+ return vehicleRentalPlaceStream.toList();
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
index a8096d806ba..a10f8f8ef7b 100644
--- a/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
+++ b/application/src/main/java/org/opentripplanner/standalone/OTPMain.java
@@ -52,7 +52,7 @@ public static void main(String[] args) {
try {
Thread.currentThread().setName("main");
CommandLineParameters params = parseAndValidateCmdLine(args);
- OtpStartupInfo.logInfo();
+ OtpStartupInfo.logInfo(params.logTaskInfo());
startOTPServer(params);
} catch (OtpAppException ae) {
LOG.error(ae.getMessage(), ae);
diff --git a/application/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java b/application/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java
index c601846d889..ddcc2c60212 100644
--- a/application/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java
+++ b/application/src/main/java/org/opentripplanner/standalone/OtpStartupInfo.java
@@ -34,13 +34,18 @@ private static String info() {
);
}
- public static void logInfo() {
+ public static void logInfo(String cliTaskInfo) {
// This is good when aggregating logs across multiple load balanced instances of OTP
// Hint: a regexp filter like "^OTP (START|SHUTTING)" will list nodes going up/down
- LOG.info("OTP STARTING UP ({}) using Java {}", projectInfo().getVersionString(), javaVersion());
+ LOG.info(
+ "OTP STARTING UP - {} - {} - Java {}",
+ cliTaskInfo,
+ projectInfo().getVersionString(),
+ javaVersion()
+ );
ApplicationShutdownSupport.addShutdownHook(
"server-shutdown-info",
- () -> LOG.info("OTP SHUTTING DOWN ({})", projectInfo().getVersionString())
+ () -> LOG.info("OTP SHUTTING DOWN - {} - {}", cliTaskInfo, projectInfo().getVersionString())
);
LOG.info(NEW_LINE + "{}", info());
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java
index 3513ec252ea..2324ce325d9 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/CommandLineParameters.java
@@ -201,6 +201,21 @@ public boolean doServe() {
return load || (serve && doBuildTransit());
}
+ public String logTaskInfo() {
+ var mainCommands = new ArrayList();
+ if (doBuildStreet() & doBuildTransit()) {
+ mainCommands.add("Build Street & Transit Graph");
+ } else if (doBuildStreet()) {
+ mainCommands.add("Build Street Graph");
+ } else if (doBuildTransit()) {
+ mainCommands.add("Build Transit Graph");
+ }
+ if (doServe()) {
+ mainCommands.add("Run Server");
+ }
+ return String.join(", ", mainCommands);
+ }
+
/**
* @param port a port that we plan to bind to
* @throws ParameterException if that port is not available
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java
index 65fb7bb2c11..3396e56f6e3 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETGooglePubsubUpdaterConfig.java
@@ -1,6 +1,7 @@
package org.opentripplanner.standalone.config.routerconfig.updaters;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.INITIAL_GET_DATA_TIMEOUT;
import static org.opentripplanner.updater.siri.updater.google.SiriETGooglePubsubUpdaterParameters.RECONNECT_PERIOD;
@@ -79,6 +80,11 @@ If this parameter is set, the updater will be marked as initialized (primed) onl
.of("fuzzyTripMatching")
.since(V2_1)
.summary("If the trips should be matched fuzzily.")
+ .asBoolean(false),
+ c
+ .of("producerMetrics")
+ .since(V2_7)
+ .summary("If failure, success, and warning metrics should be collected per producer.")
.asBoolean(false)
);
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java
index 4402ba83b7e..1b0eb2e4420 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/SiriETUpdaterConfig.java
@@ -2,6 +2,7 @@
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_0;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
import java.time.Duration;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
@@ -43,7 +44,12 @@ public static SiriETUpdaterParameters create(String configRef, NodeAdapter c) {
.since(V2_0)
.summary("If the fuzzy trip matcher should be used to match trips.")
.asBoolean(false),
- HttpHeadersConfig.headers(c, V2_3)
+ HttpHeadersConfig.headers(c, V2_3),
+ c
+ .of("producerMetrics")
+ .since(V2_7)
+ .summary("If failure, success, and warning metrics should be collected per producer.")
+ .asBoolean(false)
);
}
}
diff --git a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/sources/VehicleRentalSourceFactory.java b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/sources/VehicleRentalSourceFactory.java
index aa548977c7e..e58b5e96ae8 100644
--- a/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/sources/VehicleRentalSourceFactory.java
+++ b/application/src/main/java/org/opentripplanner/standalone/config/routerconfig/updaters/sources/VehicleRentalSourceFactory.java
@@ -1,16 +1,20 @@
package org.opentripplanner.standalone.config.routerconfig.updaters.sources;
+import static org.opentripplanner.standalone.config.framework.json.EnumMapper.docEnumValueList;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V1_5;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_1;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_2;
import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_3;
+import static org.opentripplanner.standalone.config.framework.json.OtpVersion.V2_7;
+import java.util.Set;
import org.opentripplanner.ext.smoovebikerental.SmooveBikeRentalDataSourceParameters;
import org.opentripplanner.standalone.config.framework.json.NodeAdapter;
import org.opentripplanner.standalone.config.routerconfig.updaters.HttpHeadersConfig;
import org.opentripplanner.updater.spi.HttpHeaders;
import org.opentripplanner.updater.vehicle_rental.VehicleRentalSourceType;
import org.opentripplanner.updater.vehicle_rental.datasources.params.GbfsVehicleRentalDataSourceParameters;
+import org.opentripplanner.updater.vehicle_rental.datasources.params.RentalPickupType;
import org.opentripplanner.updater.vehicle_rental.datasources.params.VehicleRentalDataSourceParameters;
/**
@@ -43,13 +47,15 @@ public VehicleRentalDataSourceParameters create() {
headers(),
network(),
geofencingZones(),
- overloadingAllowed()
+ overloadingAllowed(),
+ rentalPickupTypes()
);
case SMOOVE -> new SmooveBikeRentalDataSourceParameters(
url(),
network(),
overloadingAllowed(),
- headers()
+ headers(),
+ rentalPickupTypes()
);
};
}
@@ -121,4 +127,13 @@ private boolean geofencingZones() {
)
.asBoolean(false);
}
+
+ private Set rentalPickupTypes() {
+ return c
+ .of("rentalPickupTypes")
+ .since(V2_7)
+ .summary(RentalPickupType.STATION.typeDescription())
+ .description(docEnumValueList(RentalPickupType.values()))
+ .asEnumSet(RentalPickupType.class, RentalPickupType.ALL);
+ }
}
diff --git a/application/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java b/application/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
index b606bcd9962..ee24e87f07c 100644
--- a/application/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
+++ b/application/src/main/java/org/opentripplanner/street/model/edge/StreetEdge.java
@@ -2,13 +2,16 @@
import java.io.IOException;
import java.io.ObjectOutputStream;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
import java.util.stream.Stream;
+import javax.annotation.Nullable;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.impl.PackedCoordinateSequence;
import org.opentripplanner.framework.geometry.CompactLineStringUtils;
@@ -976,6 +979,47 @@ static int defaultMillimeterLength(LineString geometry) {
return (int) (SphericalDistanceLibrary.length(geometry) * 1000);
}
+ /**
+ * Helper method for {@link #splitStatesAfterHavingExitedNoDropOffZoneWhenReverseSearching}.
+ * Create a single new state, exiting a no-drop-off zone, in reverse, and continuing
+ * on a rental vehicle in the known network, or an unknown network if network is null,
+ * unless the known network is not accepted by the provided {@link RoutingPreferences}.
+ * @param s0 The parent state (i.e. the following state, as we are in reverse)
+ * @param network Network id, or null if unknown
+ * @param preferences Active {@link RoutingPreferences}
+ * @return Newly generated {@link State}, or null if the state would have been forbidden.
+ */
+ private State createStateAfterHavingExitedNoDropOffZoneWhenReverseSearching(
+ State s0,
+ String network,
+ RoutingPreferences preferences
+ ) {
+ var edit = doTraverse(s0, TraverseMode.WALK, false);
+ if (edit != null) {
+ edit.dropFloatingVehicle(s0.vehicleRentalFormFactor(), network, s0.getRequest().arriveBy());
+ if (network != null) {
+ edit.resetStartedInNoDropOffZone();
+ }
+ State state = edit.makeState();
+ if (state != null && network != null) {
+ var rentalPreferences = preferences.rental(state.currentMode());
+ var allowedNetworks = rentalPreferences.allowedNetworks();
+ var bannedNetworks = rentalPreferences.bannedNetworks();
+ if (allowedNetworks.isEmpty()) {
+ if (bannedNetworks.contains(network)) {
+ return null;
+ }
+ } else {
+ if (!allowedNetworks.contains(network)) {
+ return null;
+ }
+ }
+ }
+ return state;
+ }
+ return null;
+ }
+
/**
* A very special case: an arriveBy rental search has started in a no-drop-off zone
* we don't know yet which rental network we will end up using.
@@ -987,25 +1031,38 @@ static int defaultMillimeterLength(LineString geometry) {
* zone applies to where we pick up a vehicle with a specific network.
*/
private State[] splitStatesAfterHavingExitedNoDropOffZoneWhenReverseSearching(State s0) {
- var networks = Stream.concat(
- // null is a special rental network that speculatively assumes that you can take any vehicle
- // you have to check in the rental edge if this has search has been started in a no-drop off zone
- Stream.of((String) null),
- tov.rentalRestrictions().noDropOffNetworks().stream()
- );
+ var preferences = s0.getRequest().preferences();
+ var states = new ArrayList();
+
+ // Also include a state which continues walking, because the vehicle rental states are
+ // speculation. It is possible that the rental states don't end up at the target at all
+ // due to mode limitations or not finding a place to pick up the rental vehicle, or that
+ // the rental possibility is simply more expensive than walking.
+ StateEditor walking = doTraverse(s0, TraverseMode.WALK, false);
+ if (walking != null) {
+ states.add(walking.makeState());
+ }
- var states = networks.map(network -> {
- var edit = doTraverse(s0, TraverseMode.WALK, false);
- if (edit != null) {
- edit.dropFloatingVehicle(s0.vehicleRentalFormFactor(), network, s0.getRequest().arriveBy());
- if (network != null) {
- edit.resetStartedInNoDropOffZone();
- }
- return edit.makeState();
+ boolean hasNetworkStates = false;
+ for (var network : tov.rentalRestrictions().noDropOffNetworks()) {
+ var state = createStateAfterHavingExitedNoDropOffZoneWhenReverseSearching(
+ s0,
+ network,
+ preferences
+ );
+ if (state != null) {
+ states.add(state);
+ hasNetworkStates = true;
}
- return null;
- });
- return State.ofStream(states);
+ }
+ if (hasNetworkStates) {
+ // null is a special rental network that speculatively assumes that you can take any vehicle
+ // you have to check in the rental edge if this has search has been started in a no-drop off zone
+ states.add(
+ createStateAfterHavingExitedNoDropOffZoneWhenReverseSearching(s0, null, preferences)
+ );
+ }
+ return states.toArray(State[]::new);
}
/**
diff --git a/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java b/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
index c93ea598256..df8933cd22d 100644
--- a/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
+++ b/application/src/main/java/org/opentripplanner/street/search/request/StreetSearchRequest.java
@@ -124,6 +124,10 @@ public DataOverlayContext dataOverlayContext() {
return dataOverlayContext;
}
+ public StreetSearchRequestBuilder copyOf(Instant time) {
+ return copyOf(this).withStartTime(time);
+ }
+
public StreetSearchRequestBuilder copyOfReversed(Instant time) {
return copyOf(this).withStartTime(time).withArriveBy(!arriveBy);
}
diff --git a/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java b/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
index 8755f014e14..502d014e358 100644
--- a/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
+++ b/application/src/main/java/org/opentripplanner/street/search/state/EdgeTraverser.java
@@ -2,7 +2,10 @@
import java.util.Collection;
import java.util.Optional;
+import org.opentripplanner.astar.model.ShortestPathTree;
import org.opentripplanner.street.model.edge.Edge;
+import org.opentripplanner.street.model.vertex.Vertex;
+import org.opentripplanner.street.search.strategy.DominanceFunctions;
/**
* This is a very reduced version of the A* algorithm: from an initial state a number of edges are
@@ -14,24 +17,49 @@
*/
public class EdgeTraverser {
- public static Optional traverseEdges(final State s, final Collection edges) {
- var state = s;
+ public static Optional traverseEdges(
+ final Collection initialStates,
+ final Collection edges
+ ) {
+ return traverseEdges(initialStates.toArray(new State[0]), edges);
+ }
+
+ public static Optional traverseEdges(
+ final State[] initialStates,
+ final Collection edges
+ ) {
+ if (edges.isEmpty()) {
+ return Optional.of(initialStates[0]);
+ }
+
+ // The shortest path tree is used to prune dominated parallel states. For example,
+ // CAR_PICKUP can return both a CAR/WALK state after each traversal of which only
+ // the optimal states need to be continued.
+ var dominanceFunction = new DominanceFunctions.MinimumWeight();
+ var spt = new ShortestPathTree<>(dominanceFunction);
+ for (State initialState : initialStates) {
+ spt.add(initialState);
+ }
+
+ Vertex lastVertex = null;
+ var isArriveBy = initialStates[0].getRequest().arriveBy();
for (Edge e : edges) {
- var afterTraversal = e.traverse(state);
- if (afterTraversal.length > 1) {
- throw new IllegalStateException(
- "Expected only a single state returned from edge %s but received %s".formatted(
- e,
- afterTraversal.length
- )
- );
- }
- if (State.isEmpty(afterTraversal)) {
+ var vertex = isArriveBy ? e.getToVertex() : e.getFromVertex();
+ var fromStates = spt.getStates(vertex);
+ if (fromStates == null || fromStates.isEmpty()) {
return Optional.empty();
- } else {
- state = afterTraversal[0];
}
+
+ for (State fromState : fromStates) {
+ var newToStates = e.traverse(fromState);
+ for (State newToState : newToStates) {
+ spt.add(newToState);
+ }
+ }
+
+ lastVertex = isArriveBy ? e.getFromVertex() : e.getToVertex();
}
- return Optional.ofNullable(state);
+
+ return Optional.ofNullable(lastVertex).map(spt::getState);
}
}
diff --git a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java
index be12be83548..0b269220ae1 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/framework/FeedScopedId.java
@@ -78,7 +78,7 @@ public static boolean isValidString(String value) throws IllegalArgumentExceptio
/**
* Concatenate feedId and id into a string.
*/
- public static String concatenateId(String feedId, String id) {
+ private static String concatenateId(String feedId, String id) {
return feedId + ID_SEPARATOR + id;
}
diff --git a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java
index ec2451ea66d..66f695521c0 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/network/TripPatternBuilder.java
@@ -1,7 +1,5 @@
package org.opentripplanner.transit.model.network;
-import static java.util.Objects.requireNonNullElseGet;
-
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
@@ -219,15 +217,12 @@ private List generateHopGeometriesFromOriginalTripPattern() {
// being replaced having a different number of stops. In that case the geometry will be
// preserved up until the first mismatching stop, and a straight line will be used for
// all segments after that.
- int sizeOfShortestPattern = Math.min(
- stopPattern.getSize(),
- originalTripPattern.numberOfStops()
- );
-
List hopGeometries = new ArrayList<>();
- for (int i = 0; i < sizeOfShortestPattern - 1; i++) {
- LineString hopGeometry = originalTripPattern.getHopGeometry(i);
+ for (int i = 0; i < stopPattern.getSize() - 1; i++) {
+ LineString hopGeometry = i < originalTripPattern.numberOfStops() - 1
+ ? originalTripPattern.getHopGeometry(i)
+ : null;
if (hopGeometry != null && stopPattern.sameStops(originalTripPattern.getStopPattern(), i)) {
// Copy hop geometry from previous pattern
@@ -236,15 +231,8 @@ private List generateHopGeometriesFromOriginalTripPattern() {
hopGeometry != null && stopPattern.sameStations(originalTripPattern.getStopPattern(), i)
) {
// Use old geometry but patch first and last point with new stops
- var newStart = new Coordinate(
- stopPattern.getStop(i).getCoordinate().longitude(),
- stopPattern.getStop(i).getCoordinate().latitude()
- );
-
- var newEnd = new Coordinate(
- stopPattern.getStop(i + 1).getCoordinate().longitude(),
- stopPattern.getStop(i + 1).getCoordinate().latitude()
- );
+ var newStart = stopPattern.getStop(i).getCoordinate().asJtsCoordinate();
+ var newEnd = stopPattern.getStop(i + 1).getCoordinate().asJtsCoordinate();
Coordinate[] coordinates = originalTripPattern.getHopGeometry(i).getCoordinates().clone();
coordinates[0].setCoordinate(newStart);
diff --git a/application/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java b/application/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java
index 0ded9d64dab..8dd581b5699 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/site/RegularStop.java
@@ -29,7 +29,7 @@ public final class RegularStop
private final ZoneId timeZone;
- private final TransitMode gtfsVehicleType;
+ private final TransitMode vehicleType;
private final SubMode netexVehicleSubmode;
@@ -43,7 +43,7 @@ public final class RegularStop
this.platformCode = builder.platformCode();
this.url = builder.url();
this.timeZone = builder.timeZone();
- this.gtfsVehicleType = builder.vehicleType();
+ this.vehicleType = builder.vehicleType();
this.netexVehicleSubmode = SubMode.getOrBuildAndCacheForever(builder.netexVehicleSubmode());
this.boardingAreas = setOfNullSafe(builder.boardingAreas());
this.fareZones = setOfNullSafe(builder.fareZones());
@@ -102,8 +102,16 @@ public ZoneId getTimeZone() {
*/
@Override
@Nullable
- public TransitMode getGtfsVehicleType() {
- return gtfsVehicleType;
+ public TransitMode getVehicleType() {
+ return vehicleType;
+ }
+
+ /**
+ * Return {@code true} if the vehicle type is set in the import to be RAIL. Note! This does
+ * not check patterns visiting the stop.
+ */
+ public boolean isRailStop() {
+ return vehicleType == TransitMode.RAIL;
}
public SubMode getNetexVehicleSubmode() {
@@ -148,7 +156,7 @@ public boolean sameAs(RegularStop other) {
Objects.equals(platformCode, other.platformCode) &&
Objects.equals(url, other.url) &&
Objects.equals(timeZone, other.timeZone) &&
- Objects.equals(gtfsVehicleType, other.gtfsVehicleType) &&
+ Objects.equals(vehicleType, other.vehicleType) &&
Objects.equals(netexVehicleSubmode, other.netexVehicleSubmode) &&
Objects.equals(boardingAreas, other.boardingAreas) &&
Objects.equals(fareZones, other.fareZones)
diff --git a/application/src/main/java/org/opentripplanner/transit/model/site/RegularStopBuilder.java b/application/src/main/java/org/opentripplanner/transit/model/site/RegularStopBuilder.java
index 3b37a709078..5e6cd01618b 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/site/RegularStopBuilder.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/site/RegularStopBuilder.java
@@ -26,7 +26,7 @@ public final class RegularStopBuilder
private ZoneId timeZone;
- private TransitMode gtfsVehicleType;
+ private TransitMode vehicleType;
private String netexVehicleSubmode;
@@ -45,7 +45,7 @@ public final class RegularStopBuilder
this.platformCode = original.getPlatformCode();
this.url = original.getUrl();
this.timeZone = original.getTimeZone();
- this.gtfsVehicleType = original.getGtfsVehicleType();
+ this.vehicleType = original.getVehicleType();
this.netexVehicleSubmode = original.getNetexVehicleSubmode().name();
}
@@ -68,11 +68,11 @@ public RegularStopBuilder withUrl(I18NString url) {
}
public TransitMode vehicleType() {
- return gtfsVehicleType;
+ return vehicleType;
}
public RegularStopBuilder withVehicleType(TransitMode vehicleType) {
- this.gtfsVehicleType = vehicleType;
+ this.vehicleType = vehicleType;
return this;
}
diff --git a/application/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java b/application/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java
index ecd0cf440b6..c4e704b954d 100644
--- a/application/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java
+++ b/application/src/main/java/org/opentripplanner/transit/model/site/StopLocation.java
@@ -62,7 +62,7 @@ default String getPlatformCode() {
}
@Nullable
- default TransitMode getGtfsVehicleType() {
+ default TransitMode getVehicleType() {
return null;
}
diff --git a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
index 5f7cfa95524..f8d1e437a34 100644
--- a/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
+++ b/application/src/main/java/org/opentripplanner/transit/service/DefaultTransitService.java
@@ -599,6 +599,18 @@ public List getTripOnServiceDates(TripOnServiceDateRequest re
return getAllTripOnServiceDates().stream().filter(matcher::match).toList();
}
+ @Override
+ public boolean containsTrip(FeedScopedId id) {
+ TimetableSnapshot currentSnapshot = lazyGetTimeTableSnapShot();
+ if (currentSnapshot != null) {
+ Trip trip = currentSnapshot.getRealTimeAddedTrip(id);
+ if (trip != null) {
+ return true;
+ }
+ }
+ return this.timetableRepositoryIndex.containsTrip(id);
+ }
+
/**
* TODO OTP2 - This is NOT THREAD-SAFE and is used in the real-time updaters, we need to fix
* this when doing the issue #3030.
@@ -731,8 +743,8 @@ public Map getServiceCodesRunningForDate() {
* For each pattern visiting this {@link StopLocation} return its {@link TransitMode}
*/
private Stream getPatternModesOfStop(StopLocation stop) {
- if (stop.getGtfsVehicleType() != null) {
- return Stream.of(stop.getGtfsVehicleType());
+ if (stop.getVehicleType() != null) {
+ return Stream.of(stop.getVehicleType());
} else {
return getPatternsForStop(stop).stream().map(TripPattern::getMode);
}
diff --git a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java
index a57299d96ab..b81dd7d4f14 100644
--- a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java
+++ b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepository.java
@@ -166,10 +166,9 @@ public TimetableRepository() {
*/
public void index() {
if (index == null) {
- LOG.info("Index transit model...");
- // the transit model indexing updates the site repository index (flex stops added to the stop index)
+ LOG.info("Index timetable repository...");
this.index = new TimetableRepositoryIndex(this);
- LOG.info("Index transit model complete.");
+ LOG.info("Index timetable repository complete.");
}
}
diff --git a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepositoryIndex.java b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepositoryIndex.java
index dc618b0943f..ca52b710957 100644
--- a/application/src/main/java/org/opentripplanner/transit/service/TimetableRepositoryIndex.java
+++ b/application/src/main/java/org/opentripplanner/transit/service/TimetableRepositoryIndex.java
@@ -59,7 +59,7 @@ class TimetableRepositoryIndex {
private FlexIndex flexIndex = null;
TimetableRepositoryIndex(TimetableRepository timetableRepository) {
- LOG.info("Transit model index init...");
+ LOG.info("Timetable repository index init...");
for (Agency agency : timetableRepository.getAgencies()) {
this.agencyForId.put(agency.getId(), agency);
@@ -113,7 +113,7 @@ class TimetableRepositoryIndex {
}
}
- LOG.info("Transit Model index init complete.");
+ LOG.info("Timetable repository index init complete.");
}
Agency getAgencyForId(FeedScopedId id) {
@@ -161,6 +161,16 @@ Trip getTripForId(FeedScopedId tripId) {
return tripForId.get(tripId);
}
+ /**
+ * Checks if the specified trip is contained within the index.
+ *
+ * @param tripId the {@link FeedScopedId} of the trip to check
+ * @return true if the trip exists in the index map, false otherwise
+ */
+ boolean containsTrip(FeedScopedId tripId) {
+ return tripForId.containsKey(tripId);
+ }
+
TripOnServiceDate getTripOnServiceDateForTripAndDay(TripIdAndServiceDate tripIdAndServiceDate) {
return tripOnServiceDateForTripAndDay.get(tripIdAndServiceDate);
}
diff --git a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java
index 7be2a29065a..b77eb77d892 100644
--- a/application/src/main/java/org/opentripplanner/transit/service/TransitService.java
+++ b/application/src/main/java/org/opentripplanner/transit/service/TransitService.java
@@ -287,7 +287,7 @@ List stopTimesForPatternAtStop(
/**
* For a {@link StopLocationsGroup} get all child stops and get their modes.
*
- * The mode is either taken from {@link StopLocation#getGtfsVehicleType()} (if non-null)
+ * The mode is either taken from {@link StopLocation#getVehicleType()} (if non-null)
* or from the list of patterns that use the stop location.
*
* The returning stream is ordered by the number of occurrences of the mode in the child stops.
@@ -297,10 +297,10 @@ List stopTimesForPatternAtStop(
/**
* For a {@link StopLocation} return its modes.
*
- * The mode is either taken from {@link StopLocation#getGtfsVehicleType()} (if non-null)
+ * The mode is either taken from {@link StopLocation#getVehicleType()} (if non-null)
* or from the list of patterns that use the stop location.
*
- * If {@link StopLocation#getGtfsVehicleType()} is null the returning stream is ordered by the number
+ * If {@link StopLocation#getVehicleType()} is null the returning stream is ordered by the number
* of occurrences of the mode in the stop.
*
* So, if more patterns of mode BUS than RAIL visit the stop, the result will be [BUS,RAIL].
@@ -320,4 +320,12 @@ List stopTimesForPatternAtStop(
* @return - A list of TripOnServiceDates
*/
List getTripOnServiceDates(TripOnServiceDateRequest request);
+
+ /**
+ * Checks if a trip with the given ID exists in the model.
+ *
+ * @param id the {@link FeedScopedId} of the trip to check
+ * @return true if the trip exists, false otherwise
+ */
+ boolean containsTrip(FeedScopedId id);
}
diff --git a/application/src/main/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcher.java b/application/src/main/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcher.java
index 96e2d25bbd8..05d0f814263 100644
--- a/application/src/main/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcher.java
+++ b/application/src/main/java/org/opentripplanner/updater/GtfsRealtimeFuzzyTripMatcher.java
@@ -35,7 +35,9 @@ public GtfsRealtimeFuzzyTripMatcher(TransitService transitService) {
}
public TripDescriptor match(String feedId, TripDescriptor trip) {
- if (trip.hasTripId()) {
+ if (
+ trip.hasTripId() && transitService.containsTrip(new FeedScopedId(feedId, trip.getTripId()))
+ ) {
// trip_id already exists
return trip;
}
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java
index a1ca89f5c83..aff1659653b 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/AddedTripBuilder.java
@@ -56,6 +56,7 @@ class AddedTripBuilder {
private final Function getTripPatternId;
private final FeedScopedId tripId;
private final Operator operator;
+ private final String dataSource;
private final String lineRef;
private final Route replacedRoute;
private final LocalDate serviceDate;
@@ -81,11 +82,14 @@ class AddedTripBuilder {
Objects.requireNonNull(newServiceJourneyRef, "EstimatedVehicleJourneyCode is required");
tripId = entityResolver.resolveId(newServiceJourneyRef);
- //OperatorRef of added trip
+ // OperatorRef of added trip
Objects.requireNonNull(estimatedVehicleJourney.getOperatorRef(), "OperatorRef is required");
String operatorRef = estimatedVehicleJourney.getOperatorRef().getValue();
operator = entityResolver.resolveOperator(operatorRef);
+ // DataSource of added trip
+ dataSource = estimatedVehicleJourney.getDataSource();
+
// LineRef of added trip
Objects.requireNonNull(estimatedVehicleJourney.getLineRef(), "LineRef is required");
lineRef = estimatedVehicleJourney.getLineRef().getValue();
@@ -135,7 +139,8 @@ class AddedTripBuilder {
boolean cancellation,
String shortName,
String headsign,
- List replacedTrips
+ List replacedTrips,
+ String dataSource
) {
this.transitService = transitService;
this.entityResolver = entityResolver;
@@ -155,20 +160,21 @@ class AddedTripBuilder {
this.shortName = shortName;
this.headsign = headsign;
this.replacedTrips = replacedTrips;
+ this.dataSource = dataSource;
}
Result build() {
if (calls.size() < 2) {
- return UpdateError.result(tripId, TOO_FEW_STOPS);
+ return UpdateError.result(tripId, TOO_FEW_STOPS, dataSource);
}
if (serviceDate == null) {
- return UpdateError.result(tripId, NO_START_DATE);
+ return UpdateError.result(tripId, NO_START_DATE, dataSource);
}
FeedScopedId calServiceId = transitService.getOrCreateServiceIdForDate(serviceDate);
if (calServiceId == null) {
- return UpdateError.result(tripId, NO_START_DATE);
+ return UpdateError.result(tripId, NO_START_DATE, dataSource);
}
boolean isAddedRoute = false;
@@ -176,7 +182,7 @@ Result build() {
if (route == null) {
Agency agency = resolveAgency();
if (agency == null) {
- return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY);
+ return UpdateError.result(tripId, CANNOT_RESOLVE_AGENCY, dataSource);
}
route = createRoute(agency);
isAddedRoute = true;
@@ -201,7 +207,7 @@ Result build() {
// Drop this update if the call refers to an unknown stop (not present in the site repository).
if (stopTime == null) {
- return UpdateError.result(tripId, NO_VALID_STOPS);
+ return UpdateError.result(tripId, NO_VALID_STOPS, dataSource);
}
aimedStopTimes.add(stopTime);
@@ -256,7 +262,7 @@ Result build() {
try {
updatedTripTimes.validateNonIncreasingTimes();
} catch (DataValidationException e) {
- return DataValidationExceptionMapper.toResult(e);
+ return DataValidationExceptionMapper.toResult(e, dataSource);
}
var tripOnServiceDate = TripOnServiceDate
@@ -273,7 +279,8 @@ Result build() {
serviceDate,
tripOnServiceDate,
pattern,
- isAddedRoute
+ isAddedRoute,
+ dataSource
)
);
}
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java
index 0f1475f7b50..06768087c74 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/ModifiedTripBuilder.java
@@ -45,6 +45,7 @@ public class ModifiedTripBuilder {
private final boolean cancellation;
private final OccupancyEnumeration occupancy;
private final boolean predictionInaccurate;
+ private final String dataSource;
public ModifiedTripBuilder(
TripTimes existingTripTimes,
@@ -64,6 +65,7 @@ public ModifiedTripBuilder(
cancellation = TRUE.equals(journey.isCancellation());
predictionInaccurate = TRUE.equals(journey.isPredictionInaccurate());
occupancy = journey.getOccupancy();
+ dataSource = journey.getDataSource();
}
/**
@@ -78,7 +80,8 @@ public ModifiedTripBuilder(
List calls,
boolean cancellation,
OccupancyEnumeration occupancy,
- boolean predictionInaccurate
+ boolean predictionInaccurate,
+ String dataSource
) {
this.existingTripTimes = existingTripTimes;
this.pattern = pattern;
@@ -89,6 +92,7 @@ public ModifiedTripBuilder(
this.cancellation = cancellation;
this.occupancy = occupancy;
this.predictionInaccurate = predictionInaccurate;
+ this.dataSource = dataSource;
}
/**
@@ -103,7 +107,9 @@ public Result build() {
if (cancellation || stopPattern.isAllStopsNonRoutable()) {
LOG.debug("Trip is cancelled");
newTimes.cancelTrip();
- return Result.success(new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate));
+ return Result.success(
+ new TripUpdate(pattern.getStopPattern(), newTimes, serviceDate, dataSource)
+ );
}
applyUpdates(newTimes);
@@ -116,7 +122,7 @@ public Result build() {
newTimes.setRealTimeState(RealTimeState.MODIFIED);
}
- // TODO - Handle DataValidationException at the outemost level(pr trip)
+ // TODO - Handle DataValidationException at the outermost level (pr trip)
try {
newTimes.validateNonIncreasingTimes();
} catch (DataValidationException e) {
@@ -125,7 +131,7 @@ public Result build() {
newTimes.getTrip().getId(),
e.getMessage()
);
- return DataValidationExceptionMapper.toResult(e);
+ return DataValidationExceptionMapper.toResult(e, dataSource);
}
int numStopsInUpdate = newTimes.getNumStops();
@@ -137,11 +143,11 @@ public Result build() {
numStopsInUpdate,
numStopsInPattern
);
- return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS);
+ return UpdateError.result(existingTripTimes.getTrip().getId(), TOO_FEW_STOPS, dataSource);
}
LOG.debug("A valid TripUpdate object was applied using the Timetable class update method.");
- return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate));
+ return Result.success(new TripUpdate(stopPattern, newTimes, serviceDate, dataSource));
}
/**
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java b/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java
index a5817eca41b..9656d1a0b01 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/SiriAlertsUpdateHandler.java
@@ -135,9 +135,9 @@ private TransitAlert mapSituationToAlert(
TransitAlertBuilder alert = createAlertWithTexts(situation);
if (
- (alert.headerText() == null || alert.headerText().toString().isEmpty()) &&
- (alert.descriptionText() == null || alert.descriptionText().toString().isEmpty()) &&
- (alert.detailText() == null || alert.detailText().toString().isEmpty())
+ I18NString.hasNoValue(alert.headerText()) &&
+ I18NString.hasNoValue(alert.descriptionText()) &&
+ I18NString.hasNoValue(alert.detailText())
) {
LOG.debug(
"Empty Alert - ignoring situationNumber: {}",
@@ -221,18 +221,18 @@ private long getEpochSecond(ZonedDateTime startTime) {
private TransitAlertBuilder createAlertWithTexts(PtSituationElement situation) {
return TransitAlert
.of(new FeedScopedId(feedId, situation.getSituationNumber().getValue()))
- .withDescriptionText(getTranslatedString(situation.getDescriptions()))
- .withDetailText(getTranslatedString(situation.getDetails()))
- .withAdviceText(getTranslatedString(situation.getAdvices()))
- .withHeaderText(getTranslatedString(situation.getSummaries()))
- .withUrl(getInfoLinkAsString(situation.getInfoLinks()))
- .addSiriUrls(getInfoLinks(situation.getInfoLinks()));
+ .withDescriptionText(mapTranslatedString(situation.getDescriptions()))
+ .withDetailText(mapTranslatedString(situation.getDetails()))
+ .withAdviceText(mapTranslatedString(situation.getAdvices()))
+ .withHeaderText(mapTranslatedString(situation.getSummaries()))
+ .withUrl(mapInfoLinkToI18NString(situation.getInfoLinks()))
+ .addSiriUrls(mapInfoLinks(situation));
}
/*
* Returns first InfoLink-uri as a String
*/
- private I18NString getInfoLinkAsString(PtSituationElement.InfoLinks infoLinks) {
+ private I18NString mapInfoLinkToI18NString(PtSituationElement.InfoLinks infoLinks) {
if (infoLinks != null) {
if (isNotEmpty(infoLinks.getInfoLinks())) {
InfoLinkStructure infoLinkStructure = infoLinks.getInfoLinks().get(0);
@@ -247,21 +247,32 @@ private I18NString getInfoLinkAsString(PtSituationElement.InfoLinks infoLinks) {
/*
* Returns all InfoLinks
*/
- private List getInfoLinks(PtSituationElement.InfoLinks infoLinks) {
+ private List mapInfoLinks(PtSituationElement situation) {
+ PtSituationElement.InfoLinks infoLinks = situation.getInfoLinks();
List alertUrls = new ArrayList<>();
if (infoLinks != null) {
if (isNotEmpty(infoLinks.getInfoLinks())) {
for (InfoLinkStructure infoLink : infoLinks.getInfoLinks()) {
- AlertUrl alertUrl = new AlertUrl();
-
+ String label = null;
List labels = infoLink.getLabels();
if (labels != null && !labels.isEmpty()) {
- NaturalLanguageStringStructure label = labels.get(0);
- alertUrl.label = label.getValue();
+ NaturalLanguageStringStructure lbl = labels.get(0);
+ label = lbl.getValue();
}
- alertUrl.uri = infoLink.getUri();
- alertUrls.add(alertUrl);
+ var uri = infoLink.getUri();
+ if (uri != null) {
+ alertUrls.add(new AlertUrl(uri, label));
+ } else {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug(
+ "URI missing in info-link - ignoring info-link in situation: {}",
+ situation.getSituationNumber() != null
+ ? situation.getSituationNumber().getValue()
+ : null
+ );
+ }
+ }
}
}
}
@@ -281,7 +292,7 @@ private boolean isNotEmpty(List> list) {
*
* @return A TranslatedString containing the same information as the input
*/
- private I18NString getTranslatedString(List input) {
+ private I18NString mapTranslatedString(List input) {
Map translations = new HashMap<>();
if (input != null && input.size() > 0) {
for (DefaultedTextStructure textStructure : input) {
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java
index b6aa5310d83..fd716e7c232 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/SiriTimetableSnapshotSource.java
@@ -1,6 +1,7 @@
package org.opentripplanner.updater.siri;
import static java.lang.Boolean.TRUE;
+import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.EMPTY_STOP_POINT_REF;
import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NOT_MONITORED;
import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_FUZZY_TRIP_MATCH;
import static org.opentripplanner.updater.spi.UpdateError.UpdateErrorType.NO_START_DATE;
@@ -33,6 +34,7 @@
import org.opentripplanner.updater.spi.UpdateSuccess;
import org.opentripplanner.updater.trip.TimetableSnapshotManager;
import org.opentripplanner.updater.trip.UpdateIncrementality;
+import org.opentripplanner.utils.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure;
@@ -150,6 +152,11 @@ private Result apply(
@Nullable SiriFuzzyTripMatcher fuzzyTripMatcher,
EntityResolver entityResolver
) {
+ for (var call : CallWrapper.of(journey)) {
+ if (StringUtils.hasNoValueOrNullAsString(call.getStopPointRef())) {
+ return UpdateError.result(null, EMPTY_STOP_POINT_REF, journey.getDataSource());
+ }
+ }
boolean shouldAddNewTrip = false;
try {
shouldAddNewTrip = shouldAddNewTrip(journey, entityResolver);
@@ -174,7 +181,7 @@ private Result apply(
/* commit */
return addTripToGraphAndBuffer(result.successValue());
} catch (DataValidationException e) {
- return DataValidationExceptionMapper.toResult(e);
+ return DataValidationExceptionMapper.toResult(e, journey.getDataSource());
} catch (Exception e) {
LOG.warn(
"{} EstimatedJourney {} failed.",
@@ -217,6 +224,7 @@ private Result handleModifiedTrip(
EstimatedVehicleJourney estimatedVehicleJourney
) {
Trip trip = entityResolver.resolveTrip(estimatedVehicleJourney);
+ String dataSource = estimatedVehicleJourney.getDataSource();
// Check if EstimatedVehicleJourney is reported as NOT monitored, ignore the notMonitored-flag
// if the journey is NOT monitored because it has been cancelled
@@ -224,13 +232,13 @@ private Result handleModifiedTrip(
!TRUE.equals(estimatedVehicleJourney.isMonitored()) &&
!TRUE.equals(estimatedVehicleJourney.isCancellation())
) {
- return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED);
+ return UpdateError.result(trip != null ? trip.getId() : null, NOT_MONITORED, dataSource);
}
LocalDate serviceDate = entityResolver.resolveServiceDate(estimatedVehicleJourney);
if (serviceDate == null) {
- return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE);
+ return UpdateError.result(trip != null ? trip.getId() : null, NO_START_DATE, dataSource);
}
TripPattern pattern;
@@ -252,20 +260,20 @@ private Result handleModifiedTrip(
"No trips found for EstimatedVehicleJourney. {}",
DebugString.of(estimatedVehicleJourney)
);
- return UpdateError.result(null, NO_FUZZY_TRIP_MATCH);
+ return UpdateError.result(null, NO_FUZZY_TRIP_MATCH, dataSource);
}
trip = tripAndPattern.trip();
pattern = tripAndPattern.tripPattern();
} else {
- return UpdateError.result(null, TRIP_NOT_FOUND);
+ return UpdateError.result(null, TRIP_NOT_FOUND, dataSource);
}
Timetable currentTimetable = getCurrentTimetable(pattern, serviceDate);
TripTimes existingTripTimes = currentTimetable.getTripTimes(trip);
if (existingTripTimes == null) {
LOG.debug("tripId {} not found in pattern.", trip.getId());
- return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN);
+ return UpdateError.result(trip.getId(), TRIP_NOT_FOUND_IN_PATTERN, dataSource);
}
var updateResult = new ModifiedTripBuilder(
existingTripTimes,
@@ -315,7 +323,8 @@ private Result addTripToGraphAndBuffer(TripUpdate tr
serviceDate,
tripUpdate.addedTripOnServiceDate(),
tripUpdate.tripCreation(),
- tripUpdate.routeCreation()
+ tripUpdate.routeCreation(),
+ tripUpdate.dataSource()
);
var result = snapshotManager.updateBuffer(realTimeTripUpdate);
LOG.debug("Applied real-time data for trip {} on {}", trip, serviceDate);
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java
index 71c521b76fb..8432ed06d54 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/TripUpdate.java
@@ -11,12 +11,18 @@
/**
* Represents the SIRI real-time update of a single trip.
- * @param stopPattern the stop pattern to which belongs the updated trip.
- * @param tripTimes the new trip times for the updated trip.
- * @param serviceDate the service date for which this update applies (updates are valid only for one service date)
- * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the TripOnServiceDate corresponding to this new trip.
- * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new trip pattern to this new trip.
- * @param routeCreation true if an added trip cannot be registered under an existing route and a new route must be created.
+ *
+ * @param stopPattern the stop pattern to which belongs the updated trip.
+ * @param tripTimes the new trip times for the updated trip.
+ * @param serviceDate the service date for which this update applies (updates are valid
+ * only for one service date)
+ * @param addedTripOnServiceDate optionally if this trip update adds a new trip, the
+ * TripOnServiceDate corresponding to this new trip.
+ * @param addedTripPattern optionally if this trip update adds a new trip pattern , the new
+ * trip pattern to this new trip.
+ * @param routeCreation true if an added trip cannot be registered under an existing route
+ * and a new route must be created.
+ * @param dataSource the dataSource of the real-time update.
*/
record TripUpdate(
StopPattern stopPattern,
@@ -24,7 +30,8 @@ record TripUpdate(
LocalDate serviceDate,
@Nullable TripOnServiceDate addedTripOnServiceDate,
@Nullable TripPattern addedTripPattern,
- boolean routeCreation
+ boolean routeCreation,
+ @Nullable String dataSource
) {
public TripUpdate {
Objects.requireNonNull(stopPattern);
@@ -38,9 +45,10 @@ record TripUpdate(
public TripUpdate(
StopPattern stopPattern,
RealTimeTripTimes updatedTripTimes,
- LocalDate serviceDate
+ LocalDate serviceDate,
+ String dataSource
) {
- this(stopPattern, updatedTripTimes, serviceDate, null, null, false);
+ this(stopPattern, updatedTripTimes, serviceDate, null, null, false, dataSource);
}
/**
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java
index 8bbe66559c6..dc479c034e1 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/SiriETUpdaterParameters.java
@@ -15,7 +15,8 @@ public record SiriETUpdaterParameters(
Duration timeout,
Duration previewInterval,
boolean fuzzyTripMatching,
- HttpHeaders httpRequestHeaders
+ HttpHeaders httpRequestHeaders,
+ boolean producerMetrics
)
implements
PollingGraphUpdaterParameters, UrlUpdaterParameters, SiriETHttpTripUpdateSource.Parameters {
diff --git a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java
index 2970ac114c0..d7fb064966a 100644
--- a/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java
+++ b/application/src/main/java/org/opentripplanner/updater/siri/updater/google/SiriETGooglePubsubUpdaterParameters.java
@@ -15,7 +15,8 @@ public record SiriETGooglePubsubUpdaterParameters(
@Nullable String dataInitializationUrl,
Duration reconnectPeriod,
Duration initialGetDataTimeout,
- boolean fuzzyTripMatching
+ boolean fuzzyTripMatching,
+ boolean producerMetrics
)
implements UrlUpdaterParameters {
public static Duration RECONNECT_PERIOD = Duration.ofSeconds(30);
diff --git a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java
index dee103bc385..2a650f684f9 100644
--- a/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java
+++ b/application/src/main/java/org/opentripplanner/updater/spi/DataValidationExceptionMapper.java
@@ -1,5 +1,6 @@
package org.opentripplanner.updater.spi;
+import com.beust.jcommander.internal.Nullable;
import org.opentripplanner.transit.model.framework.DataValidationException;
import org.opentripplanner.transit.model.framework.Result;
import org.opentripplanner.transit.model.timetable.TimetableValidationError;
@@ -15,13 +16,20 @@ public class DataValidationExceptionMapper {
private static final Logger LOG = LoggerFactory.getLogger(DataValidationExceptionMapper.class);
public static Result toResult(DataValidationException error) {
+ return toResult(error, null);
+ }
+
+ public static Result toResult(
+ DataValidationException error,
+ @Nullable String producer
+ ) {
if (error.error() instanceof TimetableValidationError tt) {
return Result.failure(
- new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex())
+ new UpdateError(tt.trip().getId(), mapTimeTableError(tt.code()), tt.stopIndex(), producer)
);
}
// The mapper should handle all possible errors
- LOG.error("Unhandled error: " + error.getMessage(), error);
+ LOG.error("Unhandled error: {}", error.getMessage(), error);
return Result.failure(UpdateError.noTripId(UpdateError.UpdateErrorType.UNKNOWN));
}
diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java
index 1f568ba99a4..945139e2457 100644
--- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java
+++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateError.java
@@ -11,10 +11,18 @@
public record UpdateError(
@Nullable FeedScopedId tripId,
UpdateErrorType errorType,
- @Nullable Integer stopIndex
+ @Nullable Integer stopIndex,
+ @Nullable String producer
) {
public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType) {
- this(tripId, errorType, null);
+ this(tripId, errorType, null, null);
+ }
+
+ public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, Integer stopIndex) {
+ this(tripId, errorType, stopIndex, null);
+ }
+ public UpdateError(@Nullable FeedScopedId tripId, UpdateErrorType errorType, String producer) {
+ this(tripId, errorType, null, producer);
}
public String debugId() {
@@ -33,6 +41,7 @@ public enum UpdateErrorType {
TRIP_NOT_FOUND,
TRIP_NOT_FOUND_IN_PATTERN,
NO_FUZZY_TRIP_MATCH,
+ EMPTY_STOP_POINT_REF,
NO_TRIP_FOR_CANCELLATION_FOUND,
TRIP_ALREADY_EXISTS,
NO_START_DATE,
@@ -56,6 +65,14 @@ public static Result result(FeedScopedId tripId, UpdateError
return Result.failure(new UpdateError(tripId, errorType));
}
+ public static Result result(
+ FeedScopedId tripId,
+ UpdateErrorType errorType,
+ String producer
+ ) {
+ return Result.failure(new UpdateError(tripId, errorType, producer));
+ }
+
public static UpdateError noTripId(UpdateErrorType errorType) {
return new UpdateError(null, errorType);
}
diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java
index 643703f2252..0e47d8d202f 100644
--- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java
+++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateResult.java
@@ -1,6 +1,7 @@
package org.opentripplanner.updater.spi;
import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import java.util.List;
@@ -15,23 +16,43 @@ public record UpdateResult(
int successful,
int failed,
Multimap failures,
- List warnings
+ List warnings,
+ List successes,
+ List errors
) {
/**
* Create an empty result.
*/
public static UpdateResult empty() {
- return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of());
+ return new UpdateResult(0, 0, ArrayListMultimap.create(), List.of(), List.of(), List.of());
}
/**
* Aggregate a list of results into an instance of {@link UpdateResult}.
*/
public static UpdateResult ofResults(List> results) {
- var errors = results.stream().filter(Result::isFailure).map(Result::failureValue).toList();
- var successes = results.stream().filter(Result::isSuccess).map(Result::successValue).toList();
- var warnings = successes.stream().flatMap(s -> s.warnings().stream()).toList();
- var errorIndex = Multimaps.index(errors, UpdateError::errorType);
- return new UpdateResult(successes.size(), errors.size(), errorIndex, warnings);
+ List errors = results
+ .stream()
+ .filter(Result::isFailure)
+ .map(Result::failureValue)
+ .toList();
+ List successes = results
+ .stream()
+ .filter(Result::isSuccess)
+ .map(Result::successValue)
+ .toList();
+ List warnings = successes
+ .stream()
+ .flatMap(s -> s.warnings().stream())
+ .toList();
+ ImmutableListMultimap errorIndex = Multimaps.index(errors, UpdateError::errorType);
+ return new UpdateResult(
+ successes.size(),
+ errors.size(),
+ errorIndex,
+ warnings,
+ successes,
+ errors
+ );
}
}
diff --git a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java
index 6d797a9a650..861f4c5edff 100644
--- a/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java
+++ b/application/src/main/java/org/opentripplanner/updater/spi/UpdateSuccess.java
@@ -1,34 +1,35 @@
package org.opentripplanner.updater.spi;
-import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import org.opentripplanner.utils.collection.ListUtils;
/**
* The result of a successful application of a realtime update, for example for trips or
- * vehicle positions. Its only extra information is a collection of possible warnings that
- * ought to be looked at but didn't prevent the application of the update.
+ * vehicle positions. Its extra information is a collection of possible warnings that
+ * ought to be looked at but didn't prevent the application of the update and the provider of the
+ * update.
*/
-public record UpdateSuccess(List warnings) {
+public record UpdateSuccess(List warnings, String producer) {
/**
- * Create an instance with no warnings.
+ * Create an instance with no warnings and no provider.
*/
public static UpdateSuccess noWarnings() {
- return new UpdateSuccess(List.of());
+ return new UpdateSuccess(List.of(), null);
}
+
/**
- * Create an instance with the provided warnings.
+ * Create an instance with no warnings, but a provider.
*/
- public static UpdateSuccess ofWarnings(WarningType... warnings) {
- return new UpdateSuccess(Arrays.asList(warnings));
+ public static UpdateSuccess noWarnings(String producer) {
+ return new UpdateSuccess(List.of(), producer);
}
/**
* Return a copy of the instance with the provided warnings added.
*/
public UpdateSuccess addWarnings(Collection