);
}
diff --git a/client/src/static/img/graphql-solid.svg b/client/src/static/img/graphql-solid.svg
new file mode 100644
index 00000000000..32d6e5e0f00
--- /dev/null
+++ b/client/src/static/img/graphql-solid.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/client/src/static/img/graphql.svg b/client/src/static/img/graphql.svg
new file mode 100644
index 00000000000..ef85915ffaa
--- /dev/null
+++ b/client/src/static/img/graphql.svg
@@ -0,0 +1,4 @@
+
+
diff --git a/client/src/static/img/wheelchair.svg b/client/src/static/img/wheelchair.svg
new file mode 100644
index 00000000000..d8cf96ca995
--- /dev/null
+++ b/client/src/static/img/wheelchair.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/client/src/static/query/tripQuery.tsx b/client/src/static/query/tripQuery.tsx
index ccb19dc745e..57cca5d4056 100644
--- a/client/src/static/query/tripQuery.tsx
+++ b/client/src/static/query/tripQuery.tsx
@@ -11,6 +11,7 @@ export const query = graphql(`
$searchWindow: Int
$modes: Modes
$itineraryFiltersDebug: ItineraryFilterDebugProfile
+ $wheelchairAccessible: Boolean
$pageCursor: String
) {
trip(
@@ -22,6 +23,7 @@ export const query = graphql(`
searchWindow: $searchWindow
modes: $modes
itineraryFilters: { debug: $itineraryFiltersDebug }
+ wheelchairAccessible: $wheelchairAccessible
pageCursor: $pageCursor
) {
previousPageCursor
@@ -64,6 +66,9 @@ export const query = graphql(`
publicCode
name
id
+ presentation {
+ colour
+ }
}
authority {
name
diff --git a/client/src/style.css b/client/src/style.css
index 1a24ac2c072..86310fd857d 100644
--- a/client/src/style.css
+++ b/client/src/style.css
@@ -7,7 +7,7 @@
margin-right: 14px;
}
-@media (min-width: 2160px) {
+@media (min-width: 1895px) {
.top-content {
height: 75px;
}
@@ -17,7 +17,7 @@
}
}
-@media (max-width: 2159px) {
+@media (max-width: 1896px) {
.top-content {
height: 150px;
}
@@ -50,16 +50,34 @@
margin-right: 1rem;
}
+.search-bar input.input-small {
+ max-width: 100px;
+}
+
+.search-bar input.input-medium {
+ max-width: 130px;
+}
+
.search-bar-route-button-wrapper {
height: 5rem;
padding-top: 25px;
}
+.search-bar-route-button-wrapper a.btn img {
+ margin-top: -2px;
+}
+
.itinerary-list-container {
width: 36rem;
overflow-y: auto;
}
+.itinerary-list-container .time-zone-info {
+ margin: 10px 20px;
+ font-size: 12px;
+ text-align: right;
+}
+
.itinerary-header-wrapper {
position: relative;
background: #0a53be;
diff --git a/client/src/util/formatTime.ts b/client/src/util/formatTime.ts
index 1849640fe3f..1818ced5cd1 100644
--- a/client/src/util/formatTime.ts
+++ b/client/src/util/formatTime.ts
@@ -4,10 +4,11 @@
* If style argument is provided formatted with ('medium') or without ('short') seconds,
* otherwise seconds are shown if not 0.
*/
-export function formatTime(dateTime: string, style?: 'short' | 'medium') {
+export function formatTime(dateTime: string, timeZone: string, style?: 'short' | 'medium') {
const parsed = new Date(dateTime);
return parsed.toLocaleTimeString('en-US', {
timeStyle: style ? style : parsed.getSeconds() === 0 ? 'short' : 'medium',
hourCycle: 'h24',
+ timeZone: timeZone,
});
}
diff --git a/client/src/util/getColorForMode.ts b/client/src/util/getColorForLeg.ts
similarity index 64%
rename from client/src/util/getColorForMode.ts
rename to client/src/util/getColorForLeg.ts
index cb1ad8b6981..ecb8cbc1676 100644
--- a/client/src/util/getColorForMode.ts
+++ b/client/src/util/getColorForLeg.ts
@@ -1,6 +1,6 @@
-import { Mode } from '../gql/graphql.ts';
+import { Leg, Mode } from '../gql/graphql.ts';
-export const getColorForMode = function (mode: Mode) {
+const getColorForMode = function (mode: Mode) {
if (mode === Mode.Foot) return '#191616';
if (mode === Mode.Bicycle) return '#5076D9';
if (mode === Mode.Scooter) return '#253664';
@@ -19,3 +19,14 @@ export const getColorForMode = function (mode: Mode) {
if (mode === Mode.Taxi) return '#81304C';
return '#aaa';
};
+
+/**
+ * Extract a line color from a leg. If there isn't one given by its line, this method returns a fallback color.
+ */
+export const getColorForLeg = function (leg: Leg) {
+ if (leg.line?.presentation?.colour) {
+ return `#${leg.line.presentation.colour}`;
+ } else {
+ return getColorForMode(leg.mode);
+ }
+};
diff --git a/doc/templates/BuildConfiguration.md b/doc/templates/BuildConfiguration.md
index 7e768e30eb1..77b03dae500 100644
--- a/doc/templates/BuildConfiguration.md
+++ b/doc/templates/BuildConfiguration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Graph Build Configuration
diff --git a/doc/templates/Configuration.md b/doc/templates/Configuration.md
index 6f6f4fc9960..45b2c36c67b 100644
--- a/doc/templates/Configuration.md
+++ b/doc/templates/Configuration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Configuring OpenTripPlanner
diff --git a/doc/templates/GraphQL-Tutorial.md b/doc/templates/GraphQL-Tutorial.md
index 11a2e304119..2a78be65cc2 100644
--- a/doc/templates/GraphQL-Tutorial.md
+++ b/doc/templates/GraphQL-Tutorial.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# GraphQL tutorial
diff --git a/doc/templates/RouteRequest.md b/doc/templates/RouteRequest.md
index a452e1d1480..9b7cd6fd58f 100644
--- a/doc/templates/RouteRequest.md
+++ b/doc/templates/RouteRequest.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Route Request
diff --git a/doc/templates/RouterConfiguration.md b/doc/templates/RouterConfiguration.md
index b6c6ccf9c4b..87e4c1693cc 100644
--- a/doc/templates/RouterConfiguration.md
+++ b/doc/templates/RouterConfiguration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Router configuration
diff --git a/doc/templates/StopConsolidation.md b/doc/templates/StopConsolidation.md
index 6817ee47d4c..70866882bd1 100644
--- a/doc/templates/StopConsolidation.md
+++ b/doc/templates/StopConsolidation.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Stop consolidation
diff --git a/doc/templates/UpdaterConfig.md b/doc/templates/UpdaterConfig.md
index 440cd96f733..aab5631e6e2 100644
--- a/doc/templates/UpdaterConfig.md
+++ b/doc/templates/UpdaterConfig.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
diff --git a/doc/user/BuildConfiguration.md b/doc/user/BuildConfiguration.md
index b311991120e..0bf4a3c29ec 100644
--- a/doc/user/BuildConfiguration.md
+++ b/doc/user/BuildConfiguration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Graph Build Configuration
diff --git a/doc/user/Changelog.md b/doc/user/Changelog.md
index 2bdee72c6e0..800cdd1bb69 100644
--- a/doc/user/Changelog.md
+++ b/doc/user/Changelog.md
@@ -3,6 +3,15 @@
The changelog lists most feature changes between each release. The list is automatically created
based on merged pull requests. Search GitHub issues and pull requests for smaller issues.
+## 2.7.0-SNAPSHOT (under development)
+
+- Extra leg when transferring at the same stop [#5984](https://github.com/opentripplanner/OpenTripPlanner/pull/5984)
+- Filter vector tiles stops by current service week [#6003](https://github.com/opentripplanner/OpenTripPlanner/pull/6003)
+- Add a matcher API for filters in the transit service used for datedServiceJourneyQuery [#5713](https://github.com/opentripplanner/OpenTripPlanner/pull/5713)
+- Refetch transit leg with a leg query of GTFS GraphQL API [#6045](https://github.com/opentripplanner/OpenTripPlanner/pull/6045)
+- Remove deprecated support for GTFS flex stop areas [#6074](https://github.com/opentripplanner/OpenTripPlanner/pull/6074)
+[](AUTOMATIC_CHANGELOG_PLACEHOLDER_DO_NOT_REMOVE)
+
## 2.6.0 (2024-09-18)
### Notable Changes
diff --git a/doc/user/Configuration.md b/doc/user/Configuration.md
index 832784d977b..bca974f8617 100644
--- a/doc/user/Configuration.md
+++ b/doc/user/Configuration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Configuring OpenTripPlanner
@@ -226,6 +226,7 @@ Here is a list of all features which can be toggled on/off and their default val
| `APIUpdaterStatus` | Enable endpoint for graph updaters status. | ✓️ | |
| `ConsiderPatternsForDirectTransfers` | Enable limiting transfers so that there is only a single transfer to each pattern. | ✓️ | |
| `DebugUi` | Enable the debug GraphQL client and web UI and located at the root of the web server as well as the debug map tiles it uses. Be aware that the map tiles are not a stable API and can change without notice. Use the [vector tiles feature if](sandbox/MapboxVectorTilesApi.md) you want a stable map tiles API. | ✓️ | |
+| `ExtraTransferLegOnSameStop` | Should there be a transfer leg when transferring on the very same stop. Note that for in-seat/interlined transfers no transfer leg will be generated. | | |
| `FloatingBike` | Enable floating bike routing. | ✓️ | |
| `GtfsGraphQlApi` | Enable the [GTFS GraphQL API](apis/GTFS-GraphQL-API.md). | ✓️ | |
| `GtfsGraphQlApiRentalStationFuzzyMatching` | Does vehicleRentalStation query also allow ids that are not feed scoped. | | |
diff --git a/doc/user/Developers-Guide.md b/doc/user/Developers-Guide.md
index 368a12edb62..ee0fe1eab9a 100644
--- a/doc/user/Developers-Guide.md
+++ b/doc/user/Developers-Guide.md
@@ -56,7 +56,7 @@ There are several ways to get involved:
* Join the [Gitter chat room](https://gitter.im/opentripplanner/OpenTripPlanner) and the
[user mailing list](http://groups.google.com/group/opentripplanner-users).
-* Fix typos and improve the documentation within the `/docs` directory of the project (details
+* Fix typos and improve the documentation within the `/doc/user` directory of the project (details
below).
* [File a bug or new feature request](http://github.com/openplans/OpenTripPlanner/issues/new).
@@ -133,7 +133,7 @@ control to be applied to documentation as well as program source code. All pull
how OTP is used or configured should include changes to the documentation alongside code
modifications.
-The documentation files are in Markdown format and are in the `/docs` directory under the root of
+The documentation files are in Markdown format and are in the `/doc/user` directory under the root of
the project. On every push to the `dev-2.x` branch the documentation will be rebuilt and deployed as
static pages to our subdomain of [Github Pages](https://github.com/opentripplanner/docs).
MkDocs is a Python program and should run on any major platform.
@@ -143,7 +143,7 @@ how to generate a live local preview of the documentation while you're writing i
In short:
```
-$ pip install -r docs/requirements.txt
+$ pip install -r doc/user/requirements.txt
$ mkdocs serve
```
diff --git a/doc/user/RouteRequest.md b/doc/user/RouteRequest.md
index 674ab238888..c00502b2726 100644
--- a/doc/user/RouteRequest.md
+++ b/doc/user/RouteRequest.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Route Request
diff --git a/doc/user/RouterConfiguration.md b/doc/user/RouterConfiguration.md
index 4e565cfe17d..2c42bea6d74 100644
--- a/doc/user/RouterConfiguration.md
+++ b/doc/user/RouterConfiguration.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Router configuration
diff --git a/doc/user/UpdaterConfig.md b/doc/user/UpdaterConfig.md
index f3a0d982e68..3d03cb0a5ae 100644
--- a/doc/user/UpdaterConfig.md
+++ b/doc/user/UpdaterConfig.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
diff --git a/doc/user/apis/GraphQL-Tutorial.md b/doc/user/apis/GraphQL-Tutorial.md
index d65fbc144ba..3d365de5862 100644
--- a/doc/user/apis/GraphQL-Tutorial.md
+++ b/doc/user/apis/GraphQL-Tutorial.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# GraphQL tutorial
diff --git a/doc/user/examples/ibi/portland/router-config.json b/doc/user/examples/ibi/portland/router-config.json
index 0bf3547dbfd..acbcbc2e0a0 100644
--- a/doc/user/examples/ibi/portland/router-config.json
+++ b/doc/user/examples/ibi/portland/router-config.json
@@ -59,6 +59,62 @@
"url": "https://gbfs.spin.pm/api/gbfs/v2/portland"
}
],
+ "vectorTiles": {
+ "basePath": "/rtp/routers/default/vectorTiles",
+ "attribution": "Regional Partners",
+ "layers": [
+ {
+ "name": "stops",
+ "type": "Stop",
+ "mapper": "Digitransit",
+ "maxZoom": 20,
+ "minZoom": 14,
+ "cacheMaxSeconds": 600,
+ "filter": "sunday-to-sunday-service-week"
+ },
+ {
+ "name": "areaStops",
+ "type": "AreaStop",
+ "mapper": "OTPRR",
+ "maxZoom": 30,
+ "minZoom": 8,
+ "cacheMaxSeconds": 600
+ },
+ {
+ "name": "stations",
+ "type": "Station",
+ "mapper": "Digitransit",
+ "maxZoom": 20,
+ "minZoom": 2,
+ "cacheMaxSeconds": 600
+ },
+ {
+ "name": "rentalVehicles",
+ "type": "VehicleRentalVehicle",
+ "mapper": "Digitransit",
+ "maxZoom": 20,
+ "minZoom": 2,
+ "cacheMaxSeconds": 60
+ },
+ {
+ "name": "rentalStations",
+ "type": "VehicleRentalStation",
+ "mapper": "Digitransit",
+ "maxZoom": 20,
+ "minZoom": 2,
+ "cacheMaxSeconds": 600
+ },
+ {
+ "name": "vehicleParking",
+ "type": "VehicleParking",
+ "mapper": "Digitransit",
+ "maxZoom": 20,
+ "minZoom": 10,
+ "cacheMaxSeconds": 60,
+ "expansionFactor": 0.25
+ }
+ ]
+ },
"rideHailingServices": [
{
"type": "uber-car-hailing",
diff --git a/doc/user/sandbox/MapboxVectorTilesApi.md b/doc/user/sandbox/MapboxVectorTilesApi.md
index 62f3bd36c38..4430a398a5d 100644
--- a/doc/user/sandbox/MapboxVectorTilesApi.md
+++ b/doc/user/sandbox/MapboxVectorTilesApi.md
@@ -173,6 +173,7 @@ For each layer, the configuration includes:
| type = "stop" | `enum` | Type of the layer. | *Required* | | 2.0 |
| [cacheMaxSeconds](#vectorTiles_layers_0_cacheMaxSeconds) | `integer` | Sets the cache header in the response. | *Optional* | `-1` | 2.0 |
| [expansionFactor](#vectorTiles_layers_0_expansionFactor) | `double` | How far outside its boundaries should the tile contain information. | *Optional* | `0.25` | 2.0 |
+| [filter](#vectorTiles_layers_0_filter) | `enum` | Reduce the result set of a layer further by a specific filter. | *Optional* | `"none"` | 2.6 |
| [mapper](#vectorTiles_layers_0_mapper) | `string` | Describes the mapper converting from the OTP model entities to the vector tile properties. | *Required* | | 2.0 |
| maxZoom | `integer` | Maximum zoom levels the layer is active for. | *Optional* | `20` | 2.0 |
| minZoom | `integer` | Minimum zoom levels the layer is active for. | *Optional* | `9` | 2.0 |
@@ -245,6 +246,18 @@ How far outside its boundaries should the tile contain information.
The value is a fraction of the tile size. If you are having problem with icons and shapes being clipped at tile edges, then increase this number.
+
filter
+
+**Since version:** `2.6` ∙ **Type:** `enum` ∙ **Cardinality:** `Optional` ∙ **Default value:** `"none"`
+**Path:** /vectorTiles/layers/[0]
+**Enum values:** `none` | `sunday-to-sunday-service-week`
+
+Reduce the result set of a layer further by a specific filter.
+
+This is useful for when the schema of a layer, say stops, should remain unchanged but some
+elements should not be included in the result.
+
+
mapper
**Since version:** `2.0` ∙ **Type:** `string` ∙ **Cardinality:** `Required`
diff --git a/doc/user/sandbox/StopConsolidation.md b/doc/user/sandbox/StopConsolidation.md
index b36429b1f60..d0e18a9ce30 100644
--- a/doc/user/sandbox/StopConsolidation.md
+++ b/doc/user/sandbox/StopConsolidation.md
@@ -2,7 +2,7 @@
NOTE! Part of this document is generated. Make sure you edit the template, not the generated doc.
- Template directory is: /doc/templates
- - Generated directory is: /docs
+ - Generated directory is: /doc/user
-->
# Stop consolidation
diff --git a/pom.xml b/pom.xml
index 710270dad16..1f5bb3ed8a3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
https://opentripplanner.orgorg.opentripplannerotp
- 2.6.0
+ 2.7.0-SNAPSHOTjar
@@ -56,9 +56,9 @@
- 157
+ 158
- 31.3
+ 32.02.522.17.23.1.8
@@ -858,7 +858,7 @@
org.apache.httpcomponents.client5httpclient5
- 5.3.1
+ 5.4commons-cli
@@ -989,7 +989,7 @@
org.apache.maven.pluginsmaven-gpg-plugin
- 3.2.5
+ 3.2.6sign-artifacts
diff --git a/renovate.json5 b/renovate.json5
index b672797d48f..045bbe07d22 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -150,6 +150,14 @@
"com.google.dagger:"
],
"minimumReleaseAge": "1 week"
+ },
+ {
+ "description": "Geotools takes a while to publish a changelog and since it pulls in JTS it can change the serialization of the graph",
+ "matchPackagePrefixes": [
+ "org.geotools:"
+ ],
+ "minimumReleaseAge": "1 week",
+ "labels": ["skip changelog", "bump serialization id"]
}
],
"timezone": "Europe/Berlin"
diff --git a/src/client/index.html b/src/client/index.html
index 5b969aed83e..88764c84a17 100644
--- a/src/client/index.html
+++ b/src/client/index.html
@@ -5,8 +5,8 @@
OTP Debug Client
-
-
+
+
diff --git a/src/ext-test/java/org/opentripplanner/ext/fares/FareRuleSetTest.java b/src/ext-test/java/org/opentripplanner/ext/fares/FareRuleSetTest.java
new file mode 100644
index 00000000000..13d4f713634
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/fares/FareRuleSetTest.java
@@ -0,0 +1,222 @@
+package org.opentripplanner.ext.fares;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.ext.fares.model.FareAttribute;
+import org.opentripplanner.ext.fares.model.FareRuleSet;
+import org.opentripplanner.transit.model.basic.Money;
+import org.opentripplanner.transit.model.framework.FeedScopedId;
+
+class FareRuleSetTest {
+
+ private FareRuleSet fareRuleSet;
+ static final Money TWO_FIFTY = Money.usDollars(2.50f);
+
+ @BeforeEach
+ void setUp() {
+ FeedScopedId id = new FeedScopedId("feed", "fare1");
+ FareAttribute fareAttribute = FareAttribute
+ .of(id)
+ .setPrice(TWO_FIFTY)
+ .setPaymentMethod(1)
+ .setTransfers(1)
+ .setTransferDuration(7200)
+ .build();
+ fareRuleSet = new FareRuleSet(fareAttribute);
+ }
+
+ @Test
+ void testHasNoRules() {
+ assertFalse(fareRuleSet.hasRules());
+ }
+
+ @Test
+ void testAddOriginDestination() {
+ fareRuleSet.addOriginDestination("A", "B");
+ assertTrue(fareRuleSet.hasRules());
+ }
+
+ @Test
+ void testAddRouteOriginDestination() {
+ fareRuleSet.addRouteOriginDestination("Route1", "A", "B");
+ assertTrue(fareRuleSet.hasRules());
+ assertEquals(1, fareRuleSet.getRouteOriginDestinations().size());
+ }
+
+ @Test
+ void testAddContains() {
+ fareRuleSet.addContains("Zone1");
+ assertTrue(fareRuleSet.hasRules());
+ assertEquals(1, fareRuleSet.getContains().size());
+ }
+
+ @Test
+ void testAddRoute() {
+ FeedScopedId routeId = new FeedScopedId("feed", "route1");
+ fareRuleSet.addRoute(routeId);
+ assertTrue(fareRuleSet.hasRules());
+ assertEquals(1, fareRuleSet.getRoutes().size());
+ }
+
+ @Test
+ void testMatchesWithNoRules() {
+ var routes = Set.of(new FeedScopedId("feed", "route1"));
+ var trips = Set.of(new FeedScopedId("feed", "trip1"));
+ var zones = Set.of("zone1");
+ assertTrue(
+ fareRuleSet.matches("A", "B", Set.of(), Set.of(), Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ assertTrue(
+ fareRuleSet.matches(
+ "A",
+ "B",
+ zones,
+ routes,
+ trips,
+ 0,
+ Duration.ofMinutes(100),
+ Duration.ofMinutes(100)
+ )
+ );
+ }
+
+ @Test
+ void testMatchesWithOriginDestination() {
+ fareRuleSet.addOriginDestination("A", "B");
+ assertTrue(
+ fareRuleSet.matches("A", "B", Set.of(), Set.of(), Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ assertFalse(
+ fareRuleSet.matches("B", "C", Set.of(), Set.of(), Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ }
+
+ @Test
+ void testMatchesWithContains() {
+ Set zones = new HashSet<>();
+ zones.add("Zone1");
+ zones.add("Zone2");
+ fareRuleSet.addContains("Zone1");
+ fareRuleSet.addContains("Zone2");
+ assertTrue(
+ fareRuleSet.matches("A", "B", zones, Set.of(), Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ assertFalse(
+ fareRuleSet.matches("A", "B", Set.of(), Set.of(), Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ }
+
+ @Test
+ void testMatchesWithRoutes() {
+ Set routes = new HashSet<>();
+ FeedScopedId routeId = new FeedScopedId("feed", "route1");
+ FeedScopedId otherRouteId = new FeedScopedId("feed", "route2");
+ routes.add(routeId);
+ fareRuleSet.addRoute(routeId);
+ assertTrue(
+ fareRuleSet.matches("A", "B", Set.of(), routes, Set.of(), 0, Duration.ZERO, Duration.ZERO)
+ );
+ assertFalse(
+ fareRuleSet.matches(
+ "A",
+ "B",
+ Set.of(),
+ Set.of(otherRouteId),
+ Set.of(),
+ 0,
+ Duration.ZERO,
+ Duration.ZERO
+ )
+ );
+ }
+
+ @Test
+ void testMatchesWithTransfers() {
+ assertTrue(
+ fareRuleSet.matches("A", "B", Set.of(), Set.of(), Set.of(), 1, Duration.ZERO, Duration.ZERO)
+ );
+ assertFalse(
+ fareRuleSet.matches("A", "B", Set.of(), Set.of(), Set.of(), 2, Duration.ZERO, Duration.ZERO)
+ );
+ }
+
+ @Test
+ void testMatchesWithTransferDuration() {
+ assertTrue(
+ fareRuleSet.matches(
+ "A",
+ "B",
+ Set.of(),
+ Set.of(),
+ Set.of(),
+ 0,
+ Duration.ofSeconds(7000),
+ Duration.ZERO
+ )
+ );
+ assertFalse(
+ fareRuleSet.matches(
+ "A",
+ "B",
+ Set.of(),
+ Set.of(),
+ Set.of(),
+ 0,
+ Duration.ofSeconds(8000),
+ Duration.ZERO
+ )
+ );
+ }
+
+ @Test
+ void testMatchesWithJourneyDuration() {
+ FareAttribute journeyFare = FareAttribute
+ .of(new FeedScopedId("feed", "journey"))
+ .setPrice(Money.usDollars(3.00f))
+ .setPaymentMethod(1)
+ .setJourneyDuration(7200)
+ .build();
+ FareRuleSet journeyRuleSet = new FareRuleSet(journeyFare);
+
+ assertTrue(
+ journeyRuleSet.matches(
+ "A",
+ "B",
+ Set.of(),
+ Set.of(),
+ Set.of(),
+ 0,
+ Duration.ZERO,
+ Duration.ofSeconds(7000)
+ )
+ );
+ assertFalse(
+ journeyRuleSet.matches(
+ "A",
+ "B",
+ Set.of(),
+ Set.of(),
+ Set.of(),
+ 0,
+ Duration.ZERO,
+ Duration.ofSeconds(8000)
+ )
+ );
+ }
+
+ @Test
+ void testAgencyMethods() {
+ assertFalse(fareRuleSet.hasAgencyDefined());
+ assertNull(fareRuleSet.getAgency());
+
+ FeedScopedId agencyId = new FeedScopedId("feed", "agency1");
+ fareRuleSet.setAgency(agencyId);
+ assertTrue(fareRuleSet.hasAgencyDefined());
+ assertEquals(agencyId, fareRuleSet.getAgency());
+ }
+}
diff --git a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java
index 21d5a7f1696..e5b842a474a 100644
--- a/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/realtimeresolver/RealtimeResolverTest.java
@@ -129,9 +129,12 @@ void testPopulateLegsWithRealtimeKeepStaySeated() {
private static TripPattern delay(TripPattern pattern1, int seconds) {
var originalTimeTable = pattern1.getScheduledTimetable();
- var delayedTimetable = new Timetable(pattern1);
var delayedTripTimes = delay(originalTimeTable.getTripTimes(0), seconds);
- delayedTimetable.addTripTimes(delayedTripTimes);
+ var delayedTimetable = Timetable
+ .of()
+ .withTripPattern(pattern1)
+ .addTripTimes(delayedTripTimes)
+ .build();
return pattern1.copy().withScheduledTimeTable(delayedTimetable).build();
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java
index cf29eec8f49..d10dbc05090 100644
--- a/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java
+++ b/src/ext-test/java/org/opentripplanner/ext/siri/SiriTimetableSnapshotSourceTest.java
@@ -2,12 +2,11 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.opentripplanner.transit.model._data.TransitModelForTest.id;
import static org.opentripplanner.updater.spi.UpdateResultAssertions.assertFailure;
-import static org.opentripplanner.updater.trip.RealtimeTestEnvironment.SERVICE_DATE;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
-import org.opentripplanner.transit.model._data.TransitModelForTest;
import org.opentripplanner.transit.model.framework.FeedScopedId;
import org.opentripplanner.transit.model.network.Route;
import org.opentripplanner.transit.model.timetable.RealTimeState;
@@ -15,33 +14,48 @@
import org.opentripplanner.transit.model.timetable.TripIdAndServiceDate;
import org.opentripplanner.transit.service.TransitService;
import org.opentripplanner.updater.spi.UpdateError;
+import org.opentripplanner.updater.trip.RealtimeTestConstants;
import org.opentripplanner.updater.trip.RealtimeTestEnvironment;
-import uk.org.siri.siri20.EstimatedTimetableDeliveryStructure;
+import org.opentripplanner.updater.trip.TripInput;
-class SiriTimetableSnapshotSourceTest {
+class SiriTimetableSnapshotSourceTest implements RealtimeTestConstants {
+
+ private static final TripInput TRIP_1_INPUT = TripInput
+ .of(TRIP_1_ID)
+ .withRoute(ROUTE_1.copy().withOperator(OPERATOR1).build())
+ .addStop(STOP_A1, "0:00:10", "0:00:11")
+ .addStop(STOP_B1, "0:00:20", "0:00:21")
+ .build();
+
+ private static final TripInput TRIP_2_INPUT = TripInput
+ .of(TRIP_2_ID)
+ .addStop(STOP_A1, "0:01:00", "0:01:01")
+ .addStop(STOP_B1, "0:01:10", "0:01:11")
+ .addStop(STOP_C1, "0:01:20", "0:01:21")
+ .build();
@Test
void testCancelTrip() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
- assertEquals(RealTimeState.SCHEDULED, env.getTripTimesForTrip(env.trip1).getRealTimeState());
+ assertEquals(RealTimeState.SCHEDULED, env.getTripTimesForTrip(TRIP_1_ID).getRealTimeState());
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
.withCancellation(true)
.buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
assertEquals(1, result.successful());
- assertEquals(RealTimeState.CANCELED, env.getTripTimesForTrip(env.trip1).getRealTimeState());
+ assertEquals(RealTimeState.CANCELED, env.getTripTimesForTrip(TRIP_1_ID).getRealTimeState());
}
@Test
void testAddJourneyWithExistingRoute() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
- Route route = env.getTransitService().getRouteForId(env.route1Id);
+ Route route = ROUTE_1;
int numPatternForRoute = env.getTransitService().getPatternsForRoute(route).size();
String newJourneyId = "newJourney";
@@ -55,7 +69,7 @@ void testAddJourneyWithExistingRoute() {
"SCHEDULED | C1 0:01 0:01 | D1 0:03 0:03",
env.getScheduledTimetable(newJourneyId)
);
- FeedScopedId tripId = TransitModelForTest.id(newJourneyId);
+ FeedScopedId tripId = id(newJourneyId);
TransitService transitService = env.getTransitService();
Trip trip = transitService.getTripForId(tripId);
assertNotNull(trip);
@@ -75,7 +89,8 @@ void testAddJourneyWithExistingRoute() {
@Test
void testAddJourneyWithNewRoute() {
- var env = RealtimeTestEnvironment.siri();
+ // we actually don't need the trip, but it's the only way to add a route to the index
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
String newRouteRef = "new route ref";
var updates = createValidAddedJourney(env)
@@ -93,7 +108,7 @@ void testAddJourneyWithNewRoute() {
);
TransitService transitService = env.getTransitService();
assertEquals(numRoutes + 1, transitService.getAllRoutes().size());
- FeedScopedId newRouteId = TransitModelForTest.id(newRouteRef);
+ FeedScopedId newRouteId = id(newRouteRef);
Route newRoute = transitService.getRouteForId(newRouteId);
assertNotNull(newRoute);
assertEquals(1, transitService.getPatternsForRoute(newRoute).size());
@@ -101,7 +116,8 @@ void testAddJourneyWithNewRoute() {
@Test
void testAddJourneyMultipleTimes() {
- var env = RealtimeTestEnvironment.siri();
+ // we actually don't need the trip, but it's the only way to add a route to the index
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = createValidAddedJourney(env).buildEstimatedTimetableDeliveries();
int numTrips = env.getTransitService().getAllTrips().size();
@@ -115,20 +131,21 @@ void testAddJourneyMultipleTimes() {
@Test
void testAddedJourneyWithInvalidScheduledData() {
- var env = RealtimeTestEnvironment.siri();
+ // we actually don't need the trip, but it's the only way to add a route to the index
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
// Create an extra journey with invalid planned data (travel back in time)
// and valid real time data
var createExtraJourney = new SiriEtBuilder(env.getDateTimeHelper())
.withEstimatedVehicleJourneyCode("newJourney")
.withIsExtraJourney(true)
- .withOperatorRef(env.operator1Id.getId())
- .withLineRef(env.route1Id.getId())
+ .withOperatorRef(OPERATOR_1_ID)
+ .withLineRef(ROUTE_1_ID)
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("10:58", "10:48")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("10:08", "10:58")
)
.buildEstimatedTimetableDeliveries();
@@ -140,7 +157,7 @@ void testAddedJourneyWithInvalidScheduledData() {
@Test
void testAddedJourneyWithUnresolvableAgency() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().build();
// Create an extra journey with unknown line and operator
var createExtraJourney = new SiriEtBuilder(env.getDateTimeHelper())
@@ -150,9 +167,9 @@ void testAddedJourneyWithUnresolvableAgency() {
.withLineRef("unknown line")
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("10:58", "10:48")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("10:08", "10:58")
)
.buildEstimatedTimetableDeliveries();
@@ -164,17 +181,17 @@ void testAddedJourneyWithUnresolvableAgency() {
@Test
void testReplaceJourney() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withEstimatedVehicleJourneyCode("newJourney")
.withIsExtraJourney(true)
// replace trip1
- .withVehicleJourneyRef(env.trip1.getId().getId())
- .withOperatorRef(env.operator1Id.getId())
- .withLineRef(env.route1Id.getId())
- .withRecordedCalls(builder -> builder.call(env.stopA1).departAimedActual("00:01", "00:02"))
- .withEstimatedCalls(builder -> builder.call(env.stopC1).arriveAimedExpected("00:03", "00:04"))
+ .withVehicleJourneyRef(TRIP_1_ID)
+ .withOperatorRef(OPERATOR_1_ID)
+ .withLineRef(ROUTE_1_ID)
+ .withRecordedCalls(builder -> builder.call(STOP_A1).departAimedActual("00:01", "00:02"))
+ .withEstimatedCalls(builder -> builder.call(STOP_C1).arriveAimedExpected("00:03", "00:04"))
.buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
@@ -188,7 +205,7 @@ void testReplaceJourney() {
);
// Original trip should not get canceled
- var originalTripTimes = env.getTripTimesForTrip(env.trip1);
+ var originalTripTimes = env.getTripTimesForTrip(TRIP_1_ID);
assertEquals(RealTimeState.SCHEDULED, originalTripTimes.getRealTimeState());
}
@@ -197,17 +214,17 @@ void testReplaceJourney() {
*/
@Test
void testUpdateJourneyWithDatedVehicleJourneyRef() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = updatedJourneyBuilder(env)
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
.buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
assertEquals(1, result.successful());
assertTripUpdated(env);
assertEquals(
"UPDATED | A1 0:00:15 0:00:15 | B1 0:00:25 0:00:25",
- env.getRealtimeTimetable(env.trip1)
+ env.getRealtimeTimetable(TRIP_1_ID)
);
}
@@ -216,11 +233,11 @@ void testUpdateJourneyWithDatedVehicleJourneyRef() {
*/
@Test
void testUpdateJourneyWithFramedVehicleJourneyRef() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = updatedJourneyBuilder(env)
.withFramedVehicleJourneyRef(builder ->
- builder.withServiceDate(SERVICE_DATE).withVehicleJourneyRef(env.trip1.getId().getId())
+ builder.withServiceDate(SERVICE_DATE).withVehicleJourneyRef(TRIP_1_ID)
)
.buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
@@ -233,7 +250,7 @@ void testUpdateJourneyWithFramedVehicleJourneyRef() {
*/
@Test
void testUpdateJourneyWithoutJourneyRef() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = updatedJourneyBuilder(env).buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetable(updates);
@@ -246,7 +263,7 @@ void testUpdateJourneyWithoutJourneyRef() {
*/
@Test
void testUpdateJourneyWithFuzzyMatching() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = updatedJourneyBuilder(env).buildEstimatedTimetableDeliveries();
var result = env.applyEstimatedTimetableWithFuzzyMatcher(updates);
@@ -260,17 +277,17 @@ void testUpdateJourneyWithFuzzyMatching() {
*/
@Test
void testUpdateJourneyWithFuzzyMatchingAndMissingAimedDepartureTime() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withFramedVehicleJourneyRef(builder ->
- builder.withServiceDate(RealtimeTestEnvironment.SERVICE_DATE).withVehicleJourneyRef("XXX")
+ builder.withServiceDate(SERVICE_DATE).withVehicleJourneyRef("XXX")
)
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected(null, "00:00:12")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("00:00:20", "00:00:22")
)
.buildEstimatedTimetableDeliveries();
@@ -285,15 +302,13 @@ void testUpdateJourneyWithFuzzyMatchingAndMissingAimedDepartureTime() {
*/
@Test
void testChangeQuay() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
- .withRecordedCalls(builder ->
- builder.call(env.stopA1).departAimedActual("00:00:11", "00:00:15")
- )
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
+ .withRecordedCalls(builder -> builder.call(STOP_A1).departAimedActual("00:00:11", "00:00:15"))
.withEstimatedCalls(builder ->
- builder.call(env.stopB2).arriveAimedExpected("00:00:20", "00:00:33")
+ builder.call(STOP_B2).arriveAimedExpected("00:00:20", "00:00:33")
)
.buildEstimatedTimetableDeliveries();
@@ -302,23 +317,23 @@ void testChangeQuay() {
assertEquals(1, result.successful());
assertEquals(
"MODIFIED | A1 [R] 0:00:15 0:00:15 | B2 0:00:33 0:00:33",
- env.getRealtimeTimetable(env.trip1)
+ env.getRealtimeTimetable(TRIP_1_ID)
);
}
@Test
void testCancelStop() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_2_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip2.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_2_ID)
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("00:01:01", "00:01:01")
- .call(env.stopB1)
+ .call(STOP_B1)
.withIsCancellation(true)
- .call(env.stopC1)
+ .call(STOP_C1)
.arriveAimedExpected("00:01:30", "00:01:30")
)
.buildEstimatedTimetableDeliveries();
@@ -328,7 +343,7 @@ void testCancelStop() {
assertEquals(1, result.successful());
assertEquals(
"MODIFIED | A1 0:01:01 0:01:01 | B1 [C] 0:01:10 0:01:11 | C1 0:01:30 0:01:30",
- env.getRealtimeTimetable(env.trip2)
+ env.getRealtimeTimetable(TRIP_2_ID)
);
}
@@ -336,20 +351,18 @@ void testCancelStop() {
@Test
@Disabled("Not supported yet")
void testAddStop() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
- .withRecordedCalls(builder ->
- builder.call(env.stopA1).departAimedActual("00:00:11", "00:00:15")
- )
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
+ .withRecordedCalls(builder -> builder.call(STOP_A1).departAimedActual("00:00:11", "00:00:15"))
.withEstimatedCalls(builder ->
builder
- .call(env.stopD1)
+ .call(STOP_D1)
.withIsExtraCall(true)
.arriveAimedExpected("00:00:19", "00:00:20")
.departAimedExpected("00:00:24", "00:00:25")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("00:00:20", "00:00:33")
)
.buildEstimatedTimetableDeliveries();
@@ -359,7 +372,7 @@ void testAddStop() {
assertEquals(1, result.successful());
assertEquals(
"MODIFIED | A1 0:00:15 0:00:15 | D1 [C] 0:00:20 0:00:25 | B1 0:00:33 0:00:33",
- env.getRealtimeTimetable(env.trip1)
+ env.getRealtimeTimetable(TRIP_1_ID)
);
}
@@ -369,7 +382,7 @@ void testAddStop() {
@Test
void testNotMonitored() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withMonitored(false)
@@ -382,19 +395,19 @@ void testNotMonitored() {
@Test
void testReplaceJourneyWithoutEstimatedVehicleJourneyCode() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
.withDatedVehicleJourneyRef("newJourney")
.withIsExtraJourney(true)
- .withVehicleJourneyRef(env.trip1.getId().getId())
- .withOperatorRef(env.operator1Id.getId())
- .withLineRef(env.route1Id.getId())
+ .withVehicleJourneyRef(TRIP_1_ID)
+ .withOperatorRef(OPERATOR_1_ID)
+ .withLineRef(ROUTE_1_ID)
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("00:01", "00:02")
- .call(env.stopC1)
+ .call(STOP_C1)
.arriveAimedExpected("00:03", "00:04")
)
.buildEstimatedTimetableDeliveries();
@@ -407,15 +420,15 @@ void testReplaceJourneyWithoutEstimatedVehicleJourneyCode() {
@Test
void testNegativeHopTime() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
.withRecordedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedActual("00:00:11", "00:00:15")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedActual("00:00:20", "00:00:14")
)
.buildEstimatedTimetableDeliveries();
@@ -427,18 +440,18 @@ void testNegativeHopTime() {
@Test
void testNegativeDwellTime() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_2_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip2.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_2_ID)
.withRecordedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedActual("00:01:01", "00:01:01")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedActual("00:01:10", "00:01:13")
.departAimedActual("00:01:11", "00:01:12")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedActual("00:01:20", "00:01:20")
)
.buildEstimatedTimetableDeliveries();
@@ -452,19 +465,19 @@ void testNegativeDwellTime() {
@Test
@Disabled("Not supported yet")
void testExtraUnknownStop() {
- var env = RealtimeTestEnvironment.siri();
+ var env = RealtimeTestEnvironment.siri().addTrip(TRIP_1_INPUT).build();
var updates = new SiriEtBuilder(env.getDateTimeHelper())
- .withDatedVehicleJourneyRef(env.trip1.getId().getId())
+ .withDatedVehicleJourneyRef(TRIP_1_ID)
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("00:00:11", "00:00:15")
// Unexpected extra stop without isExtraCall flag
- .call(env.stopD1)
+ .call(STOP_D1)
.arriveAimedExpected("00:00:19", "00:00:20")
.departAimedExpected("00:00:24", "00:00:25")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("00:00:20", "00:00:33")
)
.buildEstimatedTimetableDeliveries();
@@ -478,20 +491,19 @@ private static SiriEtBuilder createValidAddedJourney(RealtimeTestEnvironment env
return new SiriEtBuilder(env.getDateTimeHelper())
.withEstimatedVehicleJourneyCode("newJourney")
.withIsExtraJourney(true)
- .withOperatorRef(env.operator1Id.getId())
- .withLineRef(env.route1Id.getId())
- .withRecordedCalls(builder -> builder.call(env.stopC1).departAimedActual("00:01", "00:02"))
- .withEstimatedCalls(builder -> builder.call(env.stopD1).arriveAimedExpected("00:03", "00:04")
- );
+ .withOperatorRef(OPERATOR_1_ID)
+ .withLineRef(ROUTE_1_ID)
+ .withRecordedCalls(builder -> builder.call(STOP_C1).departAimedActual("00:01", "00:02"))
+ .withEstimatedCalls(builder -> builder.call(STOP_D1).arriveAimedExpected("00:03", "00:04"));
}
private static SiriEtBuilder updatedJourneyBuilder(RealtimeTestEnvironment env) {
return new SiriEtBuilder(env.getDateTimeHelper())
.withEstimatedCalls(builder ->
builder
- .call(env.stopA1)
+ .call(STOP_A1)
.departAimedExpected("00:00:11", "00:00:15")
- .call(env.stopB1)
+ .call(STOP_B1)
.arriveAimedExpected("00:00:20", "00:00:25")
);
}
@@ -499,7 +511,7 @@ private static SiriEtBuilder updatedJourneyBuilder(RealtimeTestEnvironment env)
private static void assertTripUpdated(RealtimeTestEnvironment env) {
assertEquals(
"UPDATED | A1 0:00:15 0:00:15 | B1 0:00:25 0:00:25",
- env.getRealtimeTimetable(env.trip1)
+ env.getRealtimeTimetable(TRIP_1_ID)
);
}
}
diff --git a/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/LayerFiltersTest.java b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/LayerFiltersTest.java
new file mode 100644
index 00000000000..f12d43b62cf
--- /dev/null
+++ b/src/ext-test/java/org/opentripplanner/ext/vectortiles/layers/LayerFiltersTest.java
@@ -0,0 +1,42 @@
+package org.opentripplanner.ext.vectortiles.layers;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.jupiter.api.Test;
+import org.opentripplanner.transit.model._data.PatternTestModel;
+import org.opentripplanner.transit.model._data.TransitModelForTest;
+import org.opentripplanner.transit.model.network.TripPattern;
+import org.opentripplanner.transit.model.site.RegularStop;
+
+class LayerFiltersTest {
+
+ private static final RegularStop STOP = TransitModelForTest.of().stop("1").build();
+ private static final LocalDate DATE = LocalDate.of(2024, 9, 5);
+ private static final TripPattern PATTERN = PatternTestModel.pattern();
+
+ @Test
+ void includeStopWithinServiceWeek() {
+ var predicate = LayerFilters.buildCurrentServiceWeekPredicate(
+ s -> List.of(PATTERN),
+ trip -> List.of(DATE),
+ () -> DATE
+ );
+
+ assertTrue(predicate.test(STOP));
+ }
+
+ @Test
+ void excludeOutsideServiceWeek() {
+ var inThreeWeeks = DATE.plusDays(21);
+ var predicate = LayerFilters.buildCurrentServiceWeekPredicate(
+ s -> List.of(PATTERN),
+ trip -> List.of(inThreeWeeks),
+ () -> DATE
+ );
+
+ assertFalse(predicate.test(STOP));
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java
index ee4c87924ea..d7a3a0425ec 100644
--- a/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java
+++ b/src/ext/java/org/opentripplanner/ext/fares/impl/DefaultFareServiceFactory.java
@@ -92,7 +92,7 @@ protected void fillFareRules(
FareRuleSet fareRule = fareRuleSet.get(id);
if (fareRule == null) {
// Should never happen by design
- LOG.error("Inexistant fare ID in fare rule: " + id);
+ LOG.error("Nonexistent fare ID in fare rule: " + id);
continue;
}
String contains = rule.getContainsId();
diff --git a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java
index 34a03c1fc06..d48cad48450 100644
--- a/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java
+++ b/src/ext/java/org/opentripplanner/ext/fares/impl/OrcaFareFactory.java
@@ -24,6 +24,8 @@ public FareService makeFareService() {
@Override
public void processGtfs(FareRulesData fareRuleService, OtpTransitService transitService) {
fillFareRules(fareRuleService.fareAttributes(), fareRuleService.fareRules(), regularFareRules);
+ // ORCA agencies don't rely on fare attributes without rules, so let's remove them.
+ regularFareRules.entrySet().removeIf(entry -> !entry.getValue().hasRules());
}
/**
diff --git a/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java b/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java
index 30119631216..b8fbb0f1fe9 100644
--- a/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java
+++ b/src/ext/java/org/opentripplanner/ext/fares/model/FareRuleSet.java
@@ -40,6 +40,19 @@ public Set getRouteOriginDestinations() {
return routeOriginDestinations;
}
+ /**
+ * Determine whether the FareRuleSet has any rules added.
+ * @return True if any rules have been added.
+ */
+ public boolean hasRules() {
+ return (
+ !routes.isEmpty() ||
+ !originDestinations.isEmpty() ||
+ !routeOriginDestinations.isEmpty() ||
+ !contains.isEmpty()
+ );
+ }
+
public void addContains(String containsId) {
contains.add(containsId);
}
@@ -60,6 +73,19 @@ public FareAttribute getFareAttribute() {
return fareAttribute;
}
+ /**
+ * Determines whether the FareRuleSet matches against a set of itinerary parameters
+ * based on the added rules and fare attribute
+ * @param startZone Origin zone
+ * @param endZone End zone
+ * @param zonesVisited A set containing the names of zones visited on the fare
+ * @param routesVisited A set containing the route IDs visited
+ * @param tripsVisited [Not implemented] A set containing the trip IDs visited
+ * @param transfersUsed Number of transfers already used
+ * @param tripTime Time from beginning of first leg to beginning of current leg to be evaluated
+ * @param journeyTime Total journey time from beginning of first leg to end of current leg
+ * @return True if this FareAttribute should apply to this leg
+ */
public boolean matches(
String startZone,
String endZone,
diff --git a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java
index cdbaa8f3d4c..ed2378a839d 100644
--- a/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java
+++ b/src/ext/java/org/opentripplanner/ext/siri/AddedTripBuilder.java
@@ -210,13 +210,6 @@ Result build() {
// TODO: We always create a new TripPattern to be able to modify its scheduled timetable
StopPattern stopPattern = new StopPattern(aimedStopTimes);
- TripPattern pattern = TripPattern
- .of(getTripPatternId.apply(trip))
- .withRoute(trip.getRoute())
- .withMode(trip.getMode())
- .withNetexSubmode(trip.getNetexSubMode())
- .withStopPattern(stopPattern)
- .build();
RealTimeTripTimes tripTimes = TripTimesFactory.tripTimes(
trip,
@@ -229,7 +222,16 @@ Result build() {
// therefore they must be valid
tripTimes.validateNonIncreasingTimes();
tripTimes.setServiceCode(transitService.getServiceCodeForId(trip.getServiceId()));
- pattern.add(tripTimes);
+
+ TripPattern pattern = TripPattern
+ .of(getTripPatternId.apply(trip))
+ .withRoute(trip.getRoute())
+ .withMode(trip.getMode())
+ .withNetexSubmode(trip.getNetexSubMode())
+ .withStopPattern(stopPattern)
+ .withScheduledTimeTableBuilder(builder -> builder.addTripTimes(tripTimes))
+ .build();
+
RealTimeTripTimes updatedTripTimes = tripTimes.copyScheduledTimes();
// Loop through calls again and apply updates
diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/LayerFilters.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/LayerFilters.java
new file mode 100644
index 00000000000..7d63bbe5398
--- /dev/null
+++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/LayerFilters.java
@@ -0,0 +1,73 @@
+package org.opentripplanner.ext.vectortiles.layers;
+
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.temporal.TemporalAdjusters;
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import org.opentripplanner.apis.gtfs.model.LocalDateRange;
+import org.opentripplanner.transit.model.network.TripPattern;
+import org.opentripplanner.transit.model.site.RegularStop;
+import org.opentripplanner.transit.model.timetable.Trip;
+import org.opentripplanner.transit.service.PatternByServiceDatesFilter;
+import org.opentripplanner.transit.service.TransitService;
+
+/**
+ * Predicates for filtering elements of vector tile layers. Currently only contains predicates
+ * for {@link RegularStop}. Once more types need to be filtered, this may need some refactoring.
+ */
+public class LayerFilters {
+
+ /**
+ * No filter is applied: all stops are included in the result.
+ */
+ public static final Predicate NO_FILTER = x -> true;
+
+ /**
+ * Returns a predicate which only includes stop which are visited by a pattern that is in the current
+ * "service week", which lasts from Sunday to Sunday.
+ */
+ public static Predicate buildCurrentServiceWeekPredicate(
+ Function> getPatternsForStop,
+ Function> getServiceDatesForTrip,
+ Supplier nowSupplier
+ ) {
+ var serviceDate = nowSupplier.get();
+ var lastSunday = serviceDate.with(TemporalAdjusters.previousOrSame(DayOfWeek.SUNDAY));
+ var nextSundayPlusOne = serviceDate.with(TemporalAdjusters.next(DayOfWeek.SUNDAY)).plusDays(1);
+
+ var filter = new PatternByServiceDatesFilter(
+ // reminder, the end of the date range is exclusive so it's the next Sunday plus one day
+ new LocalDateRange(lastSunday, nextSundayPlusOne),
+ // not used
+ route -> List.of(),
+ getServiceDatesForTrip
+ );
+
+ return regularStop -> {
+ var patterns = getPatternsForStop.apply(regularStop);
+ var patternsInCurrentWeek = filter.filterPatterns(patterns);
+ return !patternsInCurrentWeek.isEmpty();
+ };
+ }
+
+ public static Predicate forType(FilterType type, TransitService transitService) {
+ return switch (type) {
+ case NONE -> NO_FILTER;
+ case SUNDAY_TO_SUNDAY_SERVICE_WEEK -> buildCurrentServiceWeekPredicate(
+ transitService::getPatternsForStop,
+ trip ->
+ transitService.getCalendarService().getServiceDatesForServiceId(trip.getServiceId()),
+ () -> LocalDate.now(transitService.getTimeZone())
+ );
+ };
+ }
+
+ public enum FilterType {
+ NONE,
+ SUNDAY_TO_SUNDAY_SERVICE_WEEK,
+ }
+}
diff --git a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
index aa664497728..141157f8f3e 100644
--- a/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
+++ b/src/ext/java/org/opentripplanner/ext/vectortiles/layers/stops/StopsLayerBuilder.java
@@ -5,24 +5,20 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.function.BiFunction;
-import java.util.stream.Collectors;
+import java.util.function.Predicate;
import org.locationtech.jts.geom.Envelope;
import org.locationtech.jts.geom.Geometry;
-import org.opentripplanner.apis.support.mapping.PropertyMapper;
import org.opentripplanner.ext.vectortiles.VectorTilesResource;
+import org.opentripplanner.ext.vectortiles.layers.LayerFilters;
import org.opentripplanner.inspector.vector.LayerBuilder;
import org.opentripplanner.inspector.vector.LayerParameters;
import org.opentripplanner.transit.model.site.RegularStop;
import org.opentripplanner.transit.service.TransitService;
-public class StopsLayerBuilder extends LayerBuilder {
+public class StopsLayerBuilder extends LayerBuilder {
- static Map>> mappers = Map.of(
- MapperType.Digitransit,
- DigitransitStopPropertyMapper::create
- );
private final TransitService transitService;
+ private final Predicate filter;
public StopsLayerBuilder(
TransitService transitService,
@@ -30,7 +26,7 @@ public StopsLayerBuilder(
Locale locale
) {
super(
- (PropertyMapper) Map
+ Map
.ofEntries(
entry(MapperType.Digitransit, new DigitransitStopPropertyMapper(transitService, locale)),
entry(
@@ -43,12 +39,14 @@ public StopsLayerBuilder(
layerParameters.expansionFactor()
);
this.transitService = transitService;
+ this.filter = LayerFilters.forType(layerParameters.filterType(), transitService);
}
protected List getGeometries(Envelope query) {
return transitService
.findRegularStops(query)
.stream()
+ .filter(filter)
.map(stop -> {
Geometry point = stop.getGeometry();
@@ -56,7 +54,7 @@ protected List getGeometries(Envelope query) {
return point;
})
- .collect(Collectors.toList());
+ .toList();
}
enum MapperType {
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
index 9ec83a4bf67..950e3d9bba5 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/LegImpl.java
@@ -23,6 +23,7 @@
import org.opentripplanner.model.plan.StreetLeg;
import org.opentripplanner.model.plan.TransitLeg;
import org.opentripplanner.model.plan.WalkStep;
+import org.opentripplanner.model.plan.legreference.LegReferenceSerializer;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.alternativelegs.AlternativeLegs;
import org.opentripplanner.routing.alternativelegs.AlternativeLegsFilter;
@@ -189,10 +190,12 @@ public DataFetcher realTime() {
return environment -> getSource(environment).getRealTime();
}
- // TODO
@Override
public DataFetcher realtimeState() {
- return environment -> null;
+ return environment -> {
+ var state = getSource(environment).getRealTimeState();
+ return (state != null) ? state.name() : null;
+ };
}
@Override
@@ -324,4 +327,15 @@ public DataFetcher> nextLegs() {
public DataFetcher accessibilityScore() {
return environment -> NumberMapper.toDouble(getSource(environment).accessibilityScore());
}
+
+ @Override
+ public DataFetcher id() {
+ return environment -> {
+ var ref = getSource(environment).getLegReference();
+ if (ref == null) {
+ return null;
+ }
+ return LegReferenceSerializer.encode(ref);
+ };
+ }
}
diff --git a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
index 0e70c13074b..95984ba6dd0 100644
--- a/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
+++ b/src/main/java/org/opentripplanner/apis/gtfs/datafetchers/QueryTypeImpl.java
@@ -27,12 +27,12 @@
import org.locationtech.jts.geom.Envelope;
import org.opentripplanner.apis.gtfs.GraphQLRequestContext;
import org.opentripplanner.apis.gtfs.GraphQLUtils;
-import org.opentripplanner.apis.gtfs.PatternByServiceDatesFilter;
import org.opentripplanner.apis.gtfs.generated.GraphQLDataFetchers;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes;
import org.opentripplanner.apis.gtfs.generated.GraphQLTypes.GraphQLQueryTypeStopsByRadiusArgs;
import org.opentripplanner.apis.gtfs.mapping.routerequest.LegacyRouteRequestMapper;
import org.opentripplanner.apis.gtfs.mapping.routerequest.RouteRequestMapper;
+import org.opentripplanner.apis.gtfs.support.filter.PatternByDateFilterUtil;
import org.opentripplanner.apis.gtfs.support.time.LocalDateRangeUtil;
import org.opentripplanner.ext.fares.impl.DefaultFareService;
import org.opentripplanner.ext.fares.impl.GtfsFaresService;
@@ -42,6 +42,9 @@
import org.opentripplanner.graph_builder.issue.api.DataImportIssueStore;
import org.opentripplanner.gtfs.mapping.DirectionMapper;
import org.opentripplanner.model.TripTimeOnDate;
+import org.opentripplanner.model.plan.Leg;
+import org.opentripplanner.model.plan.legreference.LegReference;
+import org.opentripplanner.model.plan.legreference.LegReferenceSerializer;
import org.opentripplanner.routing.alertpatch.EntitySelector;
import org.opentripplanner.routing.alertpatch.TransitAlert;
import org.opentripplanner.routing.api.request.RouteRequest;
@@ -362,6 +365,20 @@ public DataFetcher> nearest() {
};
}
+ @Override
+ public DataFetcher leg() {
+ return environment -> {
+ TransitService transitService = getTransitService(environment);
+ var args = new GraphQLTypes.GraphQLQueryTypeLegArgs(environment.getArguments());
+ String id = args.getGraphQLId();
+ LegReference ref = LegReferenceSerializer.decode(id);
+ if (ref == null) {
+ return null;
+ }
+ return ref.getLeg(transitService);
+ };
+ }
+
@Override
public DataFetcher